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