1 /*
2 * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package javax.swing.text.rtf;
26
27 import java.lang.*;
28 import java.util.*;
29 import java.io.*;
30 import java.awt.Color;
31 import java.security.AccessController;
32 import java.security.PrivilegedAction;
33 import javax.swing.text.*;
34
35 /**
36 * Takes a sequence of RTF tokens and text and appends the text
37 * described by the RTF to a <code>StyledDocument</code> (the <em>target</em>).
38 * The RTF is lexed
39 * from the character stream by the <code>RTFParser</code> which is this class's
40 * superclass.
41 *
42 * This class is an indirect subclass of OutputStream. It must be closed
43 * in order to guarantee that all of the text has been sent to
44 * the text acceptor.
45 *
46 * @see RTFParser
47 * @see java.io.OutputStream
48 */
49 class RTFReader extends RTFParser
50 {
51 /** The object to which the parsed text is sent. */
52 StyledDocument target;
53
54 /** Miscellaneous information about the parser's state. This
55 * dictionary is saved and restored when an RTF group begins
56 * or ends. */
57 Dictionary<Object, Object> parserState; /* Current parser state */
58 /** This is the "dst" item from parserState. rtfDestination
59 * is the current rtf destination. It is cached in an instance
60 * variable for speed. */
61 Destination rtfDestination;
62 /** This holds the current document attributes. */
63 MutableAttributeSet documentAttributes;
64
65 /** This Dictionary maps Integer font numbers to String font names. */
66 Dictionary<Integer, String> fontTable;
67 /** This array maps color indices to Color objects. */
68 Color[] colorTable;
69 /** This Map maps character style numbers to Style objects. */
70 Map<Integer, Style> characterStyles;
71 /** This Map maps paragraph style numbers to Style objects. */
72 Map<Integer, Style> paragraphStyles;
73 /** This Map maps section style numbers to Style objects. */
74 Map<Integer, Style> sectionStyles;
75
76 /** This is the RTF version number, extracted from the \rtf keyword.
77 * The version information is currently not used. */
78 int rtfversion;
79
80 /** <code>true</code> to indicate that if the next keyword is unknown,
81 * the containing group should be ignored. */
82 boolean ignoreGroupIfUnknownKeyword;
83
84 /** The parameter of the most recently parsed \\ucN keyword,
85 * used for skipping alternative representations after a
86 * Unicode character. */
87 int skippingCharacters;
88
89 static private Dictionary<String, RTFAttribute> straightforwardAttributes;
90 static {
91 straightforwardAttributes = RTFAttributes.attributesByKeyword();
92 }
93
94 private MockAttributeSet mockery;
95
96 /* this should be final, but there's a bug in javac... */
97 /** textKeywords maps RTF keywords to single-character strings,
98 * for those keywords which simply insert some text. */
99 static Dictionary<String, String> textKeywords = null;
100 static {
101 textKeywords = new Hashtable<String, String>();
102 textKeywords.put("\\", "\\");
103 textKeywords.put("{", "{");
104 textKeywords.put("}", "}");
105 textKeywords.put(" ", "\u00A0"); /* not in the spec... */
106 textKeywords.put("~", "\u00A0"); /* nonbreaking space */
107 textKeywords.put("_", "\u2011"); /* nonbreaking hyphen */
108 textKeywords.put("bullet", "\u2022");
109 textKeywords.put("emdash", "\u2014");
110 textKeywords.put("emspace", "\u2003");
111 textKeywords.put("endash", "\u2013");
112 textKeywords.put("enspace", "\u2002");
113 textKeywords.put("ldblquote", "\u201C");
114 textKeywords.put("lquote", "\u2018");
115 textKeywords.put("ltrmark", "\u200E");
116 textKeywords.put("rdblquote", "\u201D");
117 textKeywords.put("rquote", "\u2019");
118 textKeywords.put("rtlmark", "\u200F");
119 textKeywords.put("tab", "\u0009");
120 textKeywords.put("zwj", "\u200D");
121 textKeywords.put("zwnj", "\u200C");
122
123 /* There is no Unicode equivalent to an optional hyphen, as far as
124 I can tell. */
125 textKeywords.put("-", "\u2027"); /* TODO: optional hyphen */
126 }
127
128 /* some entries in parserState */
129 static final String TabAlignmentKey = "tab_alignment";
130 static final String TabLeaderKey = "tab_leader";
131
132 static Dictionary<String, char[]> characterSets;
133 static boolean useNeXTForAnsi = false;
134 static {
135 characterSets = new Hashtable<String, char[]>();
136 }
137
138 /* TODO: per-font font encodings ( \fcharset control word ) ? */
139
140 /**
141 * Creates a new RTFReader instance. Text will be sent to
142 * the specified TextAcceptor.
143 *
144 * @param destination The TextAcceptor which is to receive the text.
145 */
RTFReader(StyledDocument destination)146 public RTFReader(StyledDocument destination)
147 {
148 int i;
149
150 target = destination;
151 parserState = new Hashtable<Object, Object>();
152 fontTable = new Hashtable<Integer, String>();
153
154 rtfversion = -1;
155
156 mockery = new MockAttributeSet();
157 documentAttributes = new SimpleAttributeSet();
158 }
159
160 /** Called when the RTFParser encounters a bin keyword in the
161 * RTF stream.
162 *
163 * @see RTFParser
164 */
handleBinaryBlob(byte[] data)165 public void handleBinaryBlob(byte[] data)
166 {
167 if (skippingCharacters > 0) {
168 /* a blob only counts as one character for skipping purposes */
169 skippingCharacters --;
170 return;
171 }
172
173 /* someday, someone will want to do something with blobs */
174 }
175
176
177 /**
178 * Handles any pure text (containing no control characters) in the input
179 * stream. Called by the superclass. */
handleText(String text)180 public void handleText(String text)
181 {
182 if (skippingCharacters > 0) {
183 if (skippingCharacters >= text.length()) {
184 skippingCharacters -= text.length();
185 return;
186 } else {
187 text = text.substring(skippingCharacters);
188 skippingCharacters = 0;
189 }
190 }
191
192 if (rtfDestination != null) {
193 rtfDestination.handleText(text);
194 return;
195 }
196
197 warning("Text with no destination. oops.");
198 }
199
200 /** The default color for text which has no specified color. */
defaultColor()201 Color defaultColor()
202 {
203 return Color.black;
204 }
205
206 /** Called by the superclass when a new RTF group is begun.
207 * This implementation saves the current <code>parserState</code>, and gives
208 * the current destination a chance to save its own state.
209 * @see RTFParser#begingroup
210 */
begingroup()211 public void begingroup()
212 {
213 if (skippingCharacters > 0) {
214 /* TODO this indicates an error in the RTF. Log it? */
215 skippingCharacters = 0;
216 }
217
218 /* we do this little dance to avoid cloning the entire state stack and
219 immediately throwing it away. */
220 Object oldSaveState = parserState.get("_savedState");
221 if (oldSaveState != null)
222 parserState.remove("_savedState");
223 Dictionary<String, Object> saveState = (Dictionary<String, Object>)((Hashtable)parserState).clone();
224 if (oldSaveState != null)
225 saveState.put("_savedState", oldSaveState);
226 parserState.put("_savedState", saveState);
227
228 if (rtfDestination != null)
229 rtfDestination.begingroup();
230 }
231
232 /** Called by the superclass when the current RTF group is closed.
233 * This restores the parserState saved by <code>begingroup()</code>
234 * as well as invoking the endgroup method of the current
235 * destination.
236 * @see RTFParser#endgroup
237 */
endgroup()238 public void endgroup()
239 {
240 if (skippingCharacters > 0) {
241 /* NB this indicates an error in the RTF. Log it? */
242 skippingCharacters = 0;
243 }
244
245 Dictionary<Object, Object> restoredState = (Dictionary<Object, Object>)parserState.get("_savedState");
246 Destination restoredDestination = (Destination)restoredState.get("dst");
247 if (restoredDestination != rtfDestination) {
248 rtfDestination.close(); /* allow the destination to clean up */
249 rtfDestination = restoredDestination;
250 }
251 Dictionary oldParserState = parserState;
252 parserState = restoredState;
253 if (rtfDestination != null)
254 rtfDestination.endgroup(oldParserState);
255 }
256
setRTFDestination(Destination newDestination)257 protected void setRTFDestination(Destination newDestination)
258 {
259 /* Check that setting the destination won't close the
260 current destination (should never happen) */
261 Dictionary previousState = (Dictionary)parserState.get("_savedState");
262 if (previousState != null) {
263 if (rtfDestination != previousState.get("dst")) {
264 warning("Warning, RTF destination overridden, invalid RTF.");
265 rtfDestination.close();
266 }
267 }
268 rtfDestination = newDestination;
269 parserState.put("dst", rtfDestination);
270 }
271
272 /** Called by the user when there is no more input (<i>i.e.</i>,
273 * at the end of the RTF file.)
274 *
275 * @see OutputStream#close
276 */
close()277 public void close()
278 throws IOException
279 {
280 Enumeration docProps = documentAttributes.getAttributeNames();
281 while(docProps.hasMoreElements()) {
282 Object propName = docProps.nextElement();
283 target.putProperty(propName,
284 documentAttributes.getAttribute(propName));
285 }
286
287 /* RTFParser should have ensured that all our groups are closed */
288
289 warning("RTF filter done.");
290
291 super.close();
292 }
293
294 /**
295 * Handles a parameterless RTF keyword. This is called by the superclass
296 * (RTFParser) when a keyword is found in the input stream.
297 *
298 * @returns <code>true</code> if the keyword is recognized and handled;
299 * <code>false</code> otherwise
300 * @see RTFParser#handleKeyword
301 */
handleKeyword(String keyword)302 public boolean handleKeyword(String keyword)
303 {
304 String item;
305 boolean ignoreGroupIfUnknownKeywordSave = ignoreGroupIfUnknownKeyword;
306
307 if (skippingCharacters > 0) {
308 skippingCharacters --;
309 return true;
310 }
311
312 ignoreGroupIfUnknownKeyword = false;
313
314 if ((item = textKeywords.get(keyword)) != null) {
315 handleText(item);
316 return true;
317 }
318
319 if (keyword.equals("fonttbl")) {
320 setRTFDestination(new FonttblDestination());
321 return true;
322 }
323
324 if (keyword.equals("colortbl")) {
325 setRTFDestination(new ColortblDestination());
326 return true;
327 }
328
329 if (keyword.equals("stylesheet")) {
330 setRTFDestination(new StylesheetDestination());
331 return true;
332 }
333
334 if (keyword.equals("info")) {
335 setRTFDestination(new InfoDestination());
336 return false;
337 }
338
339 if (keyword.equals("mac")) {
340 setCharacterSet("mac");
341 return true;
342 }
343
344 if (keyword.equals("ansi")) {
345 if (useNeXTForAnsi)
346 setCharacterSet("NeXT");
347 else
348 setCharacterSet("ansi");
349 return true;
350 }
351
352 if (keyword.equals("next")) {
353 setCharacterSet("NeXT");
354 return true;
355 }
356
357 if (keyword.equals("pc")) {
358 setCharacterSet("cpg437"); /* IBM Code Page 437 */
359 return true;
360 }
361
362 if (keyword.equals("pca")) {
363 setCharacterSet("cpg850"); /* IBM Code Page 850 */
364 return true;
365 }
366
367 if (keyword.equals("*")) {
368 ignoreGroupIfUnknownKeyword = true;
369 return true;
370 }
371
372 if (rtfDestination != null) {
373 if(rtfDestination.handleKeyword(keyword))
374 return true;
375 }
376
377 /* this point is reached only if the keyword is unrecognized */
378
379 /* other destinations we don't understand and therefore ignore */
380 if (keyword.equals("aftncn") ||
381 keyword.equals("aftnsep") ||
382 keyword.equals("aftnsepc") ||
383 keyword.equals("annotation") ||
384 keyword.equals("atnauthor") ||
385 keyword.equals("atnicn") ||
386 keyword.equals("atnid") ||
387 keyword.equals("atnref") ||
388 keyword.equals("atntime") ||
389 keyword.equals("atrfend") ||
390 keyword.equals("atrfstart") ||
391 keyword.equals("bkmkend") ||
392 keyword.equals("bkmkstart") ||
393 keyword.equals("datafield") ||
394 keyword.equals("do") ||
395 keyword.equals("dptxbxtext") ||
396 keyword.equals("falt") ||
397 keyword.equals("field") ||
398 keyword.equals("file") ||
399 keyword.equals("filetbl") ||
400 keyword.equals("fname") ||
401 keyword.equals("fontemb") ||
402 keyword.equals("fontfile") ||
403 keyword.equals("footer") ||
404 keyword.equals("footerf") ||
405 keyword.equals("footerl") ||
406 keyword.equals("footerr") ||
407 keyword.equals("footnote") ||
408 keyword.equals("ftncn") ||
409 keyword.equals("ftnsep") ||
410 keyword.equals("ftnsepc") ||
411 keyword.equals("header") ||
412 keyword.equals("headerf") ||
413 keyword.equals("headerl") ||
414 keyword.equals("headerr") ||
415 keyword.equals("keycode") ||
416 keyword.equals("nextfile") ||
417 keyword.equals("object") ||
418 keyword.equals("pict") ||
419 keyword.equals("pn") ||
420 keyword.equals("pnseclvl") ||
421 keyword.equals("pntxtb") ||
422 keyword.equals("pntxta") ||
423 keyword.equals("revtbl") ||
424 keyword.equals("rxe") ||
425 keyword.equals("tc") ||
426 keyword.equals("template") ||
427 keyword.equals("txe") ||
428 keyword.equals("xe")) {
429 ignoreGroupIfUnknownKeywordSave = true;
430 }
431
432 if (ignoreGroupIfUnknownKeywordSave) {
433 setRTFDestination(new DiscardingDestination());
434 }
435
436 return false;
437 }
438
439 /**
440 * Handles an RTF keyword and its integer parameter.
441 * This is called by the superclass
442 * (RTFParser) when a keyword is found in the input stream.
443 *
444 * @returns <code>true</code> if the keyword is recognized and handled;
445 * <code>false</code> otherwise
446 * @see RTFParser#handleKeyword
447 */
handleKeyword(String keyword, int parameter)448 public boolean handleKeyword(String keyword, int parameter)
449 {
450 boolean ignoreGroupIfUnknownKeywordSave = ignoreGroupIfUnknownKeyword;
451
452 if (skippingCharacters > 0) {
453 skippingCharacters --;
454 return true;
455 }
456
457 ignoreGroupIfUnknownKeyword = false;
458
459 if (keyword.equals("uc")) {
460 /* count of characters to skip after a unicode character */
461 parserState.put("UnicodeSkip", Integer.valueOf(parameter));
462 return true;
463 }
464 if (keyword.equals("u")) {
465 if (parameter < 0)
466 parameter = parameter + 65536;
467 handleText((char)parameter);
468 Number skip = (Number)(parserState.get("UnicodeSkip"));
469 if (skip != null) {
470 skippingCharacters = skip.intValue();
471 } else {
472 skippingCharacters = 1;
473 }
474 return true;
475 }
476
477 if (keyword.equals("rtf")) {
478 rtfversion = parameter;
479 setRTFDestination(new DocumentDestination());
480 return true;
481 }
482
483 if (keyword.startsWith("NeXT") ||
484 keyword.equals("private"))
485 ignoreGroupIfUnknownKeywordSave = true;
486
487 if (rtfDestination != null) {
488 if(rtfDestination.handleKeyword(keyword, parameter))
489 return true;
490 }
491
492 /* this point is reached only if the keyword is unrecognized */
493
494 if (ignoreGroupIfUnknownKeywordSave) {
495 setRTFDestination(new DiscardingDestination());
496 }
497
498 return false;
499 }
500
setTargetAttribute(String name, Object value)501 private void setTargetAttribute(String name, Object value)
502 {
503 // target.changeAttributes(new LFDictionary(LFArray.arrayWithObject(value), LFArray.arrayWithObject(name)));
504 }
505
506 /**
507 * setCharacterSet sets the current translation table to correspond with
508 * the named character set. The character set is loaded if necessary.
509 *
510 * @see AbstractFilter
511 */
setCharacterSet(String name)512 public void setCharacterSet(String name)
513 {
514 Object set;
515
516 try {
517 set = getCharacterSet(name);
518 } catch (Exception e) {
519 warning("Exception loading RTF character set \"" + name + "\": " + e);
520 set = null;
521 }
522
523 if (set != null) {
524 translationTable = (char[])set;
525 } else {
526 warning("Unknown RTF character set \"" + name + "\"");
527 if (!name.equals("ansi")) {
528 try {
529 translationTable = (char[])getCharacterSet("ansi");
530 } catch (IOException e) {
531 throw new InternalError("RTFReader: Unable to find character set resources (" + e + ")", e);
532 }
533 }
534 }
535
536 setTargetAttribute(Constants.RTFCharacterSet, name);
537 }
538
539 /** Adds a character set to the RTFReader's list
540 * of known character sets */
541 public static void
defineCharacterSet(String name, char[] table)542 defineCharacterSet(String name, char[] table)
543 {
544 if (table.length < 256)
545 throw new IllegalArgumentException("Translation table must have 256 entries.");
546 characterSets.put(name, table);
547 }
548
549 /** Looks up a named character set. A character set is a 256-entry
550 * array of characters, mapping unsigned byte values to their Unicode
551 * equivalents. The character set is loaded if necessary.
552 *
553 * @returns the character set
554 */
555 public static Object
getCharacterSet(final String name)556 getCharacterSet(final String name)
557 throws IOException
558 {
559 char[] set = characterSets.get(name);
560 if (set == null) {
561 InputStream charsetStream = AccessController.doPrivileged(
562 new PrivilegedAction<InputStream>() {
563 public InputStream run() {
564 return RTFReader.class.getResourceAsStream("charsets/" + name + ".txt");
565 }
566 });
567 set = readCharset(charsetStream);
568 defineCharacterSet(name, set);
569 }
570 return set;
571 }
572
573 /** Parses a character set from an InputStream. The character set
574 * must contain 256 decimal integers, separated by whitespace, with
575 * no punctuation. B- and C- style comments are allowed.
576 *
577 * @returns the newly read character set
578 */
readCharset(InputStream strm)579 static char[] readCharset(InputStream strm)
580 throws IOException
581 {
582 char[] values = new char[256];
583 int i;
584 StreamTokenizer in = new StreamTokenizer(new BufferedReader(
585 new InputStreamReader(strm, "ISO-8859-1")));
586
587 in.eolIsSignificant(false);
588 in.commentChar('#');
589 in.slashSlashComments(true);
590 in.slashStarComments(true);
591
592 i = 0;
593 while (i < 256) {
594 int ttype;
595 try {
596 ttype = in.nextToken();
597 } catch (Exception e) {
598 throw new IOException("Unable to read from character set file (" + e + ")");
599 }
600 if (ttype != in.TT_NUMBER) {
601 // System.out.println("Bad token: type=" + ttype + " tok=" + in.sval);
602 throw new IOException("Unexpected token in character set file");
603 // continue;
604 }
605 values[i] = (char)(in.nval);
606 i++;
607 }
608
609 return values;
610 }
611
readCharset(java.net.URL href)612 static char[] readCharset(java.net.URL href)
613 throws IOException
614 {
615 return readCharset(href.openStream());
616 }
617
618 /** An interface (could be an entirely abstract class) describing
619 * a destination. The RTF reader always has a current destination
620 * which is where text is sent.
621 *
622 * @see RTFReader
623 */
624 interface Destination {
handleBinaryBlob(byte[] data)625 void handleBinaryBlob(byte[] data);
handleText(String text)626 void handleText(String text);
handleKeyword(String keyword)627 boolean handleKeyword(String keyword);
handleKeyword(String keyword, int parameter)628 boolean handleKeyword(String keyword, int parameter);
629
begingroup()630 void begingroup();
endgroup(Dictionary oldState)631 void endgroup(Dictionary oldState);
632
close()633 void close();
634 }
635
636 /** This data-sink class is used to implement ignored destinations
637 * (e.g. {\*\blegga blah blah blah} )
638 * It accepts all keywords and text but does nothing with them. */
639 class DiscardingDestination implements Destination
640 {
handleBinaryBlob(byte[] data)641 public void handleBinaryBlob(byte[] data)
642 {
643 /* Discard binary blobs. */
644 }
645
handleText(String text)646 public void handleText(String text)
647 {
648 /* Discard text. */
649 }
650
handleKeyword(String text)651 public boolean handleKeyword(String text)
652 {
653 /* Accept and discard keywords. */
654 return true;
655 }
656
handleKeyword(String text, int parameter)657 public boolean handleKeyword(String text, int parameter)
658 {
659 /* Accept and discard parameterized keywords. */
660 return true;
661 }
662
begingroup()663 public void begingroup()
664 {
665 /* Ignore groups --- the RTFReader will keep track of the
666 current group level as necessary */
667 }
668
endgroup(Dictionary oldState)669 public void endgroup(Dictionary oldState)
670 {
671 /* Ignore groups */
672 }
673
close()674 public void close()
675 {
676 /* No end-of-destination cleanup needed */
677 }
678 }
679
680 /** Reads the fonttbl group, inserting fonts into the RTFReader's
681 * fontTable dictionary. */
682 class FonttblDestination implements Destination
683 {
684 int nextFontNumber;
685 Integer fontNumberKey = null;
686 String nextFontFamily;
687
handleBinaryBlob(byte[] data)688 public void handleBinaryBlob(byte[] data)
689 { /* Discard binary blobs. */ }
690
handleText(String text)691 public void handleText(String text)
692 {
693 int semicolon = text.indexOf(';');
694 String fontName;
695
696 if (semicolon > -1)
697 fontName = text.substring(0, semicolon);
698 else
699 fontName = text;
700
701
702 /* TODO: do something with the font family. */
703
704 if (nextFontNumber == -1
705 && fontNumberKey != null) {
706 //font name might be broken across multiple calls
707 fontName = fontTable.get(fontNumberKey) + fontName;
708 } else {
709 fontNumberKey = Integer.valueOf(nextFontNumber);
710 }
711 fontTable.put(fontNumberKey, fontName);
712
713 nextFontNumber = -1;
714 nextFontFamily = null;
715 }
716
handleKeyword(String keyword)717 public boolean handleKeyword(String keyword)
718 {
719 if (keyword.charAt(0) == 'f') {
720 nextFontFamily = keyword.substring(1);
721 return true;
722 }
723
724 return false;
725 }
726
handleKeyword(String keyword, int parameter)727 public boolean handleKeyword(String keyword, int parameter)
728 {
729 if (keyword.equals("f")) {
730 nextFontNumber = parameter;
731 return true;
732 }
733
734 return false;
735 }
736
737 /* Groups are irrelevant. */
begingroup()738 public void begingroup() {}
endgroup(Dictionary oldState)739 public void endgroup(Dictionary oldState) {}
740
741 /* currently, the only thing we do when the font table ends is
742 dump its contents to the debugging log. */
close()743 public void close()
744 {
745 Enumeration<Integer> nums = fontTable.keys();
746 warning("Done reading font table.");
747 while(nums.hasMoreElements()) {
748 Integer num = nums.nextElement();
749 warning("Number " + num + ": " + fontTable.get(num));
750 }
751 }
752 }
753
754 /** Reads the colortbl group. Upon end-of-group, the RTFReader's
755 * color table is set to an array containing the read colors. */
756 class ColortblDestination implements Destination
757 {
758 int red, green, blue;
759 Vector<Color> proTemTable;
760
ColortblDestination()761 public ColortblDestination()
762 {
763 red = 0;
764 green = 0;
765 blue = 0;
766 proTemTable = new Vector<Color>();
767 }
768
handleText(String text)769 public void handleText(String text)
770 {
771 int index;
772
773 for (index = 0; index < text.length(); index ++) {
774 if (text.charAt(index) == ';') {
775 Color newColor;
776 newColor = new Color(red, green, blue);
777 proTemTable.addElement(newColor);
778 }
779 }
780 }
781
close()782 public void close()
783 {
784 int count = proTemTable.size();
785 warning("Done reading color table, " + count + " entries.");
786 colorTable = new Color[count];
787 proTemTable.copyInto(colorTable);
788 }
789
handleKeyword(String keyword, int parameter)790 public boolean handleKeyword(String keyword, int parameter)
791 {
792 if (keyword.equals("red"))
793 red = parameter;
794 else if (keyword.equals("green"))
795 green = parameter;
796 else if (keyword.equals("blue"))
797 blue = parameter;
798 else
799 return false;
800
801 return true;
802 }
803
804 /* Colortbls don't understand any parameterless keywords */
handleKeyword(String keyword)805 public boolean handleKeyword(String keyword) { return false; }
806
807 /* Groups are irrelevant. */
begingroup()808 public void begingroup() {}
endgroup(Dictionary oldState)809 public void endgroup(Dictionary oldState) {}
810
811 /* Shouldn't see any binary blobs ... */
handleBinaryBlob(byte[] data)812 public void handleBinaryBlob(byte[] data) {}
813 }
814
815 /** Handles the stylesheet keyword. Styles are read and sorted
816 * into the three style arrays in the RTFReader. */
817 class StylesheetDestination
818 extends DiscardingDestination
819 implements Destination
820 {
821 Dictionary<Integer, StyleDefiningDestination> definedStyles;
822
StylesheetDestination()823 public StylesheetDestination()
824 {
825 definedStyles = new Hashtable<Integer, StyleDefiningDestination>();
826 }
827
begingroup()828 public void begingroup()
829 {
830 setRTFDestination(new StyleDefiningDestination());
831 }
832
close()833 public void close()
834 {
835 Map<Integer, Style> chrStyles = new HashMap<>();
836 Map<Integer, Style> pgfStyles = new HashMap<>();
837 Map<Integer, Style> secStyles = new HashMap<>();
838 Enumeration<StyleDefiningDestination> styles = definedStyles.elements();
839 while(styles.hasMoreElements()) {
840 StyleDefiningDestination style;
841 Style defined;
842 style = styles.nextElement();
843 defined = style.realize();
844 warning("Style "+style.number+" ("+style.styleName+"): "+defined);
845 String stype = (String)defined.getAttribute(Constants.StyleType);
846 Map<Integer, Style> toMap;
847 if (stype.equals(Constants.STSection)) {
848 toMap = secStyles;
849 } else if (stype.equals(Constants.STCharacter)) {
850 toMap = chrStyles;
851 } else {
852 toMap = pgfStyles;
853 }
854 toMap.put(style.number, defined);
855 }
856 if (!(chrStyles.isEmpty())) {
857 characterStyles = chrStyles;
858 }
859 if (!(pgfStyles.isEmpty())) {
860 paragraphStyles = pgfStyles;
861 }
862 if (!(secStyles.isEmpty())) {
863 sectionStyles = secStyles;
864 }
865
866 /* (old debugging code)
867 int i, m;
868 if (characterStyles != null) {
869 m = characterStyles.length;
870 for(i=0;i<m;i++)
871 warnings.println("chrStyle["+i+"]="+characterStyles[i]);
872 } else warnings.println("No character styles.");
873 if (paragraphStyles != null) {
874 m = paragraphStyles.length;
875 for(i=0;i<m;i++)
876 warnings.println("pgfStyle["+i+"]="+paragraphStyles[i]);
877 } else warnings.println("No paragraph styles.");
878 if (sectionStyles != null) {
879 m = characterStyles.length;
880 for(i=0;i<m;i++)
881 warnings.println("secStyle["+i+"]="+sectionStyles[i]);
882 } else warnings.println("No section styles.");
883 */
884 }
885
886 /** This subclass handles an individual style */
887 class StyleDefiningDestination
888 extends AttributeTrackingDestination
889 implements Destination
890 {
891 final int STYLENUMBER_NONE = 222;
892 boolean additive;
893 boolean characterStyle;
894 boolean sectionStyle;
895 public String styleName;
896 public int number;
897 int basedOn;
898 int nextStyle;
899 boolean hidden;
900
901 Style realizedStyle;
902
StyleDefiningDestination()903 public StyleDefiningDestination()
904 {
905 additive = false;
906 characterStyle = false;
907 sectionStyle = false;
908 styleName = null;
909 number = 0;
910 basedOn = STYLENUMBER_NONE;
911 nextStyle = STYLENUMBER_NONE;
912 hidden = false;
913 }
914
handleText(String text)915 public void handleText(String text)
916 {
917 if (styleName != null)
918 styleName = styleName + text;
919 else
920 styleName = text;
921 }
922
close()923 public void close() {
924 int semicolon = (styleName == null) ? 0 : styleName.indexOf(';');
925 if (semicolon > 0)
926 styleName = styleName.substring(0, semicolon);
927 definedStyles.put(Integer.valueOf(number), this);
928 super.close();
929 }
930
handleKeyword(String keyword)931 public boolean handleKeyword(String keyword)
932 {
933 if (keyword.equals("additive")) {
934 additive = true;
935 return true;
936 }
937 if (keyword.equals("shidden")) {
938 hidden = true;
939 return true;
940 }
941 return super.handleKeyword(keyword);
942 }
943
handleKeyword(String keyword, int parameter)944 public boolean handleKeyword(String keyword, int parameter)
945 {
946 // As per http://www.biblioscape.com/rtf15_spec.htm#Heading2
947 // we are restricting control word delimiter numeric value
948 // to be within -32767 through 32767
949 if (parameter > 32767) {
950 parameter = 32767;
951 } else if (parameter < -32767) {
952 parameter = -32767;
953 }
954 if (keyword.equals("s")) {
955 characterStyle = false;
956 sectionStyle = false;
957 number = parameter;
958 } else if (keyword.equals("cs")) {
959 characterStyle = true;
960 sectionStyle = false;
961 number = parameter;
962 } else if (keyword.equals("ds")) {
963 characterStyle = false;
964 sectionStyle = true;
965 number = parameter;
966 } else if (keyword.equals("sbasedon")) {
967 basedOn = parameter;
968 } else if (keyword.equals("snext")) {
969 nextStyle = parameter;
970 } else {
971 return super.handleKeyword(keyword, parameter);
972 }
973 return true;
974 }
975
realize()976 public Style realize() {
977 return realize(null);
978 }
979
realize(Set<Integer> alreadyMetBasisIndexSet)980 private Style realize(Set<Integer> alreadyMetBasisIndexSet)
981 {
982 Style basis = null;
983 Style next = null;
984
985 if (alreadyMetBasisIndexSet == null) {
986 alreadyMetBasisIndexSet = new HashSet<>();
987 }
988
989 if (realizedStyle != null)
990 return realizedStyle;
991
992 if (basedOn != STYLENUMBER_NONE && alreadyMetBasisIndexSet.add(basedOn)) {
993 StyleDefiningDestination styleDest;
994 styleDest = definedStyles.get(basedOn);
995 if (styleDest != null && styleDest != this) {
996 basis = styleDest.realize(alreadyMetBasisIndexSet);
997 }
998 }
999
1000 /* NB: Swing StyleContext doesn't allow distinct styles with
1001 the same name; RTF apparently does. This may confuse the
1002 user. */
1003 realizedStyle = target.addStyle(styleName, basis);
1004
1005 if (characterStyle) {
1006 realizedStyle.addAttributes(currentTextAttributes());
1007 realizedStyle.addAttribute(Constants.StyleType,
1008 Constants.STCharacter);
1009 } else if (sectionStyle) {
1010 realizedStyle.addAttributes(currentSectionAttributes());
1011 realizedStyle.addAttribute(Constants.StyleType,
1012 Constants.STSection);
1013 } else { /* must be a paragraph style */
1014 realizedStyle.addAttributes(currentParagraphAttributes());
1015 realizedStyle.addAttribute(Constants.StyleType,
1016 Constants.STParagraph);
1017 }
1018
1019 if (nextStyle != STYLENUMBER_NONE) {
1020 StyleDefiningDestination styleDest;
1021 styleDest = definedStyles.get(Integer.valueOf(nextStyle));
1022 if (styleDest != null) {
1023 next = styleDest.realize();
1024 }
1025 }
1026
1027 if (next != null)
1028 realizedStyle.addAttribute(Constants.StyleNext, next);
1029 realizedStyle.addAttribute(Constants.StyleAdditive,
1030 Boolean.valueOf(additive));
1031 realizedStyle.addAttribute(Constants.StyleHidden,
1032 Boolean.valueOf(hidden));
1033
1034 return realizedStyle;
1035 }
1036 }
1037 }
1038
1039 /** Handles the info group. Currently no info keywords are recognized
1040 * so this is a subclass of DiscardingDestination. */
1041 class InfoDestination
1042 extends DiscardingDestination
1043 implements Destination
1044 {
1045 }
1046
1047 /** RTFReader.TextHandlingDestination is an abstract RTF destination
1048 * which simply tracks the attributes specified by the RTF control words
1049 * in internal form and can produce acceptable AttributeSets for the
1050 * current character, paragraph, and section attributes. It is up
1051 * to the subclasses to determine what is done with the actual text. */
1052 abstract class AttributeTrackingDestination implements Destination
1053 {
1054 /** This is the "chr" element of parserState, cached for
1055 * more efficient use */
1056 MutableAttributeSet characterAttributes;
1057 /** This is the "pgf" element of parserState, cached for
1058 * more efficient use */
1059 MutableAttributeSet paragraphAttributes;
1060 /** This is the "sec" element of parserState, cached for
1061 * more efficient use */
1062 MutableAttributeSet sectionAttributes;
1063
AttributeTrackingDestination()1064 public AttributeTrackingDestination()
1065 {
1066 characterAttributes = rootCharacterAttributes();
1067 parserState.put("chr", characterAttributes);
1068 paragraphAttributes = rootParagraphAttributes();
1069 parserState.put("pgf", paragraphAttributes);
1070 sectionAttributes = rootSectionAttributes();
1071 parserState.put("sec", sectionAttributes);
1072 }
1073
handleText(String text)1074 abstract public void handleText(String text);
1075
handleBinaryBlob(byte[] data)1076 public void handleBinaryBlob(byte[] data)
1077 {
1078 /* This should really be in TextHandlingDestination, but
1079 * since *nobody* does anything with binary blobs, this
1080 * is more convenient. */
1081 warning("Unexpected binary data in RTF file.");
1082 }
1083
begingroup()1084 public void begingroup()
1085 {
1086 AttributeSet characterParent = currentTextAttributes();
1087 AttributeSet paragraphParent = currentParagraphAttributes();
1088 AttributeSet sectionParent = currentSectionAttributes();
1089
1090 /* It would probably be more efficient to use the
1091 * resolver property of the attributes set for
1092 * implementing rtf groups,
1093 * but that's needed for styles. */
1094
1095 /* update the cached attribute dictionaries */
1096 characterAttributes = new SimpleAttributeSet();
1097 characterAttributes.addAttributes(characterParent);
1098 parserState.put("chr", characterAttributes);
1099
1100 paragraphAttributes = new SimpleAttributeSet();
1101 paragraphAttributes.addAttributes(paragraphParent);
1102 parserState.put("pgf", paragraphAttributes);
1103
1104 sectionAttributes = new SimpleAttributeSet();
1105 sectionAttributes.addAttributes(sectionParent);
1106 parserState.put("sec", sectionAttributes);
1107 }
1108
endgroup(Dictionary oldState)1109 public void endgroup(Dictionary oldState)
1110 {
1111 characterAttributes = (MutableAttributeSet)parserState.get("chr");
1112 paragraphAttributes = (MutableAttributeSet)parserState.get("pgf");
1113 sectionAttributes = (MutableAttributeSet)parserState.get("sec");
1114 }
1115
close()1116 public void close()
1117 {
1118 }
1119
handleKeyword(String keyword)1120 public boolean handleKeyword(String keyword)
1121 {
1122 if (keyword.equals("ulnone")) {
1123 return handleKeyword("ul", 0);
1124 }
1125
1126 {
1127 RTFAttribute attr = straightforwardAttributes.get(keyword);
1128 if (attr != null) {
1129 boolean ok;
1130
1131 switch(attr.domain()) {
1132 case RTFAttribute.D_CHARACTER:
1133 ok = attr.set(characterAttributes);
1134 break;
1135 case RTFAttribute.D_PARAGRAPH:
1136 ok = attr.set(paragraphAttributes);
1137 break;
1138 case RTFAttribute.D_SECTION:
1139 ok = attr.set(sectionAttributes);
1140 break;
1141 case RTFAttribute.D_META:
1142 mockery.backing = parserState;
1143 ok = attr.set(mockery);
1144 mockery.backing = null;
1145 break;
1146 case RTFAttribute.D_DOCUMENT:
1147 ok = attr.set(documentAttributes);
1148 break;
1149 default:
1150 /* should never happen */
1151 ok = false;
1152 break;
1153 }
1154 if (ok)
1155 return true;
1156 }
1157 }
1158
1159
1160 if (keyword.equals("plain")) {
1161 resetCharacterAttributes();
1162 return true;
1163 }
1164
1165 if (keyword.equals("pard")) {
1166 resetParagraphAttributes();
1167 return true;
1168 }
1169
1170 if (keyword.equals("sectd")) {
1171 resetSectionAttributes();
1172 return true;
1173 }
1174
1175 return false;
1176 }
1177
handleKeyword(String keyword, int parameter)1178 public boolean handleKeyword(String keyword, int parameter)
1179 {
1180 boolean booleanParameter = (parameter != 0);
1181
1182 if (keyword.equals("fc"))
1183 keyword = "cf"; /* whatEVER, dude. */
1184
1185 if (keyword.equals("f")) {
1186 parserState.put(keyword, Integer.valueOf(parameter));
1187 return true;
1188 }
1189 if (keyword.equals("cf")) {
1190 parserState.put(keyword, Integer.valueOf(parameter));
1191 return true;
1192 }
1193
1194 {
1195 RTFAttribute attr = straightforwardAttributes.get(keyword);
1196 if (attr != null) {
1197 boolean ok;
1198
1199 switch(attr.domain()) {
1200 case RTFAttribute.D_CHARACTER:
1201 ok = attr.set(characterAttributes, parameter);
1202 break;
1203 case RTFAttribute.D_PARAGRAPH:
1204 ok = attr.set(paragraphAttributes, parameter);
1205 break;
1206 case RTFAttribute.D_SECTION:
1207 ok = attr.set(sectionAttributes, parameter);
1208 break;
1209 case RTFAttribute.D_META:
1210 mockery.backing = parserState;
1211 ok = attr.set(mockery, parameter);
1212 mockery.backing = null;
1213 break;
1214 case RTFAttribute.D_DOCUMENT:
1215 ok = attr.set(documentAttributes, parameter);
1216 break;
1217 default:
1218 /* should never happen */
1219 ok = false;
1220 break;
1221 }
1222 if (ok)
1223 return true;
1224 }
1225 }
1226
1227 if (keyword.equals("fs")) {
1228 StyleConstants.setFontSize(characterAttributes, (parameter / 2));
1229 return true;
1230 }
1231
1232 /* TODO: superscript/subscript */
1233
1234 if (keyword.equals("sl")) {
1235 if (parameter == 1000) { /* magic value! */
1236 characterAttributes.removeAttribute(StyleConstants.LineSpacing);
1237 } else {
1238 /* TODO: The RTF sl attribute has special meaning if it's
1239 negative. Make sure that SwingText has the same special
1240 meaning, or find a way to imitate that. When SwingText
1241 handles this, also recognize the slmult keyword. */
1242 StyleConstants.setLineSpacing(characterAttributes,
1243 parameter / 20f);
1244 }
1245 return true;
1246 }
1247
1248 /* TODO: Other kinds of underlining */
1249
1250 if (keyword.equals("tx") || keyword.equals("tb")) {
1251 float tabPosition = parameter / 20f;
1252 int tabAlignment, tabLeader;
1253 Number item;
1254
1255 tabAlignment = TabStop.ALIGN_LEFT;
1256 item = (Number)(parserState.get("tab_alignment"));
1257 if (item != null)
1258 tabAlignment = item.intValue();
1259 tabLeader = TabStop.LEAD_NONE;
1260 item = (Number)(parserState.get("tab_leader"));
1261 if (item != null)
1262 tabLeader = item.intValue();
1263 if (keyword.equals("tb"))
1264 tabAlignment = TabStop.ALIGN_BAR;
1265
1266 parserState.remove("tab_alignment");
1267 parserState.remove("tab_leader");
1268
1269 TabStop newStop = new TabStop(tabPosition, tabAlignment, tabLeader);
1270 Dictionary<Object, Object> tabs;
1271 Integer stopCount;
1272
1273 tabs = (Dictionary<Object, Object>)parserState.get("_tabs");
1274 if (tabs == null) {
1275 tabs = new Hashtable<Object, Object>();
1276 parserState.put("_tabs", tabs);
1277 stopCount = Integer.valueOf(1);
1278 } else {
1279 stopCount = (Integer)tabs.get("stop count");
1280 stopCount = Integer.valueOf(1 + stopCount.intValue());
1281 }
1282 tabs.put(stopCount, newStop);
1283 tabs.put("stop count", stopCount);
1284 parserState.remove("_tabs_immutable");
1285
1286 return true;
1287 }
1288
1289 if (keyword.equals("s") &&
1290 paragraphStyles != null) {
1291 parserState.put("paragraphStyle", paragraphStyles.get(parameter));
1292 return true;
1293 }
1294
1295 if (keyword.equals("cs") &&
1296 characterStyles != null) {
1297 parserState.put("characterStyle", characterStyles.get(parameter));
1298 return true;
1299 }
1300
1301 if (keyword.equals("ds") &&
1302 sectionStyles != null) {
1303 parserState.put("sectionStyle", sectionStyles.get(parameter));
1304 return true;
1305 }
1306
1307 return false;
1308 }
1309
1310 /** Returns a new MutableAttributeSet containing the
1311 * default character attributes */
rootCharacterAttributes()1312 protected MutableAttributeSet rootCharacterAttributes()
1313 {
1314 MutableAttributeSet set = new SimpleAttributeSet();
1315
1316 /* TODO: default font */
1317
1318 StyleConstants.setItalic(set, false);
1319 StyleConstants.setBold(set, false);
1320 StyleConstants.setUnderline(set, false);
1321 StyleConstants.setForeground(set, defaultColor());
1322
1323 return set;
1324 }
1325
1326 /** Returns a new MutableAttributeSet containing the
1327 * default paragraph attributes */
rootParagraphAttributes()1328 protected MutableAttributeSet rootParagraphAttributes()
1329 {
1330 MutableAttributeSet set = new SimpleAttributeSet();
1331
1332 StyleConstants.setLeftIndent(set, 0f);
1333 StyleConstants.setRightIndent(set, 0f);
1334 StyleConstants.setFirstLineIndent(set, 0f);
1335
1336 /* TODO: what should this be, really? */
1337 set.setResolveParent(target.getStyle(StyleContext.DEFAULT_STYLE));
1338
1339 return set;
1340 }
1341
1342 /** Returns a new MutableAttributeSet containing the
1343 * default section attributes */
rootSectionAttributes()1344 protected MutableAttributeSet rootSectionAttributes()
1345 {
1346 MutableAttributeSet set = new SimpleAttributeSet();
1347
1348 return set;
1349 }
1350
1351 /**
1352 * Calculates the current text (character) attributes in a form suitable
1353 * for SwingText from the current parser state.
1354 *
1355 * @returns a new MutableAttributeSet containing the text attributes.
1356 */
currentTextAttributes()1357 MutableAttributeSet currentTextAttributes()
1358 {
1359 MutableAttributeSet attributes =
1360 new SimpleAttributeSet(characterAttributes);
1361 Integer fontnum;
1362 Integer stateItem;
1363
1364 /* figure out the font name */
1365 /* TODO: catch exceptions for undefined attributes,
1366 bad font indices, etc.? (as it stands, it is the caller's
1367 job to clean up after corrupt RTF) */
1368 fontnum = (Integer)parserState.get("f");
1369 /* note setFontFamily() can not handle a null font */
1370 String fontFamily;
1371 if (fontnum != null)
1372 fontFamily = fontTable.get(fontnum);
1373 else
1374 fontFamily = null;
1375 if (fontFamily != null)
1376 StyleConstants.setFontFamily(attributes, fontFamily);
1377 else
1378 attributes.removeAttribute(StyleConstants.FontFamily);
1379
1380 if (colorTable != null) {
1381 stateItem = (Integer)parserState.get("cf");
1382 if (stateItem != null) {
1383 Color fg = colorTable[stateItem.intValue()];
1384 StyleConstants.setForeground(attributes, fg);
1385 } else {
1386 /* AttributeSet dies if you set a value to null */
1387 attributes.removeAttribute(StyleConstants.Foreground);
1388 }
1389 }
1390
1391 if (colorTable != null) {
1392 stateItem = (Integer)parserState.get("cb");
1393 if (stateItem != null) {
1394 Color bg = colorTable[stateItem.intValue()];
1395 attributes.addAttribute(StyleConstants.Background,
1396 bg);
1397 } else {
1398 /* AttributeSet dies if you set a value to null */
1399 attributes.removeAttribute(StyleConstants.Background);
1400 }
1401 }
1402
1403 Style characterStyle = (Style)parserState.get("characterStyle");
1404 if (characterStyle != null)
1405 attributes.setResolveParent(characterStyle);
1406
1407 /* Other attributes are maintained directly in "attributes" */
1408
1409 return attributes;
1410 }
1411
1412 /**
1413 * Calculates the current paragraph attributes (with keys
1414 * as given in StyleConstants) from the current parser state.
1415 *
1416 * @returns a newly created MutableAttributeSet.
1417 * @see StyleConstants
1418 */
currentParagraphAttributes()1419 MutableAttributeSet currentParagraphAttributes()
1420 {
1421 /* NB if there were a mutableCopy() method we should use it */
1422 MutableAttributeSet bld = new SimpleAttributeSet(paragraphAttributes);
1423
1424 Integer stateItem;
1425
1426 /*** Tab stops ***/
1427 TabStop tabs[];
1428
1429 tabs = (TabStop[])parserState.get("_tabs_immutable");
1430 if (tabs == null) {
1431 Dictionary workingTabs = (Dictionary)parserState.get("_tabs");
1432 if (workingTabs != null) {
1433 int count = ((Integer)workingTabs.get("stop count")).intValue();
1434 tabs = new TabStop[count];
1435 for (int ix = 1; ix <= count; ix ++)
1436 tabs[ix-1] = (TabStop)workingTabs.get(Integer.valueOf(ix));
1437 parserState.put("_tabs_immutable", tabs);
1438 }
1439 }
1440 if (tabs != null)
1441 bld.addAttribute(Constants.Tabs, tabs);
1442
1443 Style paragraphStyle = (Style)parserState.get("paragraphStyle");
1444 if (paragraphStyle != null)
1445 bld.setResolveParent(paragraphStyle);
1446
1447 return bld;
1448 }
1449
1450 /**
1451 * Calculates the current section attributes
1452 * from the current parser state.
1453 *
1454 * @returns a newly created MutableAttributeSet.
1455 */
currentSectionAttributes()1456 public AttributeSet currentSectionAttributes()
1457 {
1458 MutableAttributeSet attributes = new SimpleAttributeSet(sectionAttributes);
1459
1460 Style sectionStyle = (Style)parserState.get("sectionStyle");
1461 if (sectionStyle != null)
1462 attributes.setResolveParent(sectionStyle);
1463
1464 return attributes;
1465 }
1466
1467 /** Resets the filter's internal notion of the current character
1468 * attributes to their default values. Invoked to handle the
1469 * \plain keyword. */
resetCharacterAttributes()1470 protected void resetCharacterAttributes()
1471 {
1472 handleKeyword("f", 0);
1473 handleKeyword("cf", 0);
1474
1475 handleKeyword("fs", 24); /* 12 pt. */
1476
1477 Enumeration<RTFAttribute> attributes = straightforwardAttributes.elements();
1478 while(attributes.hasMoreElements()) {
1479 RTFAttribute attr = attributes.nextElement();
1480 if (attr.domain() == RTFAttribute.D_CHARACTER)
1481 attr.setDefault(characterAttributes);
1482 }
1483
1484 handleKeyword("sl", 1000);
1485
1486 parserState.remove("characterStyle");
1487 }
1488
1489 /** Resets the filter's internal notion of the current paragraph's
1490 * attributes to their default values. Invoked to handle the
1491 * \pard keyword. */
resetParagraphAttributes()1492 protected void resetParagraphAttributes()
1493 {
1494 parserState.remove("_tabs");
1495 parserState.remove("_tabs_immutable");
1496 parserState.remove("paragraphStyle");
1497
1498 StyleConstants.setAlignment(paragraphAttributes,
1499 StyleConstants.ALIGN_LEFT);
1500
1501 Enumeration<RTFAttribute> attributes = straightforwardAttributes.elements();
1502 while(attributes.hasMoreElements()) {
1503 RTFAttribute attr = attributes.nextElement();
1504 if (attr.domain() == RTFAttribute.D_PARAGRAPH)
1505 attr.setDefault(characterAttributes);
1506 }
1507 }
1508
1509 /** Resets the filter's internal notion of the current section's
1510 * attributes to their default values. Invoked to handle the
1511 * \sectd keyword. */
resetSectionAttributes()1512 protected void resetSectionAttributes()
1513 {
1514 Enumeration<RTFAttribute> attributes = straightforwardAttributes.elements();
1515 while(attributes.hasMoreElements()) {
1516 RTFAttribute attr = attributes.nextElement();
1517 if (attr.domain() == RTFAttribute.D_SECTION)
1518 attr.setDefault(characterAttributes);
1519 }
1520
1521 parserState.remove("sectionStyle");
1522 }
1523 }
1524
1525 /** RTFReader.TextHandlingDestination provides basic text handling
1526 * functionality. Subclasses must implement: <dl>
1527 * <dt>deliverText()<dd>to handle a run of text with the same
1528 * attributes
1529 * <dt>finishParagraph()<dd>to end the current paragraph and
1530 * set the paragraph's attributes
1531 * <dt>endSection()<dd>to end the current section
1532 * </dl>
1533 */
1534 abstract class TextHandlingDestination
1535 extends AttributeTrackingDestination
1536 implements Destination
1537 {
1538 /** <code>true</code> if the reader has not just finished
1539 * a paragraph; false upon startup */
1540 boolean inParagraph;
1541
TextHandlingDestination()1542 public TextHandlingDestination()
1543 {
1544 super();
1545 inParagraph = false;
1546 }
1547
handleText(String text)1548 public void handleText(String text)
1549 {
1550 if (! inParagraph)
1551 beginParagraph();
1552
1553 deliverText(text, currentTextAttributes());
1554 }
1555
deliverText(String text, AttributeSet characterAttributes)1556 abstract void deliverText(String text, AttributeSet characterAttributes);
1557
close()1558 public void close()
1559 {
1560 if (inParagraph)
1561 endParagraph();
1562
1563 super.close();
1564 }
1565
handleKeyword(String keyword)1566 public boolean handleKeyword(String keyword)
1567 {
1568 if (keyword.equals("\r") || keyword.equals("\n")) {
1569 keyword = "par";
1570 }
1571
1572 if (keyword.equals("par")) {
1573 // warnings.println("Ending paragraph.");
1574 endParagraph();
1575 return true;
1576 }
1577
1578 if (keyword.equals("sect")) {
1579 // warnings.println("Ending section.");
1580 endSection();
1581 return true;
1582 }
1583
1584 return super.handleKeyword(keyword);
1585 }
1586
beginParagraph()1587 protected void beginParagraph()
1588 {
1589 inParagraph = true;
1590 }
1591
endParagraph()1592 protected void endParagraph()
1593 {
1594 AttributeSet pgfAttributes = currentParagraphAttributes();
1595 AttributeSet chrAttributes = currentTextAttributes();
1596 finishParagraph(pgfAttributes, chrAttributes);
1597 inParagraph = false;
1598 }
1599
finishParagraph(AttributeSet pgfA, AttributeSet chrA)1600 abstract void finishParagraph(AttributeSet pgfA, AttributeSet chrA);
1601
endSection()1602 abstract void endSection();
1603 }
1604
1605 /** RTFReader.DocumentDestination is a concrete subclass of
1606 * TextHandlingDestination which appends the text to the
1607 * StyledDocument given by the <code>target</code> ivar of the
1608 * containing RTFReader.
1609 */
1610 class DocumentDestination
1611 extends TextHandlingDestination
1612 implements Destination
1613 {
deliverText(String text, AttributeSet characterAttributes)1614 public void deliverText(String text, AttributeSet characterAttributes)
1615 {
1616 try {
1617 target.insertString(target.getLength(),
1618 text,
1619 currentTextAttributes());
1620 } catch (BadLocationException ble) {
1621 /* This shouldn't be able to happen, of course */
1622 /* TODO is InternalError the correct error to throw? */
1623 throw new InternalError(ble.getMessage(), ble);
1624 }
1625 }
1626
finishParagraph(AttributeSet pgfAttributes, AttributeSet chrAttributes)1627 public void finishParagraph(AttributeSet pgfAttributes,
1628 AttributeSet chrAttributes)
1629 {
1630 int pgfEndPosition = target.getLength();
1631 try {
1632 target.insertString(pgfEndPosition, "\n", chrAttributes);
1633 target.setParagraphAttributes(pgfEndPosition, 1, pgfAttributes, true);
1634 } catch (BadLocationException ble) {
1635 /* This shouldn't be able to happen, of course */
1636 /* TODO is InternalError the correct error to throw? */
1637 throw new InternalError(ble.getMessage(), ble);
1638 }
1639 }
1640
endSection()1641 public void endSection()
1642 {
1643 /* If we implemented sections, we'd end 'em here */
1644 }
1645 }
1646
1647 }
1648