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
writeDocument(Document d, OutputStream to)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
RTFGenerator(OutputStream to)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
examineElement(Element el)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
tallyStyles(AttributeSet a)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
findStyle(AttributeSet a)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
findStyleNumber(AttributeSet a, String domain)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
attrDiff(MutableAttributeSet oldAttrs, AttributeSet newAttrs, Object key, Object dfl)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
equalArraysOK(Object a, Object b)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 */
writeLineBreak()291 public void writeLineBreak()
292 throws IOException
293 {
294 writeRawString("\n");
295 afterKeyword = false;
296 }
297
298
writeRTFHeader()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
writeDocumentProperties(Document doc)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
writeRTFTrailer()452 public void writeRTFTrailer()
453 throws IOException
454 {
455 writeEndgroup();
456 writeLineBreak();
457 }
458
checkNumericControlWord(MutableAttributeSet currentAttributes, AttributeSet newAttributes, Object attrName, String controlWord, float dflt, float scale)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
checkControlWord(MutableAttributeSet currentAttributes, AttributeSet newAttributes, RTFAttribute word)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
checkControlWords(MutableAttributeSet currentAttributes, AttributeSet newAttributes, RTFAttribute words[], int domain)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
updateSectionAttributes(MutableAttributeSet current, AttributeSet newAttributes, boolean emitStyleChanges)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
resetSectionAttributes(MutableAttributeSet currentAttributes)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
updateParagraphAttributes(MutableAttributeSet current, AttributeSet newAttributes, boolean emitStyleChanges)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
writeParagraphElement(Element el)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
resetParagraphAttributes(MutableAttributeSet currentAttributes)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
updateCharacterAttributes(MutableAttributeSet current, AttributeSet newAttributes, boolean updateStyleChanges)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
resetCharacterAttributes(MutableAttributeSet currentAttributes)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
writeTextElement(Element el)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
writeText(Segment s)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
writeText(String s)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
writeRawString(String str)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
writeControlWord(String keyword)839 public void writeControlWord(String keyword)
840 throws IOException
841 {
842 outputStream.write('\\');
843 writeRawString(keyword);
844 afterKeyword = true;
845 }
846
writeControlWord(String keyword, int arg)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
writeBegingroup()856 public void writeBegingroup()
857 throws IOException
858 {
859 outputStream.write('{');
860 afterKeyword = false;
861 }
862
writeEndgroup()863 public void writeEndgroup()
864 throws IOException
865 {
866 outputStream.write('}');
867 afterKeyword = false;
868 }
869
870 @SuppressWarnings("fallthrough")
writeCharacter(char ch)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
approximationForUnicode(char ch)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.) */
outputConversionFromTranslationTable(char[] table)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
outputConversionForName(String name)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. */
convertCharacter(int[] conversion, char ch)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 }
1002