1 /*
2 * Copyright 1997-2004 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any 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.Font;
31 import java.awt.Color;
32
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 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 fontTable;
67 /** This array maps color indices to Color objects. */
68 Color[] colorTable;
69 /** This array maps character style numbers to Style objects. */
70 Style[] characterStyles;
71 /** This array maps paragraph style numbers to Style objects. */
72 Style[] paragraphStyles;
73 /** This array maps section style numbers to Style objects. */
74 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 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 textKeywords = null;
100 static {
101 textKeywords = new Hashtable();
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 characterSets;
133 static boolean useNeXTForAnsi = false;
134 static {
135 characterSets = new Hashtable();
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 */
146 public RTFReader(StyledDocument destination)
147 {
148 int i;
149
150 target = destination;
151 parserState = new Hashtable();
152 fontTable = new Hashtable();
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 */
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. */
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. */
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 */
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 saveState = (Dictionary)((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 */
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 restoredState = (Dictionary)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
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 */
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((String)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 */
302 public boolean handleKeyword(String keyword)
303 {
304 Object 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((String)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 */
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
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 */
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 + ")");
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
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
556 getCharacterSet(final String name)
557 throws IOException
558 {
559 char[] set;
560
561 set = (char [])characterSets.get(name);
562 if (set == null) {
563 InputStream charsetStream;
564 charsetStream = (InputStream)java.security.AccessController.
565 doPrivileged(new java.security.PrivilegedAction() {
566 public Object run() {
567 return RTFReader.class.getResourceAsStream
568 ("charsets/" + name + ".txt");
569 }
570 });
571 set = readCharset(charsetStream);
572 defineCharacterSet(name, set);
573 }
574 return set;
575 }
576
577 /** Parses a character set from an InputStream. The character set
578 * must contain 256 decimal integers, separated by whitespace, with
579 * no punctuation. B- and C- style comments are allowed.
580 *
581 * @returns the newly read character set
582 */
583 static char[] readCharset(InputStream strm)
584 throws IOException
585 {
586 char[] values = new char[256];
587 int i;
588 StreamTokenizer in = new StreamTokenizer(new BufferedReader(
589 new InputStreamReader(strm, "ISO-8859-1")));
590
591 in.eolIsSignificant(false);
592 in.commentChar('#');
593 in.slashSlashComments(true);
594 in.slashStarComments(true);
595
596 i = 0;
597 while (i < 256) {
598 int ttype;
599 try {
600 ttype = in.nextToken();
601 } catch (Exception e) {
602 throw new IOException("Unable to read from character set file (" + e + ")");
603 }
604 if (ttype != in.TT_NUMBER) {
605 // System.out.println("Bad token: type=" + ttype + " tok=" + in.sval);
606 throw new IOException("Unexpected token in character set file");
607 // continue;
608 }
609 values[i] = (char)(in.nval);
610 i++;
611 }
612
613 return values;
614 }
615
616 static char[] readCharset(java.net.URL href)
617 throws IOException
618 {
619 return readCharset(href.openStream());
620 }
621
622 /** An interface (could be an entirely abstract class) describing
623 * a destination. The RTF reader always has a current destination
624 * which is where text is sent.
625 *
626 * @see RTFReader
627 */
628 interface Destination {
629 void handleBinaryBlob(byte[] data);
630 void handleText(String text);
631 boolean handleKeyword(String keyword);
632 boolean handleKeyword(String keyword, int parameter);
633
634 void begingroup();
635 void endgroup(Dictionary oldState);
636
637 void close();
638 }
639
640 /** This data-sink class is used to implement ignored destinations
641 * (e.g. {\*\blegga blah blah blah} )
642 * It accepts all keywords and text but does nothing with them. */
643 class DiscardingDestination implements Destination
644 {
645 public void handleBinaryBlob(byte[] data)
646 {
647 /* Discard binary blobs. */
648 }
649
650 public void handleText(String text)
651 {
652 /* Discard text. */
653 }
654
655 public boolean handleKeyword(String text)
656 {
657 /* Accept and discard keywords. */
658 return true;
659 }
660
661 public boolean handleKeyword(String text, int parameter)
662 {
663 /* Accept and discard parameterized keywords. */
664 return true;
665 }
666
667 public void begingroup()
668 {
669 /* Ignore groups --- the RTFReader will keep track of the
670 current group level as necessary */
671 }
672
673 public void endgroup(Dictionary oldState)
674 {
675 /* Ignore groups */
676 }
677
678 public void close()
679 {
680 /* No end-of-destination cleanup needed */
681 }
682 }
683
684 /** Reads the fonttbl group, inserting fonts into the RTFReader's
685 * fontTable dictionary. */
686 class FonttblDestination implements Destination
687 {
688 int nextFontNumber;
689 Object fontNumberKey = null;
690 String nextFontFamily;
691
692 public void handleBinaryBlob(byte[] data)
693 { /* Discard binary blobs. */ }
694
695 public void handleText(String text)
696 {
697 int semicolon = text.indexOf(';');
698 String fontName;
699
700 if (semicolon > -1)
701 fontName = text.substring(0, semicolon);
702 else
703 fontName = text;
704
705
706 /* TODO: do something with the font family. */
707
708 if (nextFontNumber == -1
709 && fontNumberKey != null) {
710 //font name might be broken across multiple calls
711 fontName = fontTable.get(fontNumberKey) + fontName;
712 } else {
713 fontNumberKey = Integer.valueOf(nextFontNumber);
714 }
715 fontTable.put(fontNumberKey, fontName);
716
717 nextFontNumber = -1;
718 nextFontFamily = null;
719 return;
720 }
721
722 public boolean handleKeyword(String keyword)
723 {
724 if (keyword.charAt(0) == 'f') {
725 nextFontFamily = keyword.substring(1);
726 return true;
727 }
728
729 return false;
730 }
731
732 public boolean handleKeyword(String keyword, int parameter)
733 {
734 if (keyword.equals("f")) {
735 nextFontNumber = parameter;
736 return true;
737 }
738
739 return false;
740 }
741
742 /* Groups are irrelevant. */
743 public void begingroup() {}
744 public void endgroup(Dictionary oldState) {}
745
746 /* currently, the only thing we do when the font table ends is
747 dump its contents to the debugging log. */
748 public void close()
749 {
750 Enumeration nums = fontTable.keys();
751 warning("Done reading font table.");
752 while(nums.hasMoreElements()) {
753 Integer num = (Integer)nums.nextElement();
754 warning("Number " + num + ": " + fontTable.get(num));
755 }
756 }
757 }
758
759 /** Reads the colortbl group. Upon end-of-group, the RTFReader's
760 * color table is set to an array containing the read colors. */
761 class ColortblDestination implements Destination
762 {
763 int red, green, blue;
764 Vector proTemTable;
765
766 public ColortblDestination()
767 {
768 red = 0;
769 green = 0;
770 blue = 0;
771 proTemTable = new Vector();
772 }
773
774 public void handleText(String text)
775 {
776 int index = 0;
777
778 for (index = 0; index < text.length(); index ++) {
779 if (text.charAt(index) == ';') {
780 Color newColor;
781 newColor = new Color(red, green, blue);
782 proTemTable.addElement(newColor);
783 }
784 }
785 }
786
787 public void close()
788 {
789 int count = proTemTable.size();
790 warning("Done reading color table, " + count + " entries.");
791 colorTable = new Color[count];
792 proTemTable.copyInto(colorTable);
793 }
794
795 public boolean handleKeyword(String keyword, int parameter)
796 {
797 if (keyword.equals("red"))
798 red = parameter;
799 else if (keyword.equals("green"))
800 green = parameter;
801 else if (keyword.equals("blue"))
802 blue = parameter;
803 else
804 return false;
805
806 return true;
807 }
808
809 /* Colortbls don't understand any parameterless keywords */
810 public boolean handleKeyword(String keyword) { return false; }
811
812 /* Groups are irrelevant. */
813 public void begingroup() {}
814 public void endgroup(Dictionary oldState) {}
815
816 /* Shouldn't see any binary blobs ... */
817 public void handleBinaryBlob(byte[] data) {}
818 }
819
820 /** Handles the stylesheet keyword. Styles are read and sorted
821 * into the three style arrays in the RTFReader. */
822 class StylesheetDestination
823 extends DiscardingDestination
824 implements Destination
825 {
826 Dictionary definedStyles;
827
828 public StylesheetDestination()
829 {
830 definedStyles = new Hashtable();
831 }
832
833 public void begingroup()
834 {
835 setRTFDestination(new StyleDefiningDestination());
836 }
837
838 public void close()
839 {
840 Vector chrStyles, pgfStyles, secStyles;
841 chrStyles = new Vector();
842 pgfStyles = new Vector();
843 secStyles = new Vector();
844 Enumeration styles = definedStyles.elements();
845 while(styles.hasMoreElements()) {
846 StyleDefiningDestination style;
847 Style defined;
848 style = (StyleDefiningDestination)styles.nextElement();
849 defined = style.realize();
850 warning("Style "+style.number+" ("+style.styleName+"): "+defined);
851 String stype = (String)defined.getAttribute(Constants.StyleType);
852 Vector toSet;
853 if (stype.equals(Constants.STSection)) {
854 toSet = secStyles;
855 } else if (stype.equals(Constants.STCharacter)) {
856 toSet = chrStyles;
857 } else {
858 toSet = pgfStyles;
859 }
860 if (toSet.size() <= style.number)
861 toSet.setSize(style.number + 1);
862 toSet.setElementAt(defined, style.number);
863 }
864 if (!(chrStyles.isEmpty())) {
865 Style[] styleArray = new Style[chrStyles.size()];
866 chrStyles.copyInto(styleArray);
867 characterStyles = styleArray;
868 }
869 if (!(pgfStyles.isEmpty())) {
870 Style[] styleArray = new Style[pgfStyles.size()];
871 pgfStyles.copyInto(styleArray);
872 paragraphStyles = styleArray;
873 }
874 if (!(secStyles.isEmpty())) {
875 Style[] styleArray = new Style[secStyles.size()];
876 secStyles.copyInto(styleArray);
877 sectionStyles = styleArray;
878 }
879
880 /* (old debugging code)
881 int i, m;
882 if (characterStyles != null) {
883 m = characterStyles.length;
884 for(i=0;i<m;i++)
885 warnings.println("chrStyle["+i+"]="+characterStyles[i]);
886 } else warnings.println("No character styles.");
887 if (paragraphStyles != null) {
888 m = paragraphStyles.length;
889 for(i=0;i<m;i++)
890 warnings.println("pgfStyle["+i+"]="+paragraphStyles[i]);
891 } else warnings.println("No paragraph styles.");
892 if (sectionStyles != null) {
893 m = characterStyles.length;
894 for(i=0;i<m;i++)
895 warnings.println("secStyle["+i+"]="+sectionStyles[i]);
896 } else warnings.println("No section styles.");
897 */
898 }
899
900 /** This subclass handles an individual style */
901 class StyleDefiningDestination
902 extends AttributeTrackingDestination
903 implements Destination
904 {
905 final int STYLENUMBER_NONE = 222;
906 boolean additive;
907 boolean characterStyle;
908 boolean sectionStyle;
909 public String styleName;
910 public int number;
911 int basedOn;
912 int nextStyle;
913 boolean hidden;
914
915 Style realizedStyle;
916
917 public StyleDefiningDestination()
918 {
919 additive = false;
920 characterStyle = false;
921 sectionStyle = false;
922 styleName = null;
923 number = 0;
924 basedOn = STYLENUMBER_NONE;
925 nextStyle = STYLENUMBER_NONE;
926 hidden = false;
927 }
928
929 public void handleText(String text)
930 {
931 if (styleName != null)
932 styleName = styleName + text;
933 else
934 styleName = text;
935 }
936
937 public void close() {
938 int semicolon = (styleName == null) ? 0 : styleName.indexOf(';');
939 if (semicolon > 0)
940 styleName = styleName.substring(0, semicolon);
941 definedStyles.put(Integer.valueOf(number), this);
942 super.close();
943 }
944
945 public boolean handleKeyword(String keyword)
946 {
947 if (keyword.equals("additive")) {
948 additive = true;
949 return true;
950 }
951 if (keyword.equals("shidden")) {
952 hidden = true;
953 return true;
954 }
955 return super.handleKeyword(keyword);
956 }
957
958 public boolean handleKeyword(String keyword, int parameter)
959 {
960 if (keyword.equals("s")) {
961 characterStyle = false;
962 sectionStyle = false;
963 number = parameter;
964 } else if (keyword.equals("cs")) {
965 characterStyle = true;
966 sectionStyle = false;
967 number = parameter;
968 } else if (keyword.equals("ds")) {
969 characterStyle = false;
970 sectionStyle = true;
971 number = parameter;
972 } else if (keyword.equals("sbasedon")) {
973 basedOn = parameter;
974 } else if (keyword.equals("snext")) {
975 nextStyle = parameter;
976 } else {
977 return super.handleKeyword(keyword, parameter);
978 }
979 return true;
980 }
981
982 public Style realize()
983 {
984 Style basis = null;
985 Style next = null;
986
987 if (realizedStyle != null)
988 return realizedStyle;
989
990 if (basedOn != STYLENUMBER_NONE) {
991 StyleDefiningDestination styleDest;
992 styleDest = (StyleDefiningDestination)definedStyles.get(Integer.valueOf(basedOn));
993 if (styleDest != null && styleDest != this) {
994 basis = styleDest.realize();
995 }
996 }
997
998 /* NB: Swing StyleContext doesn't allow distinct styles with
999 the same name; RTF apparently does. This may confuse the
1000 user. */
1001 realizedStyle = target.addStyle(styleName, basis);
1002
1003 if (characterStyle) {
1004 realizedStyle.addAttributes(currentTextAttributes());
1005 realizedStyle.addAttribute(Constants.StyleType,
1006 Constants.STCharacter);
1007 } else if (sectionStyle) {
1008 realizedStyle.addAttributes(currentSectionAttributes());
1009 realizedStyle.addAttribute(Constants.StyleType,
1010 Constants.STSection);
1011 } else { /* must be a paragraph style */
1012 realizedStyle.addAttributes(currentParagraphAttributes());
1013 realizedStyle.addAttribute(Constants.StyleType,
1014 Constants.STParagraph);
1015 }
1016
1017 if (nextStyle != STYLENUMBER_NONE) {
1018 StyleDefiningDestination styleDest;
1019 styleDest = (StyleDefiningDestination)definedStyles.get(Integer.valueOf(nextStyle));
1020 if (styleDest != null) {
1021 next = styleDest.realize();
1022 }
1023 }
1024
1025 if (next != null)
1026 realizedStyle.addAttribute(Constants.StyleNext, next);
1027 realizedStyle.addAttribute(Constants.StyleAdditive,
1028 Boolean.valueOf(additive));
1029 realizedStyle.addAttribute(Constants.StyleHidden,
1030 Boolean.valueOf(hidden));
1031
1032 return realizedStyle;
1033 }
1034 }
1035 }
1036
1037 /** Handles the info group. Currently no info keywords are recognized
1038 * so this is a subclass of DiscardingDestination. */
1039 class InfoDestination
1040 extends DiscardingDestination
1041 implements Destination
1042 {
1043 }
1044
1045 /** RTFReader.TextHandlingDestination is an abstract RTF destination
1046 * which simply tracks the attributes specified by the RTF control words
1047 * in internal form and can produce acceptable AttributeSets for the
1048 * current character, paragraph, and section attributes. It is up
1049 * to the subclasses to determine what is done with the actual text. */
1050 abstract class AttributeTrackingDestination implements Destination
1051 {
1052 /** This is the "chr" element of parserState, cached for
1053 * more efficient use */
1054 MutableAttributeSet characterAttributes;
1055 /** This is the "pgf" element of parserState, cached for
1056 * more efficient use */
1057 MutableAttributeSet paragraphAttributes;
1058 /** This is the "sec" element of parserState, cached for
1059 * more efficient use */
1060 MutableAttributeSet sectionAttributes;
1061
1062 public AttributeTrackingDestination()
1063 {
1064 characterAttributes = rootCharacterAttributes();
1065 parserState.put("chr", characterAttributes);
1066 paragraphAttributes = rootParagraphAttributes();
1067 parserState.put("pgf", paragraphAttributes);
1068 sectionAttributes = rootSectionAttributes();
1069 parserState.put("sec", sectionAttributes);
1070 }
1071
1072 abstract public void handleText(String text);
1073
1074 public void handleBinaryBlob(byte[] data)
1075 {
1076 /* This should really be in TextHandlingDestination, but
1077 * since *nobody* does anything with binary blobs, this
1078 * is more convenient. */
1079 warning("Unexpected binary data in RTF file.");
1080 }
1081
1082 public void begingroup()
1083 {
1084 AttributeSet characterParent = currentTextAttributes();
1085 AttributeSet paragraphParent = currentParagraphAttributes();
1086 AttributeSet sectionParent = currentSectionAttributes();
1087
1088 /* It would probably be more efficient to use the
1089 * resolver property of the attributes set for
1090 * implementing rtf groups,
1091 * but that's needed for styles. */
1092
1093 /* update the cached attribute dictionaries */
1094 characterAttributes = new SimpleAttributeSet();
1095 characterAttributes.addAttributes(characterParent);
1096 parserState.put("chr", characterAttributes);
1097
1098 paragraphAttributes = new SimpleAttributeSet();
1099 paragraphAttributes.addAttributes(paragraphParent);
1100 parserState.put("pgf", paragraphAttributes);
1101
1102 sectionAttributes = new SimpleAttributeSet();
1103 sectionAttributes.addAttributes(sectionParent);
1104 parserState.put("sec", sectionAttributes);
1105 }
1106
1107 public void endgroup(Dictionary oldState)
1108 {
1109 characterAttributes = (MutableAttributeSet)parserState.get("chr");
1110 paragraphAttributes = (MutableAttributeSet)parserState.get("pgf");
1111 sectionAttributes = (MutableAttributeSet)parserState.get("sec");
1112 }
1113
1114 public void close()
1115 {
1116 }
1117
1118 public boolean handleKeyword(String keyword)
1119 {
1120 if (keyword.equals("ulnone")) {
1121 return handleKeyword("ul", 0);
1122 }
1123
1124 {
1125 Object item = straightforwardAttributes.get(keyword);
1126 if (item != null) {
1127 RTFAttribute attr = (RTFAttribute)item;
1128 boolean ok;
1129
1130 switch(attr.domain()) {
1131 case RTFAttribute.D_CHARACTER:
1132 ok = attr.set(characterAttributes);
1133 break;
1134 case RTFAttribute.D_PARAGRAPH:
1135 ok = attr.set(paragraphAttributes);
1136 break;
1137 case RTFAttribute.D_SECTION:
1138 ok = attr.set(sectionAttributes);
1139 break;
1140 case RTFAttribute.D_META:
1141 mockery.backing = parserState;
1142 ok = attr.set(mockery);
1143 mockery.backing = null;
1144 break;
1145 case RTFAttribute.D_DOCUMENT:
1146 ok = attr.set(documentAttributes);
1147 break;
1148 default:
1149 /* should never happen */
1150 ok = false;
1151 break;
1152 }
1153 if (ok)
1154 return true;
1155 }
1156 }
1157
1158
1159 if (keyword.equals("plain")) {
1160 resetCharacterAttributes();
1161 return true;
1162 }
1163
1164 if (keyword.equals("pard")) {
1165 resetParagraphAttributes();
1166 return true;
1167 }
1168
1169 if (keyword.equals("sectd")) {
1170 resetSectionAttributes();
1171 return true;
1172 }
1173
1174 return false;
1175 }
1176
1177 public boolean handleKeyword(String keyword, int parameter)
1178 {
1179 boolean booleanParameter = (parameter != 0);
1180
1181 if (keyword.equals("fc"))
1182 keyword = "cf"; /* whatEVER, dude. */
1183
1184 if (keyword.equals("f")) {
1185 parserState.put(keyword, Integer.valueOf(parameter));
1186 return true;
1187 }
1188 if (keyword.equals("cf")) {
1189 parserState.put(keyword, Integer.valueOf(parameter));
1190 return true;
1191 }
1192
1193 {
1194 Object item = straightforwardAttributes.get(keyword);
1195 if (item != null) {
1196 RTFAttribute attr = (RTFAttribute)item;
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 tabs;
1271 Integer stopCount;
1272
1273 tabs = (Dictionary)parserState.get("_tabs");
1274 if (tabs == null) {
1275 tabs = new Hashtable();
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[parameter]);
1292 return true;
1293 }
1294
1295 if (keyword.equals("cs") &&
1296 characterStyles != null) {
1297 parserState.put("characterStyle", characterStyles[parameter]);
1298 return true;
1299 }
1300
1301 if (keyword.equals("ds") &&
1302 sectionStyles != null) {
1303 parserState.put("sectionStyle", sectionStyles[parameter]);
1304 return true;
1305 }
1306
1307 return false;
1308 }
1309
1310 /** Returns a new MutableAttributeSet containing the
1311 * default character attributes */
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 */
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 */
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 */
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 = (String)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 */
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 */
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. */
1470 protected void resetCharacterAttributes()
1471 {
1472 handleKeyword("f", 0);
1473 handleKeyword("cf", 0);
1474
1475 handleKeyword("fs", 24); /* 12 pt. */
1476
1477 Enumeration attributes = straightforwardAttributes.elements();
1478 while(attributes.hasMoreElements()) {
1479 RTFAttribute attr = (RTFAttribute)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. */
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 attributes = straightforwardAttributes.elements();
1502 while(attributes.hasMoreElements()) {
1503 RTFAttribute attr = (RTFAttribute)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. */
1512 protected void resetSectionAttributes()
1513 {
1514 Enumeration attributes = straightforwardAttributes.elements();
1515 while(attributes.hasMoreElements()) {
1516 RTFAttribute attr = (RTFAttribute)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
1542 public TextHandlingDestination()
1543 {
1544 super();
1545 inParagraph = false;
1546 }
1547
1548 public void handleText(String text)
1549 {
1550 if (! inParagraph)
1551 beginParagraph();
1552
1553 deliverText(text, currentTextAttributes());
1554 }
1555
1556 abstract void deliverText(String text, AttributeSet characterAttributes);
1557
1558 public void close()
1559 {
1560 if (inParagraph)
1561 endParagraph();
1562
1563 super.close();
1564 }
1565
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
1587 protected void beginParagraph()
1588 {
1589 inParagraph = true;
1590 }
1591
1592 protected void endParagraph()
1593 {
1594 AttributeSet pgfAttributes = currentParagraphAttributes();
1595 AttributeSet chrAttributes = currentTextAttributes();
1596 finishParagraph(pgfAttributes, chrAttributes);
1597 inParagraph = false;
1598 }
1599
1600 abstract void finishParagraph(AttributeSet pgfA, AttributeSet chrA);
1601
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 {
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());
1624 }
1625 }
1626
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());
1638 }
1639 }
1640
1641 public void endSection()
1642 {
1643 /* If we implemented sections, we'd end 'em here */
1644 }
1645 }
1646
1647 }