1 /* 2 * Copyright (c) 1997, 2017, 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.awt.Color; 30 import java.awt.Font; 31 import java.io.OutputStream; 32 import java.io.IOException; 33 34 import javax.swing.text.*; 35 36 /** 37 * Generates an RTF output stream (java.io.OutputStream) from rich text 38 * (handed off through a series of LTTextAcceptor calls). Can be used to 39 * generate RTF from any object which knows how to write to a text acceptor 40 * (e.g., LTAttributedText and LTRTFFilter). 41 * 42 * <p>Note that this is a lossy conversion since RTF's model of 43 * text does not exactly correspond with LightText's. 44 * 45 * @see java.io.OutputStream 46 */ 47 class RTFGenerator extends Object 48 { 49 /* These dictionaries map Colors, font names, or Style objects 50 to Integers */ 51 Dictionary<Object, Integer> colorTable; 52 int colorCount; 53 Dictionary<String, Integer> fontTable; 54 int fontCount; 55 Dictionary<AttributeSet, Integer> styleTable; 56 int styleCount; 57 58 /* where all the text is going */ 59 OutputStream outputStream; 60 61 boolean afterKeyword; 62 63 MutableAttributeSet outputAttributes; 64 65 /* the value of the last \\ucN keyword emitted */ 66 int unicodeCount; 67 68 /* for efficiency's sake (ha) */ 69 private Segment workingSegment; 70 71 int[] outputConversion; 72 73 /** The default color, used for text without an explicit color 74 * attribute. */ 75 public static final Color defaultRTFColor = Color.black; 76 77 public static final float defaultFontSize = 12f; 78 79 public static final String defaultFontFamily = "Helvetica"; 80 81 /* constants so we can avoid allocating objects in inner loops */ 82 private static final Object MagicToken; 83 84 /* An array of character-keyword pairs. This could be done 85 as a dictionary (and lookup would be quicker), but that 86 would require allocating an object for every character 87 written (slow!). */ 88 static class CharacterKeywordPair 89 { public char character; public String keyword; } 90 protected static CharacterKeywordPair[] textKeywords; 91 92 static { 93 MagicToken = new Object(); 94 95 Dictionary<String, String> textKeywordDictionary = RTFReader.textKeywords; 96 Enumeration<String> keys = textKeywordDictionary.keys(); 97 Vector<CharacterKeywordPair> tempPairs = new Vector<CharacterKeywordPair>(); 98 while(keys.hasMoreElements()) { 99 CharacterKeywordPair pair = new CharacterKeywordPair(); 100 pair.keyword = keys.nextElement(); 101 pair.character = textKeywordDictionary.get(pair.keyword).charAt(0); 102 tempPairs.addElement(pair); 103 } 104 textKeywords = new CharacterKeywordPair[tempPairs.size()]; 105 tempPairs.copyInto(textKeywords); 106 } 107 108 static final char[] hexdigits = { '0', '1', '2', '3', '4', '5', '6', '7', 109 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 110 111 public static void writeDocument(Document d, OutputStream to) 112 throws IOException 113 { 114 RTFGenerator gen = new RTFGenerator(to); 115 Element root = d.getDefaultRootElement(); 116 117 gen.examineElement(root); 118 gen.writeRTFHeader(); 119 gen.writeDocumentProperties(d); 120 121 /* TODO this assumes a particular element structure; is there 122 a way to iterate more generically ? */ 123 int max = root.getElementCount(); 124 for(int idx = 0; idx < max; idx++) 125 gen.writeParagraphElement(root.getElement(idx)); 126 127 gen.writeRTFTrailer(); 128 } 129 130 public RTFGenerator(OutputStream to) 131 { 132 colorTable = new Hashtable<Object, Integer>(); 133 colorTable.put(defaultRTFColor, Integer.valueOf(0)); 134 colorCount = 1; 135 136 fontTable = new Hashtable<String, Integer>(); 137 fontCount = 0; 138 139 styleTable = new Hashtable<AttributeSet, Integer>(); 140 /* TODO: put default style in style table */ 141 styleCount = 0; 142 143 workingSegment = new Segment(); 144 145 outputStream = to; 146 147 unicodeCount = 1; 148 } 149 150 public void examineElement(Element el) 151 { 152 AttributeSet a = el.getAttributes(); 153 String fontName; 154 Object foregroundColor, backgroundColor; 155 156 tallyStyles(a); 157 158 if (a != null) { 159 /* TODO: default color must be color 0! */ 160 161 foregroundColor = StyleConstants.getForeground(a); 162 if (foregroundColor != null && 163 colorTable.get(foregroundColor) == null) { 164 colorTable.put(foregroundColor, colorCount); 165 colorCount ++; 166 } 167 168 backgroundColor = a.getAttribute(StyleConstants.Background); 169 if (backgroundColor != null && 170 colorTable.get(backgroundColor) == null) { 171 colorTable.put(backgroundColor, colorCount); 172 colorCount ++; 173 } 174 175 fontName = StyleConstants.getFontFamily(a); 176 177 if (fontName == null) 178 fontName = defaultFontFamily; 179 180 if (fontName != null && 181 fontTable.get(fontName) == null) { 182 fontTable.put(fontName, fontCount); 183 fontCount ++; 184 } 185 } 186 187 int el_count = el.getElementCount(); 188 for(int el_idx = 0; el_idx < el_count; el_idx ++) { 189 examineElement(el.getElement(el_idx)); 190 } 191 } 192 193 private void tallyStyles(AttributeSet a) { 194 while (a != null) { 195 if (a instanceof Style) { 196 Integer aNum = styleTable.get(a); 197 if (aNum == null) { 198 styleCount = styleCount + 1; 199 aNum = styleCount; 200 styleTable.put(a, aNum); 201 } 202 } 203 a = a.getResolveParent(); 204 } 205 } 206 207 private Style findStyle(AttributeSet a) 208 { 209 while(a != null) { 210 if (a instanceof Style) { 211 Object aNum = styleTable.get(a); 212 if (aNum != null) 213 return (Style)a; 214 } 215 a = a.getResolveParent(); 216 } 217 return null; 218 } 219 220 private Integer findStyleNumber(AttributeSet a, String domain) 221 { 222 while(a != null) { 223 if (a instanceof Style) { 224 Integer aNum = styleTable.get(a); 225 if (aNum != null) { 226 if (domain == null || 227 domain.equals(a.getAttribute(Constants.StyleType))) 228 return aNum; 229 } 230 231 } 232 a = a.getResolveParent(); 233 } 234 return null; 235 } 236 237 private static Object attrDiff(MutableAttributeSet oldAttrs, 238 AttributeSet newAttrs, 239 Object key, 240 Object dfl) 241 { 242 Object oldValue, newValue; 243 244 oldValue = oldAttrs.getAttribute(key); 245 newValue = newAttrs.getAttribute(key); 246 247 if (newValue == oldValue) 248 return null; 249 if (newValue == null) { 250 oldAttrs.removeAttribute(key); 251 if (dfl != null && !dfl.equals(oldValue)) 252 return dfl; 253 else 254 return null; 255 } 256 if (oldValue == null || 257 !equalArraysOK(oldValue, newValue)) { 258 oldAttrs.addAttribute(key, newValue); 259 return newValue; 260 } 261 return null; 262 } 263 264 private static boolean equalArraysOK(Object a, Object b) 265 { 266 Object[] aa, bb; 267 if (a == b) 268 return true; 269 if (a == null || b == null) 270 return false; 271 if (a.equals(b)) 272 return true; 273 if (!(a.getClass().isArray() && b.getClass().isArray())) 274 return false; 275 aa = (Object[])a; 276 bb = (Object[])b; 277 if (aa.length != bb.length) 278 return false; 279 280 int i; 281 int l = aa.length; 282 for(i = 0; i < l; i++) { 283 if (!equalArraysOK(aa[i], bb[i])) 284 return false; 285 } 286 287 return true; 288 } 289 290 /* Writes a line break to the output file, for ease in debugging */ 291 public void writeLineBreak() 292 throws IOException 293 { 294 writeRawString("\n"); 295 afterKeyword = false; 296 } 297 298 299 public void writeRTFHeader() 300 throws IOException 301 { 302 int index; 303 304 /* TODO: Should the writer attempt to examine the text it's writing 305 and pick a character set which will most compactly represent the 306 document? (currently the writer always uses the ansi character 307 set, which is roughly ISO-8859 Latin-1, and uses Unicode escapes 308 for all other characters. However Unicode is a relatively 309 recent addition to RTF, and not all readers will understand it.) */ 310 writeBegingroup(); 311 writeControlWord("rtf", 1); 312 writeControlWord("ansi"); 313 outputConversion = outputConversionForName("ansi"); 314 writeLineBreak(); 315 316 /* write font table */ 317 String[] sortedFontTable = new String[fontCount]; 318 Enumeration<String> fonts = fontTable.keys(); 319 String font; 320 while(fonts.hasMoreElements()) { 321 font = fonts.nextElement(); 322 Integer num = fontTable.get(font); 323 sortedFontTable[num.intValue()] = font; 324 } 325 writeBegingroup(); 326 writeControlWord("fonttbl"); 327 for(index = 0; index < fontCount; index ++) { 328 writeControlWord("f", index); 329 writeControlWord("fnil"); /* TODO: supply correct font style */ 330 writeText(sortedFontTable[index]); 331 writeText(";"); 332 } 333 writeEndgroup(); 334 writeLineBreak(); 335 336 /* write color table */ 337 if (colorCount > 1) { 338 Color[] sortedColorTable = new Color[colorCount]; 339 Enumeration<Object> colors = colorTable.keys(); 340 Color color; 341 while(colors.hasMoreElements()) { 342 color = (Color)colors.nextElement(); 343 Integer num = colorTable.get(color); 344 sortedColorTable[num.intValue()] = color; 345 } 346 writeBegingroup(); 347 writeControlWord("colortbl"); 348 for(index = 0; index < colorCount; index ++) { 349 color = sortedColorTable[index]; 350 if (color != null) { 351 writeControlWord("red", color.getRed()); 352 writeControlWord("green", color.getGreen()); 353 writeControlWord("blue", color.getBlue()); 354 } 355 writeRawString(";"); 356 } 357 writeEndgroup(); 358 writeLineBreak(); 359 } 360 361 /* write the style sheet */ 362 if (styleCount > 1) { 363 writeBegingroup(); 364 writeControlWord("stylesheet"); 365 Enumeration<AttributeSet> styles = styleTable.keys(); 366 while(styles.hasMoreElements()) { 367 Style style = (Style)styles.nextElement(); 368 int styleNumber = styleTable.get(style).intValue(); 369 writeBegingroup(); 370 String styleType = (String)style.getAttribute(Constants.StyleType); 371 if (styleType == null) 372 styleType = Constants.STParagraph; 373 if (styleType.equals(Constants.STCharacter)) { 374 writeControlWord("*"); 375 writeControlWord("cs", styleNumber); 376 } else if(styleType.equals(Constants.STSection)) { 377 writeControlWord("*"); 378 writeControlWord("ds", styleNumber); 379 } else { 380 writeControlWord("s", styleNumber); 381 } 382 383 AttributeSet basis = style.getResolveParent(); 384 MutableAttributeSet goat; 385 if (basis == null) { 386 goat = new SimpleAttributeSet(); 387 } else { 388 goat = new SimpleAttributeSet(basis); 389 } 390 391 updateSectionAttributes(goat, style, false); 392 updateParagraphAttributes(goat, style, false); 393 updateCharacterAttributes(goat, style, false); 394 395 basis = style.getResolveParent(); 396 if (basis != null && basis instanceof Style) { 397 Integer basedOn = styleTable.get(basis); 398 if (basedOn != null) { 399 writeControlWord("sbasedon", basedOn.intValue()); 400 } 401 } 402 403 Style nextStyle = (Style)style.getAttribute(Constants.StyleNext); 404 if (nextStyle != null) { 405 Integer nextNum = styleTable.get(nextStyle); 406 if (nextNum != null) { 407 writeControlWord("snext", nextNum.intValue()); 408 } 409 } 410 411 Boolean hidden = (Boolean)style.getAttribute(Constants.StyleHidden); 412 if (hidden != null && hidden.booleanValue()) 413 writeControlWord("shidden"); 414 415 Boolean additive = (Boolean)style.getAttribute(Constants.StyleAdditive); 416 if (additive != null && additive.booleanValue()) 417 writeControlWord("additive"); 418 419 420 writeText(style.getName()); 421 writeText(";"); 422 writeEndgroup(); 423 } 424 writeEndgroup(); 425 writeLineBreak(); 426 } 427 428 outputAttributes = new SimpleAttributeSet(); 429 } 430 431 void writeDocumentProperties(Document doc) 432 throws IOException 433 { 434 /* Write the document properties */ 435 int i; 436 boolean wroteSomething = false; 437 438 for(i = 0; i < RTFAttributes.attributes.length; i++) { 439 RTFAttribute attr = RTFAttributes.attributes[i]; 440 if (attr.domain() != RTFAttribute.D_DOCUMENT) 441 continue; 442 Object prop = doc.getProperty(attr.swingName()); 443 boolean ok = attr.writeValue(prop, this, false); 444 if (ok) 445 wroteSomething = true; 446 } 447 448 if (wroteSomething) 449 writeLineBreak(); 450 } 451 452 public void writeRTFTrailer() 453 throws IOException 454 { 455 writeEndgroup(); 456 writeLineBreak(); 457 } 458 459 protected void checkNumericControlWord(MutableAttributeSet currentAttributes, 460 AttributeSet newAttributes, 461 Object attrName, 462 String controlWord, 463 float dflt, float scale) 464 throws IOException 465 { 466 Object parm; 467 468 if ((parm = attrDiff(currentAttributes, newAttributes, 469 attrName, MagicToken)) != null) { 470 float targ; 471 if (parm == MagicToken) 472 targ = dflt; 473 else 474 targ = ((Number)parm).floatValue(); 475 writeControlWord(controlWord, Math.round(targ * scale)); 476 } 477 } 478 479 protected void checkControlWord(MutableAttributeSet currentAttributes, 480 AttributeSet newAttributes, 481 RTFAttribute word) 482 throws IOException 483 { 484 Object parm; 485 486 if ((parm = attrDiff(currentAttributes, newAttributes, 487 word.swingName(), MagicToken)) != null) { 488 if (parm == MagicToken) 489 parm = null; 490 if (!word.writeValue(parm, this, true) && 491 word instanceof RTFAttributes.AssertiveAttribute ) { 492 currentAttributes.removeAttribute(word.swingName()); 493 } 494 } 495 } 496 497 protected void checkControlWords(MutableAttributeSet currentAttributes, 498 AttributeSet newAttributes, 499 RTFAttribute words[], 500 int domain) 501 throws IOException 502 { 503 int wordIndex; 504 int wordCount = words.length; 505 for(wordIndex = 0; wordIndex < wordCount; wordIndex++) { 506 RTFAttribute attr = words[wordIndex]; 507 if (attr.domain() == domain) 508 checkControlWord(currentAttributes, newAttributes, attr); 509 } 510 } 511 512 void updateSectionAttributes(MutableAttributeSet current, 513 AttributeSet newAttributes, 514 boolean emitStyleChanges) 515 throws IOException 516 { 517 if (emitStyleChanges) { 518 Object oldStyle = current.getAttribute("sectionStyle"); 519 Object newStyle = findStyleNumber(newAttributes, Constants.STSection); 520 if (oldStyle != newStyle) { 521 if (oldStyle != null) { 522 resetSectionAttributes(current); 523 } 524 if (newStyle != null) { 525 writeControlWord("ds", ((Integer)newStyle).intValue()); 526 current.addAttribute("sectionStyle", newStyle); 527 } else { 528 current.removeAttribute("sectionStyle"); 529 } 530 } 531 } 532 533 checkControlWords(current, newAttributes, 534 RTFAttributes.attributes, RTFAttribute.D_SECTION); 535 } 536 537 protected void resetSectionAttributes(MutableAttributeSet currentAttributes) 538 throws IOException 539 { 540 writeControlWord("sectd"); 541 542 int wordIndex; 543 int wordCount = RTFAttributes.attributes.length; 544 for(wordIndex = 0; wordIndex < wordCount; wordIndex++) { 545 RTFAttribute attr = RTFAttributes.attributes[wordIndex]; 546 if (attr.domain() == RTFAttribute.D_SECTION) 547 attr.setDefault(currentAttributes); 548 } 549 550 currentAttributes.removeAttribute("sectionStyle"); 551 } 552 553 void updateParagraphAttributes(MutableAttributeSet current, 554 AttributeSet newAttributes, 555 boolean emitStyleChanges) 556 throws IOException 557 { 558 Object parm; 559 Object oldStyle, newStyle; 560 561 /* The only way to get rid of tabs or styles is with the \pard keyword, 562 emitted by resetParagraphAttributes(). Ideally we should avoid 563 emitting \pard if the new paragraph's tabs are a superset of the old 564 paragraph's tabs. */ 565 566 if (emitStyleChanges) { 567 oldStyle = current.getAttribute("paragraphStyle"); 568 newStyle = findStyleNumber(newAttributes, Constants.STParagraph); 569 if (oldStyle != newStyle) { 570 if (oldStyle != null) { 571 resetParagraphAttributes(current); 572 oldStyle = null; 573 } 574 } 575 } else { 576 oldStyle = null; 577 newStyle = null; 578 } 579 580 Object oldTabs = current.getAttribute(Constants.Tabs); 581 Object newTabs = newAttributes.getAttribute(Constants.Tabs); 582 if (oldTabs != newTabs) { 583 if (oldTabs != null) { 584 resetParagraphAttributes(current); 585 oldTabs = null; 586 oldStyle = null; 587 } 588 } 589 590 if (oldStyle != newStyle && newStyle != null) { 591 writeControlWord("s", ((Integer)newStyle).intValue()); 592 current.addAttribute("paragraphStyle", newStyle); 593 } 594 595 checkControlWords(current, newAttributes, 596 RTFAttributes.attributes, RTFAttribute.D_PARAGRAPH); 597 598 if (oldTabs != newTabs && newTabs != null) { 599 TabStop tabs[] = (TabStop[])newTabs; 600 int index; 601 for(index = 0; index < tabs.length; index ++) { 602 TabStop tab = tabs[index]; 603 switch (tab.getAlignment()) { 604 case TabStop.ALIGN_LEFT: 605 case TabStop.ALIGN_BAR: 606 break; 607 case TabStop.ALIGN_RIGHT: 608 writeControlWord("tqr"); 609 break; 610 case TabStop.ALIGN_CENTER: 611 writeControlWord("tqc"); 612 break; 613 case TabStop.ALIGN_DECIMAL: 614 writeControlWord("tqdec"); 615 break; 616 } 617 switch (tab.getLeader()) { 618 case TabStop.LEAD_NONE: 619 break; 620 case TabStop.LEAD_DOTS: 621 writeControlWord("tldot"); 622 break; 623 case TabStop.LEAD_HYPHENS: 624 writeControlWord("tlhyph"); 625 break; 626 case TabStop.LEAD_UNDERLINE: 627 writeControlWord("tlul"); 628 break; 629 case TabStop.LEAD_THICKLINE: 630 writeControlWord("tlth"); 631 break; 632 case TabStop.LEAD_EQUALS: 633 writeControlWord("tleq"); 634 break; 635 } 636 int twips = Math.round(20f * tab.getPosition()); 637 if (tab.getAlignment() == TabStop.ALIGN_BAR) { 638 writeControlWord("tb", twips); 639 } else { 640 writeControlWord("tx", twips); 641 } 642 } 643 current.addAttribute(Constants.Tabs, tabs); 644 } 645 } 646 647 public void writeParagraphElement(Element el) 648 throws IOException 649 { 650 updateParagraphAttributes(outputAttributes, el.getAttributes(), true); 651 652 int sub_count = el.getElementCount(); 653 for(int idx = 0; idx < sub_count; idx ++) { 654 writeTextElement(el.getElement(idx)); 655 } 656 657 writeControlWord("par"); 658 writeLineBreak(); /* makes the raw file more readable */ 659 } 660 661 /* debugging. TODO: remove. 662 private static String tabdump(Object tso) 663 { 664 String buf; 665 int i; 666 667 if (tso == null) 668 return "[none]"; 669 670 TabStop[] ts = (TabStop[])tso; 671 672 buf = "["; 673 for(i = 0; i < ts.length; i++) { 674 buf = buf + ts[i].toString(); 675 if ((i+1) < ts.length) 676 buf = buf + ","; 677 } 678 return buf + "]"; 679 } 680 */ 681 682 protected void resetParagraphAttributes(MutableAttributeSet currentAttributes) 683 throws IOException 684 { 685 writeControlWord("pard"); 686 687 currentAttributes.addAttribute(StyleConstants.Alignment, Integer.valueOf(0)); 688 689 int wordIndex; 690 int wordCount = RTFAttributes.attributes.length; 691 for(wordIndex = 0; wordIndex < wordCount; wordIndex++) { 692 RTFAttribute attr = RTFAttributes.attributes[wordIndex]; 693 if (attr.domain() == RTFAttribute.D_PARAGRAPH) 694 attr.setDefault(currentAttributes); 695 } 696 697 currentAttributes.removeAttribute("paragraphStyle"); 698 currentAttributes.removeAttribute(Constants.Tabs); 699 } 700 701 void updateCharacterAttributes(MutableAttributeSet current, 702 AttributeSet newAttributes, 703 boolean updateStyleChanges) 704 throws IOException 705 { 706 Object parm; 707 708 if (updateStyleChanges) { 709 Object oldStyle = current.getAttribute("characterStyle"); 710 Object newStyle = findStyleNumber(newAttributes, 711 Constants.STCharacter); 712 if (oldStyle != newStyle) { 713 if (oldStyle != null) { 714 resetCharacterAttributes(current); 715 } 716 if (newStyle != null) { 717 writeControlWord("cs", ((Integer)newStyle).intValue()); 718 current.addAttribute("characterStyle", newStyle); 719 } else { 720 current.removeAttribute("characterStyle"); 721 } 722 } 723 } 724 725 if ((parm = attrDiff(current, newAttributes, 726 StyleConstants.FontFamily, null)) != null) { 727 Integer fontNum = fontTable.get(parm); 728 writeControlWord("f", fontNum.intValue()); 729 } 730 731 checkNumericControlWord(current, newAttributes, 732 StyleConstants.FontSize, "fs", 733 defaultFontSize, 2f); 734 735 checkControlWords(current, newAttributes, 736 RTFAttributes.attributes, RTFAttribute.D_CHARACTER); 737 738 checkNumericControlWord(current, newAttributes, 739 StyleConstants.LineSpacing, "sl", 740 0, 20f); /* TODO: sl wackiness */ 741 742 if ((parm = attrDiff(current, newAttributes, 743 StyleConstants.Background, MagicToken)) != null) { 744 int colorNum; 745 if (parm == MagicToken) 746 colorNum = 0; 747 else 748 colorNum = colorTable.get(parm).intValue(); 749 writeControlWord("cb", colorNum); 750 } 751 752 if ((parm = attrDiff(current, newAttributes, 753 StyleConstants.Foreground, null)) != null) { 754 int colorNum; 755 if (parm == MagicToken) 756 colorNum = 0; 757 else 758 colorNum = colorTable.get(parm).intValue(); 759 writeControlWord("cf", colorNum); 760 } 761 } 762 763 protected void resetCharacterAttributes(MutableAttributeSet currentAttributes) 764 throws IOException 765 { 766 writeControlWord("plain"); 767 768 int wordIndex; 769 int wordCount = RTFAttributes.attributes.length; 770 for(wordIndex = 0; wordIndex < wordCount; wordIndex++) { 771 RTFAttribute attr = RTFAttributes.attributes[wordIndex]; 772 if (attr.domain() == RTFAttribute.D_CHARACTER) 773 attr.setDefault(currentAttributes); 774 } 775 776 StyleConstants.setFontFamily(currentAttributes, defaultFontFamily); 777 currentAttributes.removeAttribute(StyleConstants.FontSize); /* =default */ 778 currentAttributes.removeAttribute(StyleConstants.Background); 779 currentAttributes.removeAttribute(StyleConstants.Foreground); 780 currentAttributes.removeAttribute(StyleConstants.LineSpacing); 781 currentAttributes.removeAttribute("characterStyle"); 782 } 783 784 public void writeTextElement(Element el) 785 throws IOException 786 { 787 updateCharacterAttributes(outputAttributes, el.getAttributes(), true); 788 789 if (el.isLeaf()) { 790 try { 791 el.getDocument().getText(el.getStartOffset(), 792 el.getEndOffset() - el.getStartOffset(), 793 this.workingSegment); 794 } catch (BadLocationException ble) { 795 /* TODO is this the correct error to raise? */ 796 ble.printStackTrace(); 797 throw new InternalError(ble.getMessage()); 798 } 799 writeText(this.workingSegment); 800 } else { 801 int sub_count = el.getElementCount(); 802 for(int idx = 0; idx < sub_count; idx ++) 803 writeTextElement(el.getElement(idx)); 804 } 805 } 806 807 public void writeText(Segment s) 808 throws IOException 809 { 810 int pos, end; 811 char[] array; 812 813 pos = s.offset; 814 end = pos + s.count; 815 array = s.array; 816 for( ; pos < end; pos ++) 817 writeCharacter(array[pos]); 818 } 819 820 public void writeText(String s) 821 throws IOException 822 { 823 int pos, end; 824 825 pos = 0; 826 end = s.length(); 827 for( ; pos < end; pos ++) 828 writeCharacter(s.charAt(pos)); 829 } 830 831 public void writeRawString(String str) 832 throws IOException 833 { 834 int strlen = str.length(); 835 for (int offset = 0; offset < strlen; offset ++) 836 outputStream.write((int)str.charAt(offset)); 837 } 838 839 public void writeControlWord(String keyword) 840 throws IOException 841 { 842 outputStream.write('\\'); 843 writeRawString(keyword); 844 afterKeyword = true; 845 } 846 847 public void writeControlWord(String keyword, int arg) 848 throws IOException 849 { 850 outputStream.write('\\'); 851 writeRawString(keyword); 852 writeRawString(String.valueOf(arg)); /* TODO: correct in all cases? */ 853 afterKeyword = true; 854 } 855 856 public void writeBegingroup() 857 throws IOException 858 { 859 outputStream.write('{'); 860 afterKeyword = false; 861 } 862 863 public void writeEndgroup() 864 throws IOException 865 { 866 outputStream.write('}'); 867 afterKeyword = false; 868 } 869 870 @SuppressWarnings("fallthrough") 871 public void writeCharacter(char ch) 872 throws IOException 873 { 874 /* Nonbreaking space is in most RTF encodings, but the keyword is 875 preferable; same goes for tabs */ 876 if (ch == 0xA0) { /* nonbreaking space */ 877 outputStream.write(0x5C); /* backslash */ 878 outputStream.write(0x7E); /* tilde */ 879 afterKeyword = false; /* non-alpha keywords are self-terminating */ 880 return; 881 } 882 883 if (ch == 0x09) { /* horizontal tab */ 884 writeControlWord("tab"); 885 return; 886 } 887 888 if (ch == 10 || ch == 13) { /* newline / paragraph */ 889 /* ignore CRs, we'll write a paragraph element soon enough */ 890 return; 891 } 892 893 int b = convertCharacter(outputConversion, ch); 894 if (b == 0) { 895 /* Unicode characters which have corresponding RTF keywords */ 896 int i; 897 for(i = 0; i < textKeywords.length; i++) { 898 if (textKeywords[i].character == ch) { 899 writeControlWord(textKeywords[i].keyword); 900 return; 901 } 902 } 903 /* In some cases it would be reasonable to check to see if the 904 glyph being written out is in the Symbol encoding, and if so, 905 to switch to the Symbol font for this character. TODO. */ 906 /* Currently all unrepresentable characters are written as 907 Unicode escapes. */ 908 String approximation = approximationForUnicode(ch); 909 if (approximation.length() != unicodeCount) { 910 unicodeCount = approximation.length(); 911 writeControlWord("uc", unicodeCount); 912 } 913 writeControlWord("u", (int)ch); 914 writeRawString(" "); 915 writeRawString(approximation); 916 afterKeyword = false; 917 return; 918 } 919 920 if (b > 127) { 921 int nybble; 922 outputStream.write('\\'); 923 outputStream.write('\''); 924 nybble = ( b & 0xF0 ) >>> 4; 925 outputStream.write(hexdigits[nybble]); 926 nybble = ( b & 0x0F ); 927 outputStream.write(hexdigits[nybble]); 928 afterKeyword = false; 929 return; 930 } 931 932 switch (b) { 933 case '}': 934 case '{': 935 case '\\': 936 outputStream.write(0x5C); /* backslash */ 937 afterKeyword = false; /* in a keyword, actually ... */ 938 /* fall through */ 939 default: 940 if (afterKeyword) { 941 outputStream.write(0x20); /* space */ 942 afterKeyword = false; 943 } 944 outputStream.write(b); 945 break; 946 } 947 } 948 949 String approximationForUnicode(char ch) 950 { 951 /* TODO: Find reasonable approximations for all Unicode characters 952 in all RTF code pages... heh, heh... */ 953 return "?"; 954 } 955 956 /** Takes a translation table (a 256-element array of characters) 957 * and creates an output conversion table for use by 958 * convertCharacter(). */ 959 /* Not very efficient at all. Could be changed to sort the table 960 for binary search. TODO. (Even though this is inefficient however, 961 writing RTF is still much faster than reading it.) */ 962 static int[] outputConversionFromTranslationTable(char[] table) 963 { 964 int[] conversion = new int[2 * table.length]; 965 966 int index; 967 968 for(index = 0; index < table.length; index ++) { 969 conversion[index * 2] = table[index]; 970 conversion[(index * 2) + 1] = index; 971 } 972 973 return conversion; 974 } 975 976 static int[] outputConversionForName(String name) 977 throws IOException 978 { 979 char[] table = (char[])RTFReader.getCharacterSet(name); 980 return outputConversionFromTranslationTable(table); 981 } 982 983 /** Takes a char and a conversion table (an int[] in the current 984 * implementation, but conversion tables should be treated as an opaque 985 * type) and returns the 986 * corresponding byte value (as an int, since bytes are signed). 987 */ 988 /* Not very efficient. TODO. */ 989 protected static int convertCharacter(int[] conversion, char ch) 990 { 991 int index; 992 993 for(index = 0; index < conversion.length; index += 2) { 994 if(conversion[index] == ch) 995 return conversion[index + 1]; 996 } 997 998 return 0; /* 0 indicates an unrepresentable character */ 999 } 1000 1001 }