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