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