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
writeDocument(Document d, OutputStream to)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
RTFGenerator(OutputStream to)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
examineElement(Element el)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
tallyStyles(AttributeSet a)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
findStyle(AttributeSet a)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
findStyleNumber(AttributeSet a, String domain)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
attrDiff(MutableAttributeSet oldAttrs, AttributeSet newAttrs, Object key, Object dfl)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
equalArraysOK(Object a, Object b)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 */
writeLineBreak()295 public void writeLineBreak()
296 throws IOException
297 {
298 writeRawString("\n");
299 afterKeyword = false;
300 }
301
302
writeRTFHeader()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
writeDocumentProperties(Document doc)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
writeRTFTrailer()456 public void writeRTFTrailer()
457 throws IOException
458 {
459 writeEndgroup();
460 writeLineBreak();
461 }
462
checkNumericControlWord(MutableAttributeSet currentAttributes, AttributeSet newAttributes, Object attrName, String controlWord, float dflt, float scale)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
checkControlWord(MutableAttributeSet currentAttributes, AttributeSet newAttributes, RTFAttribute word)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
checkControlWords(MutableAttributeSet currentAttributes, AttributeSet newAttributes, RTFAttribute words[], int domain)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
updateSectionAttributes(MutableAttributeSet current, AttributeSet newAttributes, boolean emitStyleChanges)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
resetSectionAttributes(MutableAttributeSet currentAttributes)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
updateParagraphAttributes(MutableAttributeSet current, AttributeSet newAttributes, boolean emitStyleChanges)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
writeParagraphElement(Element el)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
resetParagraphAttributes(MutableAttributeSet currentAttributes)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
updateCharacterAttributes(MutableAttributeSet current, AttributeSet newAttributes, boolean updateStyleChanges)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
resetCharacterAttributes(MutableAttributeSet currentAttributes)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
writeTextElement(Element el)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
writeText(Segment s)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
writeText(String s)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
writeRawString(String str)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
writeControlWord(String keyword)840 public void writeControlWord(String keyword)
841 throws IOException
842 {
843 outputStream.write('\\');
844 writeRawString(keyword);
845 afterKeyword = true;
846 }
847
writeControlWord(String keyword, int arg)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
writeBegingroup()857 public void writeBegingroup()
858 throws IOException
859 {
860 outputStream.write('{');
861 afterKeyword = false;
862 }
863
writeEndgroup()864 public void writeEndgroup()
865 throws IOException
866 {
867 outputStream.write('}');
868 afterKeyword = false;
869 }
870
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 static protected 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