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