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