- /*
- * @(#)RTFGenerator.java 1.9 00/02/02
- *
- * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
- *
- * This software is the proprietary information of Sun Microsystems, Inc.
- * Use is subject to license terms.
- *
- */
- package javax.swing.text.rtf;
-
- import java.lang.*;
- import java.util.*;
- import java.awt.Color;
- import java.awt.Font;
- import java.io.OutputStream;
- import java.io.IOException;
-
- import javax.swing.text.*;
-
- /**
- * Generates an RTF output stream (java.io.OutputStream) from rich text
- * (handed off through a series of LTTextAcceptor calls). Can be used to
- * generate RTF from any object which knows how to write to a text acceptor
- * (e.g., LTAttributedText and LTRTFFilter).
- *
- * <p>Note that this is a lossy conversion since RTF's model of
- * text does not exactly correspond with LightText's.
- *
- * @see LTAttributedText
- * @see LTRTFFilter
- * @see LTTextAcceptor
- * @see java.io.OutputStream
- */
-
- class RTFGenerator extends Object
- {
- /* These dictionaries map Colors, font names, or Style objects
- to Integers */
- Dictionary colorTable;
- int colorCount;
- Dictionary fontTable;
- int fontCount;
- Dictionary styleTable;
- int styleCount;
-
- /* where all the text is going */
- OutputStream outputStream;
-
- boolean afterKeyword;
-
- MutableAttributeSet outputAttributes;
-
- /* the value of the last \\ucN keyword emitted */
- int unicodeCount;
-
- /* for efficiency's sake (ha) */
- private Segment workingSegment;
-
- int[] outputConversion;
-
- /** The default color, used for text without an explicit color
- * attribute. */
- static public final Color defaultRTFColor = Color.black;
-
- static public final float defaultFontSize = 12f;
-
- static public final String defaultFontFamily = "Helvetica";
-
- /* constants so we can avoid allocating objects in inner loops */
- /* these should all be final, but javac seems to be a bit buggy */
- static protected Integer One, Zero;
- static protected Boolean False;
- static protected Float ZeroPointZero;
- static private Object MagicToken;
-
- /* An array of character-keyword pairs. This could be done
- as a dictionary (and lookup would be quicker), but that
- would require allocating an object for every character
- written (slow!). */
- static class CharacterKeywordPair
- { public char character; public String keyword; };
- static protected CharacterKeywordPair[] textKeywords;
-
- static {
- One = new Integer(1);
- Zero = new Integer(0);
- False = new Boolean(false);
- MagicToken = new Object();
- ZeroPointZero = new Float(0);
-
- Dictionary textKeywordDictionary = RTFReader.textKeywords;
- Enumeration keys = textKeywordDictionary.keys();
- Vector tempPairs = new Vector();
- while(keys.hasMoreElements()) {
- CharacterKeywordPair pair = new CharacterKeywordPair();
- pair.keyword = (String)keys.nextElement();
- pair.character = ((String)textKeywordDictionary.get(pair.keyword)).charAt(0);
- tempPairs.addElement(pair);
- }
- textKeywords = new CharacterKeywordPair[tempPairs.size()];
- tempPairs.copyInto(textKeywords);
- }
-
- static final char[] hexdigits = { '0', '1', '2', '3', '4', '5', '6', '7',
- '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
-
- static public void writeDocument(Document d, OutputStream to)
- throws IOException
- {
- RTFGenerator gen = new RTFGenerator(to);
- Element root = d.getDefaultRootElement();
-
- gen.examineElement(root);
- gen.writeRTFHeader();
- gen.writeDocumentProperties(d);
-
- /* TODO this assumes a particular element structure; is there
- a way to iterate more generically ? */
- int max = root.getElementCount();
- for(int idx = 0; idx < max; idx++)
- gen.writeParagraphElement(root.getElement(idx));
-
- gen.writeRTFTrailer();
- }
-
- public RTFGenerator(OutputStream to)
- {
- colorTable = new Hashtable();
- colorTable.put(defaultRTFColor, new Integer(0));
- colorCount = 1;
-
- fontTable = new Hashtable();
- fontCount = 0;
-
- styleTable = new Hashtable();
- /* TODO: put default style in style table */
- styleCount = 0;
-
- workingSegment = new Segment();
-
- outputStream = to;
-
- unicodeCount = 1;
- }
-
- public void examineElement(Element el)
- {
- AttributeSet a = el.getAttributes();
- String fontName;
- Object foregroundColor, backgroundColor;
-
- tallyStyles(a);
-
- if (a != null) {
- /* TODO: default color must be color 0! */
-
- foregroundColor = StyleConstants.getForeground(a);
- if (foregroundColor != null &&
- colorTable.get(foregroundColor) == null) {
- colorTable.put(foregroundColor, new Integer(colorCount));
- colorCount ++;
- }
-
- backgroundColor = a.getAttribute(StyleConstants.Background);
- if (backgroundColor != null &&
- colorTable.get(backgroundColor) == null) {
- colorTable.put(backgroundColor, new Integer(colorCount));
- colorCount ++;
- }
-
- fontName = StyleConstants.getFontFamily(a);
-
- if (fontName == null)
- fontName = defaultFontFamily;
-
- if (fontName != null &&
- fontTable.get(fontName) == null) {
- fontTable.put(fontName, new Integer(fontCount));
- fontCount ++;
- }
- }
-
- int el_count = el.getElementCount();
- for(int el_idx = 0; el_idx < el_count; el_idx ++) {
- examineElement(el.getElement(el_idx));
- }
- }
-
- private void tallyStyles(AttributeSet a) {
- while (a != null) {
- if (a instanceof Style) {
- Integer aNum = (Integer)styleTable.get(a);
- if (aNum == null) {
- styleCount = styleCount + 1;
- aNum = new Integer(styleCount);
- styleTable.put(a, aNum);
- }
- }
- a = a.getResolveParent();
- }
- }
-
- private Style findStyle(AttributeSet a)
- {
- while(a != null) {
- if (a instanceof Style) {
- Object aNum = styleTable.get(a);
- if (aNum != null)
- return (Style)a;
- }
- a = a.getResolveParent();
- }
- return null;
- }
-
- private Integer findStyleNumber(AttributeSet a, String domain)
- {
- while(a != null) {
- if (a instanceof Style) {
- Integer aNum = (Integer)styleTable.get(a);
- if (aNum != null) {
- if (domain == null ||
- domain.equals(a.getAttribute(Constants.StyleType)))
- return aNum;
- }
-
- }
- a = a.getResolveParent();
- }
- return null;
- }
-
- static private Object attrDiff(MutableAttributeSet oldAttrs,
- AttributeSet newAttrs,
- Object key,
- Object dfl)
- {
- Object oldValue, newValue;
-
- oldValue = oldAttrs.getAttribute(key);
- newValue = newAttrs.getAttribute(key);
-
- if (newValue == oldValue)
- return null;
- if (newValue == null) {
- oldAttrs.removeAttribute(key);
- if (dfl != null && !dfl.equals(oldValue))
- return dfl;
- else
- return null;
- }
- if (oldValue == null ||
- !equalArraysOK(oldValue, newValue)) {
- oldAttrs.addAttribute(key, newValue);
- return newValue;
- }
- return null;
- }
-
- static private boolean equalArraysOK(Object a, Object b)
- {
- Object[] aa, bb;
- if (a == b)
- return true;
- if (a == null || b == null)
- return false;
- if (a.equals(b))
- return true;
- if (!(a.getClass().isArray() && b.getClass().isArray()))
- return false;
- aa = (Object[])a;
- bb = (Object[])b;
- if (aa.length != bb.length)
- return false;
-
- int i;
- int l = aa.length;
- for(i = 0; i < l; i++) {
- if (!equalArraysOK(aa[i], bb[i]))
- return false;
- }
-
- return true;
- }
-
- /* Writes a line break to the output file, for ease in debugging */
- public void writeLineBreak()
- throws IOException
- {
- writeRawString("\n");
- afterKeyword = false;
- }
-
-
- public void writeRTFHeader()
- throws IOException
- {
- int index;
-
- /* TODO: Should the writer attempt to examine the text it's writing
- and pick a character set which will most compactly represent the
- document? (currently the writer always uses the ansi character
- set, which is roughly ISO-8859 Latin-1, and uses Unicode escapes
- for all other characters. However Unicode is a relatively
- recent addition to RTF, and not all readers will understand it.) */
- writeBegingroup();
- writeControlWord("rtf", 1);
- writeControlWord("ansi");
- outputConversion = outputConversionForName("ansi");
- writeLineBreak();
-
- /* write font table */
- String[] sortedFontTable = new String[fontCount];
- Enumeration fonts = fontTable.keys();
- String font;
- while(fonts.hasMoreElements()) {
- font = (String)fonts.nextElement();
- Integer num = (Integer)(fontTable.get(font));
- sortedFontTable[num.intValue()] = font;
- }
- writeBegingroup();
- writeControlWord("fonttbl");
- for(index = 0; index < fontCount; index ++) {
- writeControlWord("f", index);
- writeControlWord("fnil"); /* TODO: supply correct font style */
- writeText(sortedFontTable[index]);
- writeText(";");
- }
- writeEndgroup();
- writeLineBreak();
-
- /* write color table */
- if (colorCount > 1) {
- Color[] sortedColorTable = new Color[colorCount];
- Enumeration colors = colorTable.keys();
- Color color;
- while(colors.hasMoreElements()) {
- color = (Color)colors.nextElement();
- Integer num = (Integer)(colorTable.get(color));
- sortedColorTable[num.intValue()] = color;
- }
- writeBegingroup();
- writeControlWord("colortbl");
- for(index = 0; index < colorCount; index ++) {
- color = sortedColorTable[index];
- if (color != null) {
- writeControlWord("red", color.getRed());
- writeControlWord("green", color.getGreen());
- writeControlWord("blue", color.getBlue());
- }
- writeRawString(";");
- }
- writeEndgroup();
- writeLineBreak();
- }
-
- /* write the style sheet */
- if (styleCount > 1) {
- writeBegingroup();
- writeControlWord("stylesheet");
- Enumeration styles = styleTable.keys();
- while(styles.hasMoreElements()) {
- Style style = (Style)styles.nextElement();
- int styleNumber = ((Integer)styleTable.get(style)).intValue();
- writeBegingroup();
- String styleType = (String)style.getAttribute(Constants.StyleType);
- if (styleType == null)
- styleType = Constants.STParagraph;
- if (styleType.equals(Constants.STCharacter)) {
- writeControlWord("*");
- writeControlWord("cs", styleNumber);
- } else if(styleType.equals(Constants.STSection)) {
- writeControlWord("*");
- writeControlWord("ds", styleNumber);
- } else {
- writeControlWord("s", styleNumber);
- }
-
- AttributeSet basis = style.getResolveParent();
- MutableAttributeSet goat;
- if (basis == null) {
- goat = new SimpleAttributeSet();
- } else {
- goat = new SimpleAttributeSet(basis);
- }
-
- updateSectionAttributes(goat, style, false);
- updateParagraphAttributes(goat, style, false);
- updateCharacterAttributes(goat, style, false);
-
- basis = style.getResolveParent();
- if (basis != null && basis instanceof Style) {
- Integer basedOn = (Integer)styleTable.get(basis);
- if (basedOn != null) {
- writeControlWord("sbasedon", basedOn.intValue());
- }
- }
-
- Style nextStyle = (Style)style.getAttribute(Constants.StyleNext);
- if (nextStyle != null) {
- Integer nextNum = (Integer)styleTable.get(nextStyle);
- if (nextNum != null) {
- writeControlWord("snext", nextNum.intValue());
- }
- }
-
- Boolean hidden = (Boolean)style.getAttribute(Constants.StyleHidden);
- if (hidden != null && hidden.booleanValue())
- writeControlWord("shidden");
-
- Boolean additive = (Boolean)style.getAttribute(Constants.StyleAdditive);
- if (additive != null && additive.booleanValue())
- writeControlWord("additive");
-
-
- writeText(style.getName());
- writeText(";");
- writeEndgroup();
- }
- writeEndgroup();
- writeLineBreak();
- }
-
- outputAttributes = new SimpleAttributeSet();
- }
-
- void writeDocumentProperties(Document doc)
- throws IOException
- {
- /* Write the document properties */
- int i;
- boolean wroteSomething = false;
-
- for(i = 0; i < RTFAttributes.attributes.length; i++) {
- RTFAttribute attr = RTFAttributes.attributes[i];
- if (attr.domain() != RTFAttribute.D_DOCUMENT)
- continue;
- Object prop = doc.getProperty(attr.swingName());
- boolean ok = attr.writeValue(prop, this, false);
- if (ok)
- wroteSomething = true;
- }
-
- if (wroteSomething)
- writeLineBreak();
- }
-
- public void writeRTFTrailer()
- throws IOException
- {
- writeEndgroup();
- writeLineBreak();
- }
-
- protected void checkNumericControlWord(MutableAttributeSet currentAttributes,
- AttributeSet newAttributes,
- Object attrName,
- String controlWord,
- float dflt, float scale)
- throws IOException
- {
- Object parm;
-
- if ((parm = attrDiff(currentAttributes, newAttributes,
- attrName, MagicToken)) != null) {
- float targ;
- if (parm == MagicToken)
- targ = dflt;
- else
- targ = ((Number)parm).floatValue();
- writeControlWord(controlWord, Math.round(targ * scale));
- }
- }
-
- protected void checkControlWord(MutableAttributeSet currentAttributes,
- AttributeSet newAttributes,
- RTFAttribute word)
- throws IOException
- {
- Object parm;
-
- if ((parm = attrDiff(currentAttributes, newAttributes,
- word.swingName(), MagicToken)) != null) {
- if (parm == MagicToken)
- parm = null;
- word.writeValue(parm, this, true);
- }
- }
-
- protected void checkControlWords(MutableAttributeSet currentAttributes,
- AttributeSet newAttributes,
- RTFAttribute words[],
- int domain)
- throws IOException
- {
- int wordIndex;
- int wordCount = words.length;
- for(wordIndex = 0; wordIndex < wordCount; wordIndex++) {
- RTFAttribute attr = words[wordIndex];
- if (attr.domain() == domain)
- checkControlWord(currentAttributes, newAttributes, attr);
- }
- }
-
- void updateSectionAttributes(MutableAttributeSet current,
- AttributeSet newAttributes,
- boolean emitStyleChanges)
- throws IOException
- {
- if (emitStyleChanges) {
- Object oldStyle = current.getAttribute("sectionStyle");
- Object newStyle = findStyleNumber(newAttributes, Constants.STSection);
- if (oldStyle != newStyle) {
- if (oldStyle != null) {
- resetSectionAttributes(current);
- }
- if (newStyle != null) {
- writeControlWord("ds", ((Integer)newStyle).intValue());
- current.addAttribute("sectionStyle", newStyle);
- } else {
- current.removeAttribute("sectionStyle");
- }
- }
- }
-
- checkControlWords(current, newAttributes,
- RTFAttributes.attributes, RTFAttribute.D_SECTION);
- }
-
- protected void resetSectionAttributes(MutableAttributeSet currentAttributes)
- throws IOException
- {
- writeControlWord("sectd");
-
- int wordIndex;
- int wordCount = RTFAttributes.attributes.length;
- for(wordIndex = 0; wordIndex < wordCount; wordIndex++) {
- RTFAttribute attr = RTFAttributes.attributes[wordIndex];
- if (attr.domain() == RTFAttribute.D_SECTION)
- attr.setDefault(currentAttributes);
- }
-
- currentAttributes.removeAttribute("sectionStyle");
- }
-
- void updateParagraphAttributes(MutableAttributeSet current,
- AttributeSet newAttributes,
- boolean emitStyleChanges)
- throws IOException
- {
- Object parm;
- Object oldStyle, newStyle;
-
- /* The only way to get rid of tabs or styles is with the \pard keyword,
- emitted by resetParagraphAttributes(). Ideally we should avoid
- emitting \pard if the new paragraph's tabs are a superset of the old
- paragraph's tabs. */
-
- if (emitStyleChanges) {
- oldStyle = current.getAttribute("paragraphStyle");
- newStyle = findStyleNumber(newAttributes, Constants.STParagraph);
- if (oldStyle != newStyle) {
- if (oldStyle != null) {
- resetParagraphAttributes(current);
- oldStyle = null;
- }
- }
- } else {
- oldStyle = null;
- newStyle = null;
- }
-
- Object oldTabs = current.getAttribute(Constants.Tabs);
- Object newTabs = newAttributes.getAttribute(Constants.Tabs);
- if (oldTabs != newTabs) {
- if (oldTabs != null) {
- resetParagraphAttributes(current);
- oldTabs = null;
- oldStyle = null;
- }
- }
-
- if (oldStyle != newStyle && newStyle != null) {
- writeControlWord("s", ((Integer)newStyle).intValue());
- current.addAttribute("paragraphStyle", newStyle);
- }
-
- checkControlWords(current, newAttributes,
- RTFAttributes.attributes, RTFAttribute.D_PARAGRAPH);
-
- if (oldTabs != newTabs && newTabs != null) {
- TabStop tabs[] = (TabStop[])newTabs;
- int index;
- for(index = 0; index < tabs.length; index ++) {
- TabStop tab = tabs[index];
- switch (tab.getAlignment()) {
- case TabStop.ALIGN_LEFT:
- case TabStop.ALIGN_BAR:
- break;
- case TabStop.ALIGN_RIGHT:
- writeControlWord("tqr");
- break;
- case TabStop.ALIGN_CENTER:
- writeControlWord("tqc");
- break;
- case TabStop.ALIGN_DECIMAL:
- writeControlWord("tqdec");
- break;
- }
- switch (tab.getLeader()) {
- case TabStop.LEAD_NONE:
- break;
- case TabStop.LEAD_DOTS:
- writeControlWord("tldot");
- break;
- case TabStop.LEAD_HYPHENS:
- writeControlWord("tlhyph");
- break;
- case TabStop.LEAD_UNDERLINE:
- writeControlWord("tlul");
- break;
- case TabStop.LEAD_THICKLINE:
- writeControlWord("tlth");
- break;
- case TabStop.LEAD_EQUALS:
- writeControlWord("tleq");
- break;
- }
- int twips = Math.round(20f * tab.getPosition());
- if (tab.getAlignment() == TabStop.ALIGN_BAR) {
- writeControlWord("tb", twips);
- } else {
- writeControlWord("tx", twips);
- }
- }
- current.addAttribute(Constants.Tabs, tabs);
- }
- }
-
- public void writeParagraphElement(Element el)
- throws IOException
- {
- updateParagraphAttributes(outputAttributes, el.getAttributes(), true);
-
- int sub_count = el.getElementCount();
- for(int idx = 0; idx < sub_count; idx ++) {
- writeTextElement(el.getElement(idx));
- }
-
- writeControlWord("par");
- writeLineBreak(); /* makes the raw file more readable */
- }
-
- /* debugging. TODO: remove.
- private static String tabdump(Object tso)
- {
- String buf;
- int i;
-
- if (tso == null)
- return "[none]";
-
- TabStop[] ts = (TabStop[])tso;
-
- buf = "[";
- for(i = 0; i < ts.length; i++) {
- buf = buf + ts[i].toString();
- if ((i+1) < ts.length)
- buf = buf + ",";
- }
- return buf + "]";
- }
- */
-
- protected void resetParagraphAttributes(MutableAttributeSet currentAttributes)
- throws IOException
- {
- writeControlWord("pard");
-
- currentAttributes.addAttribute(StyleConstants.Alignment, Zero);
-
- int wordIndex;
- int wordCount = RTFAttributes.attributes.length;
- for(wordIndex = 0; wordIndex < wordCount; wordIndex++) {
- RTFAttribute attr = RTFAttributes.attributes[wordIndex];
- if (attr.domain() == RTFAttribute.D_PARAGRAPH)
- attr.setDefault(currentAttributes);
- }
-
- currentAttributes.removeAttribute("paragraphStyle");
- currentAttributes.removeAttribute(Constants.Tabs);
- }
-
- void updateCharacterAttributes(MutableAttributeSet current,
- AttributeSet newAttributes,
- boolean updateStyleChanges)
- throws IOException
- {
- Object parm;
-
- if (updateStyleChanges) {
- Object oldStyle = current.getAttribute("characterStyle");
- Object newStyle = findStyleNumber(newAttributes,
- Constants.STCharacter);
- if (oldStyle != newStyle) {
- if (oldStyle != null) {
- resetCharacterAttributes(current);
- }
- if (newStyle != null) {
- writeControlWord("cs", ((Integer)newStyle).intValue());
- current.addAttribute("characterStyle", newStyle);
- } else {
- current.removeAttribute("characterStyle");
- }
- }
- }
-
- if ((parm = attrDiff(current, newAttributes,
- StyleConstants.FontFamily, null)) != null) {
- Number fontNum = (Number)fontTable.get(parm);
- writeControlWord("f", fontNum.intValue());
- }
-
- checkNumericControlWord(current, newAttributes,
- StyleConstants.FontSize, "fs",
- defaultFontSize, 2f);
-
- checkControlWords(current, newAttributes,
- RTFAttributes.attributes, RTFAttribute.D_CHARACTER);
-
- checkNumericControlWord(current, newAttributes,
- StyleConstants.LineSpacing, "sl",
- 0, 20f); /* TODO: sl wackiness */
-
- if ((parm = attrDiff(current, newAttributes,
- StyleConstants.Background, MagicToken)) != null) {
- int colorNum;
- if (parm == MagicToken)
- colorNum = 0;
- else
- colorNum = ((Number)colorTable.get(parm)).intValue();
- writeControlWord("cb", colorNum);
- }
-
- if ((parm = attrDiff(current, newAttributes,
- StyleConstants.Foreground, null)) != null) {
- int colorNum;
- if (parm == MagicToken)
- colorNum = 0;
- else
- colorNum = ((Number)colorTable.get(parm)).intValue();
- writeControlWord("cf", colorNum);
- }
- }
-
- protected void resetCharacterAttributes(MutableAttributeSet currentAttributes)
- throws IOException
- {
- writeControlWord("plain");
-
- int wordIndex;
- int wordCount = RTFAttributes.attributes.length;
- for(wordIndex = 0; wordIndex < wordCount; wordIndex++) {
- RTFAttribute attr = RTFAttributes.attributes[wordIndex];
- if (attr.domain() == RTFAttribute.D_CHARACTER)
- attr.setDefault(currentAttributes);
- }
-
- StyleConstants.setFontFamily(currentAttributes, defaultFontFamily);
- currentAttributes.removeAttribute(StyleConstants.FontSize); /* =default */
- currentAttributes.removeAttribute(StyleConstants.Background);
- currentAttributes.removeAttribute(StyleConstants.Foreground);
- currentAttributes.removeAttribute(StyleConstants.LineSpacing);
- currentAttributes.removeAttribute("characterStyle");
- }
-
- public void writeTextElement(Element el)
- throws IOException
- {
- updateCharacterAttributes(outputAttributes, el.getAttributes(), true);
-
- if (el.isLeaf()) {
- try {
- el.getDocument().getText(el.getStartOffset(),
- el.getEndOffset() - el.getStartOffset(),
- this.workingSegment);
- } catch (BadLocationException ble) {
- /* TODO is this the correct error to raise? */
- ble.printStackTrace();
- throw new InternalError(ble.getMessage());
- }
- writeText(this.workingSegment);
- } else {
- int sub_count = el.getElementCount();
- for(int idx = 0; idx < sub_count; idx ++)
- writeTextElement(el.getElement(idx));
- }
- }
-
- public void writeText(Segment s)
- throws IOException
- {
- int pos, end;
- char[] array;
-
- pos = s.offset;
- end = pos + s.count;
- array = s.array;
- for( ; pos < end; pos ++)
- writeCharacter(array[pos]);
- }
-
- public void writeText(String s)
- throws IOException
- {
- int pos, end;
-
- pos = 0;
- end = s.length();
- for( ; pos < end; pos ++)
- writeCharacter(s.charAt(pos));
- }
-
- public void writeRawString(String str)
- throws IOException
- {
- int strlen = str.length();
- for (int offset = 0; offset < strlen; offset ++)
- outputStream.write((int)str.charAt(offset));
- }
-
- public void writeControlWord(String keyword)
- throws IOException
- {
- outputStream.write('\\');
- writeRawString(keyword);
- afterKeyword = true;
- }
-
- public void writeControlWord(String keyword, int arg)
- throws IOException
- {
- outputStream.write('\\');
- writeRawString(keyword);
- writeRawString(String.valueOf(arg)); /* TODO: correct in all cases? */
- afterKeyword = true;
- }
-
- public void writeBegingroup()
- throws IOException
- {
- outputStream.write('{');
- afterKeyword = false;
- }
-
- public void writeEndgroup()
- throws IOException
- {
- outputStream.write('}');
- afterKeyword = false;
- }
-
- public void writeCharacter(char ch)
- throws IOException
- {
- /* Nonbreaking space is in most RTF encodings, but the keyword is
- preferable; same goes for tabs */
- if (ch == 0xA0) { /* nonbreaking space */
- outputStream.write(0x5C); /* backslash */
- outputStream.write(0x7E); /* tilde */
- afterKeyword = false; /* non-alpha keywords are self-terminating */
- return;
- }
-
- if (ch == 0x09) { /* horizontal tab */
- writeControlWord("tab");
- return;
- }
-
- if (ch == 10 || ch == 13) { /* newline / paragraph */
- /* ignore CRs, we'll write a paragraph element soon enough */
- return;
- }
-
- int b = convertCharacter(outputConversion, ch);
- if (b == 0) {
- /* Unicode characters which have corresponding RTF keywords */
- int i;
- for(i = 0; i < textKeywords.length; i++) {
- if (textKeywords[i].character == ch) {
- writeControlWord(textKeywords[i].keyword);
- return;
- }
- }
- /* In some cases it would be reasonable to check to see if the
- glyph being written out is in the Symbol encoding, and if so,
- to switch to the Symbol font for this character. TODO. */
- /* Currently all unrepresentable characters are written as
- Unicode escapes. */
- String approximation = approximationForUnicode(ch);
- if (approximation.length() != unicodeCount) {
- unicodeCount = approximation.length();
- writeControlWord("uc", unicodeCount);
- }
- writeControlWord("u", (int)ch);
- writeRawString(" ");
- writeRawString(approximation);
- afterKeyword = false;
- return;
- }
-
- if (b > 127) {
- int nybble;
- outputStream.write('\\');
- outputStream.write('\'');
- nybble = ( b & 0xF0 ) >>> 4;
- outputStream.write(hexdigits[nybble]);
- nybble = ( b & 0x0F );
- outputStream.write(hexdigits[nybble]);
- afterKeyword = false;
- return;
- }
-
- switch (b) {
- case '}':
- case '{':
- case '\\':
- outputStream.write(0x5C); /* backslash */
- afterKeyword = false; /* in a keyword, actually ... */
- /* fall through */
- default:
- if (afterKeyword) {
- outputStream.write(0x20); /* space */
- afterKeyword = false;
- }
- outputStream.write(b);
- break;
- }
- }
-
- String approximationForUnicode(char ch)
- {
- /* TODO: Find reasonable approximations for all Unicode characters
- in all RTF code pages... heh, heh... */
- return "?";
- }
-
- /** Takes a translation table (a 256-element array of characters)
- * and creates an output conversion table for use by
- * convertCharacter(). */
- /* Not very efficient at all. Could be changed to sort the table
- for binary search. TODO. (Even though this is inefficient however,
- writing RTF is still much faster than reading it.) */
- static int[] outputConversionFromTranslationTable(char[] table)
- {
- int[] conversion = new int[2 * table.length];
-
- int index;
-
- for(index = 0; index < table.length; index ++) {
- conversion[index * 2] = table[index];
- conversion[(index * 2) + 1] = index;
- }
-
- return conversion;
- }
-
- static int[] outputConversionForName(String name)
- throws IOException
- {
- char[] table = (char[])RTFReader.getCharacterSet(name);
- return outputConversionFromTranslationTable(table);
- }
-
- /** Takes a char and a conversion table (an int[] in the current
- * implementation, but conversion tables should be treated as an opaque
- * type) and returns the
- * corresponding byte value (as an int, since bytes are signed).
- */
- /* Not very efficient. TODO. */
- static protected int convertCharacter(int[] conversion, char ch)
- {
- int index;
-
- for(index = 0; index < conversion.length; index += 2) {
- if(conversion[index] == ch)
- return conversion[index + 1];
- }
-
- return 0; /* 0 indicates an unrepresentable character */
- }
-
- }