home *** CD-ROM | disk | FTP | other *** search
/ Chip 1998 November / Chip_1998-11_cd.bin / tema / Cafe / jfc.bin / RTFGenerator.java < prev    next >
Text File  |  1998-02-26  |  28KB  |  1,006 lines

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