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         if (keyword.equals("cb")) {
1189             parserState.put(keyword, Integer.valueOf(parameter));
1190             return true;
1191         }
1192 
1193         {
1194             RTFAttribute attr = straightforwardAttributes.get(keyword);
1195             if (attr != null) {
1196                 boolean ok;
1197 
1198                 switch(attr.domain()) {
1199                   case RTFAttribute.D_CHARACTER:
1200                     ok = attr.set(characterAttributes, parameter);
1201                     break;
1202                   case RTFAttribute.D_PARAGRAPH:
1203                     ok = attr.set(paragraphAttributes, parameter);
1204                     break;
1205                   case RTFAttribute.D_SECTION:
1206                     ok = attr.set(sectionAttributes, parameter);
1207                     break;
1208                   case RTFAttribute.D_META:
1209                     mockery.backing = parserState;
1210                     ok = attr.set(mockery, parameter);
1211                     mockery.backing = null;
1212                     break;
1213                   case RTFAttribute.D_DOCUMENT:
1214                     ok = attr.set(documentAttributes, parameter);
1215                     break;
1216                   default:
1217                     /* should never happen */
1218                     ok = false;
1219                     break;
1220                 }
1221                 if (ok)
1222                     return true;
1223             }
1224         }
1225 
1226         if (keyword.equals("fs")) {
1227             StyleConstants.setFontSize(characterAttributes, (parameter / 2));
1228             return true;
1229         }
1230 
1231         /* TODO: superscript/subscript */
1232 
1233         if (keyword.equals("sl")) {
1234             if (parameter == 1000) {  /* magic value! */
1235                 characterAttributes.removeAttribute(StyleConstants.LineSpacing);
1236             } else {
1237                 /* TODO: The RTF sl attribute has special meaning if it's
1238                    negative. Make sure that SwingText has the same special
1239                    meaning, or find a way to imitate that. When SwingText
1240                    handles this, also recognize the slmult keyword. */
1241                 StyleConstants.setLineSpacing(characterAttributes,
1242                                               parameter / 20f);
1243             }
1244             return true;
1245         }
1246 
1247         /* TODO: Other kinds of underlining */
1248 
1249         if (keyword.equals("tx") || keyword.equals("tb")) {
1250             float tabPosition = parameter / 20f;
1251             int tabAlignment, tabLeader;
1252             Number item;
1253 
1254             tabAlignment = TabStop.ALIGN_LEFT;
1255             item = (Number)(parserState.get("tab_alignment"));
1256             if (item != null)
1257                 tabAlignment = item.intValue();
1258             tabLeader = TabStop.LEAD_NONE;
1259             item = (Number)(parserState.get("tab_leader"));
1260             if (item != null)
1261                 tabLeader = item.intValue();
1262             if (keyword.equals("tb"))
1263                 tabAlignment = TabStop.ALIGN_BAR;
1264 
1265             parserState.remove("tab_alignment");
1266             parserState.remove("tab_leader");
1267 
1268             TabStop newStop = new TabStop(tabPosition, tabAlignment, tabLeader);
1269             Dictionary<Object, Object> tabs;
1270             Integer stopCount;
1271 
1272             @SuppressWarnings("unchecked")
1273             Dictionary<Object, Object>tmp = (Dictionary)parserState.get("_tabs");
1274             tabs = tmp;
1275             if (tabs == null) {
1276                 tabs = new Hashtable<Object, Object>();
1277                 parserState.put("_tabs", tabs);
1278                 stopCount = Integer.valueOf(1);
1279             } else {
1280                 stopCount = (Integer)tabs.get("stop count");
1281                 stopCount = Integer.valueOf(1 + stopCount.intValue());
1282             }
1283             tabs.put(stopCount, newStop);
1284             tabs.put("stop count", stopCount);
1285             parserState.remove("_tabs_immutable");
1286 
1287             return true;
1288         }
1289 
1290         if (keyword.equals("s") &&
1291             paragraphStyles != null) {
1292             parserState.put("paragraphStyle", paragraphStyles[parameter]);
1293             return true;
1294         }
1295 
1296         if (keyword.equals("cs") &&
1297             characterStyles != null) {
1298             parserState.put("characterStyle", characterStyles[parameter]);
1299             return true;
1300         }
1301 
1302         if (keyword.equals("ds") &&
1303             sectionStyles != null) {
1304             parserState.put("sectionStyle", sectionStyles[parameter]);
1305             return true;
1306         }
1307 
1308         return false;
1309     }
1310 
1311     /** Returns a new MutableAttributeSet containing the
1312      *  default character attributes */
rootCharacterAttributes()1313     protected MutableAttributeSet rootCharacterAttributes()
1314     {
1315         MutableAttributeSet set = new SimpleAttributeSet();
1316 
1317         /* TODO: default font */
1318 
1319         StyleConstants.setItalic(set, false);
1320         StyleConstants.setBold(set, false);
1321         StyleConstants.setUnderline(set, false);
1322         StyleConstants.setForeground(set, defaultColor());
1323 
1324         return set;
1325     }
1326 
1327     /** Returns a new MutableAttributeSet containing the
1328      *  default paragraph attributes */
rootParagraphAttributes()1329     protected MutableAttributeSet rootParagraphAttributes()
1330     {
1331         MutableAttributeSet set = new SimpleAttributeSet();
1332 
1333         StyleConstants.setLeftIndent(set, 0f);
1334         StyleConstants.setRightIndent(set, 0f);
1335         StyleConstants.setFirstLineIndent(set, 0f);
1336 
1337         /* TODO: what should this be, really? */
1338         set.setResolveParent(target.getStyle(StyleContext.DEFAULT_STYLE));
1339 
1340         return set;
1341     }
1342 
1343     /** Returns a new MutableAttributeSet containing the
1344      *  default section attributes */
rootSectionAttributes()1345     protected MutableAttributeSet rootSectionAttributes()
1346     {
1347         MutableAttributeSet set = new SimpleAttributeSet();
1348 
1349         return set;
1350     }
1351 
1352     /**
1353      * Calculates the current text (character) attributes in a form suitable
1354      * for SwingText from the current parser state.
1355      *
1356      * @return a new MutableAttributeSet containing the text attributes.
1357      */
currentTextAttributes()1358     MutableAttributeSet currentTextAttributes()
1359     {
1360         MutableAttributeSet attributes =
1361             new SimpleAttributeSet(characterAttributes);
1362         Integer fontnum;
1363         Integer stateItem;
1364 
1365         /* figure out the font name */
1366         /* TODO: catch exceptions for undefined attributes,
1367            bad font indices, etc.? (as it stands, it is the caller's
1368            job to clean up after corrupt RTF) */
1369         fontnum = (Integer)parserState.get("f");
1370         /* note setFontFamily() can not handle a null font */
1371         String fontFamily;
1372         if (fontnum != null)
1373             fontFamily = fontTable.get(fontnum);
1374         else
1375             fontFamily = null;
1376         if (fontFamily != null)
1377             StyleConstants.setFontFamily(attributes, fontFamily);
1378         else
1379             attributes.removeAttribute(StyleConstants.FontFamily);
1380 
1381         if (colorTable != null) {
1382             stateItem = (Integer)parserState.get("cf");
1383             if (stateItem != null) {
1384                 Color fg = colorTable[stateItem.intValue()];
1385                 StyleConstants.setForeground(attributes, fg);
1386             } else {
1387                 /* AttributeSet dies if you set a value to null */
1388                 attributes.removeAttribute(StyleConstants.Foreground);
1389             }
1390         }
1391 
1392         if (colorTable != null) {
1393             stateItem = (Integer)parserState.get("cb");
1394             if (stateItem != null) {
1395                 Color bg = colorTable[stateItem.intValue()];
1396                 attributes.addAttribute(StyleConstants.Background,
1397                                         bg);
1398             } else {
1399                 /* AttributeSet dies if you set a value to null */
1400                 attributes.removeAttribute(StyleConstants.Background);
1401             }
1402         }
1403 
1404         Style characterStyle = (Style)parserState.get("characterStyle");
1405         if (characterStyle != null)
1406             attributes.setResolveParent(characterStyle);
1407 
1408         /* Other attributes are maintained directly in "attributes" */
1409 
1410         return attributes;
1411     }
1412 
1413     /**
1414      * Calculates the current paragraph attributes (with keys
1415      * as given in StyleConstants) from the current parser state.
1416      *
1417      * @return a newly created MutableAttributeSet.
1418      * @see StyleConstants
1419      */
currentParagraphAttributes()1420     MutableAttributeSet currentParagraphAttributes()
1421     {
1422         /* NB if there were a mutableCopy() method we should use it */
1423         MutableAttributeSet bld = new SimpleAttributeSet(paragraphAttributes);
1424 
1425         Integer stateItem;
1426 
1427         /*** Tab stops ***/
1428         TabStop[] tabs;
1429 
1430         tabs = (TabStop[])parserState.get("_tabs_immutable");
1431         if (tabs == null) {
1432             @SuppressWarnings("unchecked")
1433             Dictionary<Object, Object> workingTabs = (Dictionary)parserState.get("_tabs");
1434             if (workingTabs != null) {
1435                 int count = ((Integer)workingTabs.get("stop count")).intValue();
1436                 tabs = new TabStop[count];
1437                 for (int ix = 1; ix <= count; ix ++)
1438                     tabs[ix-1] = (TabStop)workingTabs.get(Integer.valueOf(ix));
1439                 parserState.put("_tabs_immutable", tabs);
1440             }
1441         }
1442         if (tabs != null)
1443             bld.addAttribute(Constants.Tabs, tabs);
1444 
1445         Style paragraphStyle = (Style)parserState.get("paragraphStyle");
1446         if (paragraphStyle != null)
1447             bld.setResolveParent(paragraphStyle);
1448 
1449         return bld;
1450     }
1451 
1452     /**
1453      * Calculates the current section attributes
1454      * from the current parser state.
1455      *
1456      * @return a newly created MutableAttributeSet.
1457      */
currentSectionAttributes()1458     public AttributeSet currentSectionAttributes()
1459     {
1460         MutableAttributeSet attributes = new SimpleAttributeSet(sectionAttributes);
1461 
1462         Style sectionStyle = (Style)parserState.get("sectionStyle");
1463         if (sectionStyle != null)
1464             attributes.setResolveParent(sectionStyle);
1465 
1466         return attributes;
1467     }
1468 
1469     /** Resets the filter's internal notion of the current character
1470      *  attributes to their default values. Invoked to handle the
1471      *  \plain keyword. */
resetCharacterAttributes()1472     protected void resetCharacterAttributes()
1473     {
1474         handleKeyword("f", 0);
1475         handleKeyword("cf", 0);
1476 
1477         handleKeyword("fs", 24);  /* 12 pt. */
1478 
1479         Enumeration<RTFAttribute> attributes = straightforwardAttributes.elements();
1480         while(attributes.hasMoreElements()) {
1481             RTFAttribute attr = attributes.nextElement();
1482             if (attr.domain() == RTFAttribute.D_CHARACTER)
1483                 attr.setDefault(characterAttributes);
1484         }
1485 
1486         handleKeyword("sl", 1000);
1487 
1488         parserState.remove("characterStyle");
1489     }
1490 
1491     /** Resets the filter's internal notion of the current paragraph's
1492      *  attributes to their default values. Invoked to handle the
1493      *  \pard keyword. */
resetParagraphAttributes()1494     protected void resetParagraphAttributes()
1495     {
1496         parserState.remove("_tabs");
1497         parserState.remove("_tabs_immutable");
1498         parserState.remove("paragraphStyle");
1499 
1500         StyleConstants.setAlignment(paragraphAttributes,
1501                                     StyleConstants.ALIGN_LEFT);
1502 
1503         Enumeration<RTFAttribute> attributes = straightforwardAttributes.elements();
1504         while(attributes.hasMoreElements()) {
1505             RTFAttribute attr = attributes.nextElement();
1506             if (attr.domain() == RTFAttribute.D_PARAGRAPH)
1507                 attr.setDefault(characterAttributes);
1508         }
1509     }
1510 
1511     /** Resets the filter's internal notion of the current section's
1512      *  attributes to their default values. Invoked to handle the
1513      *  \sectd keyword. */
resetSectionAttributes()1514     protected void resetSectionAttributes()
1515     {
1516         Enumeration<RTFAttribute> attributes = straightforwardAttributes.elements();
1517         while(attributes.hasMoreElements()) {
1518             RTFAttribute attr = attributes.nextElement();
1519             if (attr.domain() == RTFAttribute.D_SECTION)
1520                 attr.setDefault(characterAttributes);
1521         }
1522 
1523         parserState.remove("sectionStyle");
1524     }
1525 }
1526 
1527 /** RTFReader.TextHandlingDestination provides basic text handling
1528  *  functionality. Subclasses must implement: <dl>
1529  *  <dt>deliverText()<dd>to handle a run of text with the same
1530  *                       attributes
1531  *  <dt>finishParagraph()<dd>to end the current paragraph and
1532  *                           set the paragraph's attributes
1533  *  <dt>endSection()<dd>to end the current section
1534  *  </dl>
1535  */
1536 abstract class TextHandlingDestination
1537     extends AttributeTrackingDestination
1538     implements Destination
1539 {
1540     /** <code>true</code> if the reader has not just finished
1541      *  a paragraph; false upon startup */
1542     boolean inParagraph;
1543 
TextHandlingDestination()1544     public TextHandlingDestination()
1545     {
1546         super();
1547         inParagraph = false;
1548     }
1549 
handleText(String text)1550     public void handleText(String text)
1551     {
1552         if (! inParagraph)
1553             beginParagraph();
1554 
1555         deliverText(text, currentTextAttributes());
1556     }
1557 
deliverText(String text, AttributeSet characterAttributes)1558     abstract void deliverText(String text, AttributeSet characterAttributes);
1559 
close()1560     public void close()
1561     {
1562         if (inParagraph)
1563             endParagraph();
1564 
1565         super.close();
1566     }
1567 
handleKeyword(String keyword)1568     public boolean handleKeyword(String keyword)
1569     {
1570         if (keyword.equals("\r") || keyword.equals("\n")) {
1571             keyword = "par";
1572         }
1573 
1574         if (keyword.equals("par")) {
1575 //          warnings.println("Ending paragraph.");
1576             endParagraph();
1577             return true;
1578         }
1579 
1580         if (keyword.equals("sect")) {
1581 //          warnings.println("Ending section.");
1582             endSection();
1583             return true;
1584         }
1585 
1586         return super.handleKeyword(keyword);
1587     }
1588 
beginParagraph()1589     protected void beginParagraph()
1590     {
1591         inParagraph = true;
1592     }
1593 
endParagraph()1594     protected void endParagraph()
1595     {
1596         AttributeSet pgfAttributes = currentParagraphAttributes();
1597         AttributeSet chrAttributes = currentTextAttributes();
1598         finishParagraph(pgfAttributes, chrAttributes);
1599         inParagraph = false;
1600     }
1601 
finishParagraph(AttributeSet pgfA, AttributeSet chrA)1602     abstract void finishParagraph(AttributeSet pgfA, AttributeSet chrA);
1603 
endSection()1604     abstract void endSection();
1605 }
1606 
1607 /** RTFReader.DocumentDestination is a concrete subclass of
1608  *  TextHandlingDestination which appends the text to the
1609  *  StyledDocument given by the <code>target</code> ivar of the
1610  *  containing RTFReader.
1611  */
1612 class DocumentDestination
1613     extends TextHandlingDestination
1614     implements Destination
1615 {
deliverText(String text, AttributeSet characterAttributes)1616     public void deliverText(String text, AttributeSet characterAttributes)
1617     {
1618         try {
1619             target.insertString(target.getLength(),
1620                                 text,
1621                                 currentTextAttributes());
1622         } catch (BadLocationException ble) {
1623             /* This shouldn't be able to happen, of course */
1624             /* TODO is InternalError the correct error to throw? */
1625             throw new InternalError(ble.getMessage(), ble);
1626         }
1627     }
1628 
finishParagraph(AttributeSet pgfAttributes, AttributeSet chrAttributes)1629     public void finishParagraph(AttributeSet pgfAttributes,
1630                                 AttributeSet chrAttributes)
1631     {
1632         int pgfEndPosition = target.getLength();
1633         try {
1634             target.insertString(pgfEndPosition, "\n", chrAttributes);
1635             target.setParagraphAttributes(pgfEndPosition, 1, pgfAttributes, true);
1636         } catch (BadLocationException ble) {
1637             /* This shouldn't be able to happen, of course */
1638             /* TODO is InternalError the correct error to throw? */
1639             throw new InternalError(ble.getMessage(), ble);
1640         }
1641     }
1642 
endSection()1643     public void endSection()
1644     {
1645         /* If we implemented sections, we'd end 'em here */
1646     }
1647 }
1648 
1649 }
1650