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