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