Home » openjdk-7 » javax » swing » text » rtf » [javadoc | source]

    1   /*
    2    * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Oracle designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Oracle in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   22    * or visit www.oracle.com if you need additional information or have any
   23    * questions.
   24    */
   25   package javax.swing.text.rtf;
   26   
   27   import java.lang;
   28   import java.util;
   29   import java.awt.Color;
   30   import java.awt.Font;
   31   import java.io.OutputStream;
   32   import java.io.IOException;
   33   
   34   import javax.swing.text;
   35   
   36   /**
   37    * Generates an RTF output stream (java.io.OutputStream) from rich text
   38    * (handed off through a series of LTTextAcceptor calls).  Can be used to
   39    * generate RTF from any object which knows how to write to a text acceptor
   40    * (e.g., LTAttributedText and LTRTFFilter).
   41    *
   42    * <p>Note that this is a lossy conversion since RTF's model of
   43    * text does not exactly correspond with LightText's.
   44    *
   45    * @see LTAttributedText
   46    * @see LTRTFFilter
   47    * @see LTTextAcceptor
   48    * @see java.io.OutputStream
   49    */
   50   
   51   class RTFGenerator extends Object
   52   {
   53       /* These dictionaries map Colors, font names, or Style objects
   54          to Integers */
   55       Dictionary<Object, Integer> colorTable;
   56       int colorCount;
   57       Dictionary<String, Integer> fontTable;
   58       int fontCount;
   59       Dictionary<AttributeSet, Integer> styleTable;
   60       int styleCount;
   61   
   62       /* where all the text is going */
   63       OutputStream outputStream;
   64   
   65       boolean afterKeyword;
   66   
   67       MutableAttributeSet outputAttributes;
   68   
   69       /* the value of the last \\ucN keyword emitted */
   70       int unicodeCount;
   71   
   72       /* for efficiency's sake (ha) */
   73       private Segment workingSegment;
   74   
   75       int[] outputConversion;
   76   
   77       /** The default color, used for text without an explicit color
   78        *  attribute. */
   79       static public final Color defaultRTFColor = Color.black;
   80   
   81       static public final float defaultFontSize = 12f;
   82   
   83       static public final String defaultFontFamily = "Helvetica";
   84   
   85       /* constants so we can avoid allocating objects in inner loops */
   86       final static private Object MagicToken;
   87   
   88       /* An array of character-keyword pairs. This could be done
   89          as a dictionary (and lookup would be quicker), but that
   90          would require allocating an object for every character
   91          written (slow!). */
   92       static class CharacterKeywordPair
   93         { public char character; public String keyword; }
   94       static protected CharacterKeywordPair[] textKeywords;
   95   
   96       static {
   97           MagicToken = new Object();
   98   
   99           Dictionary textKeywordDictionary = RTFReader.textKeywords;
  100           Enumeration keys = textKeywordDictionary.keys();
  101           Vector<CharacterKeywordPair> tempPairs = new Vector<CharacterKeywordPair>();
  102           while(keys.hasMoreElements()) {
  103               CharacterKeywordPair pair = new CharacterKeywordPair();
  104               pair.keyword = (String)keys.nextElement();
  105               pair.character = ((String)textKeywordDictionary.get(pair.keyword)).charAt(0);
  106               tempPairs.addElement(pair);
  107           }
  108           textKeywords = new CharacterKeywordPair[tempPairs.size()];
  109           tempPairs.copyInto(textKeywords);
  110       }
  111   
  112       static final char[] hexdigits = { '0', '1', '2', '3', '4', '5', '6', '7',
  113                                         '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
  114   
  115   static public void writeDocument(Document d, OutputStream to)
  116       throws IOException
  117   {
  118       RTFGenerator gen = new RTFGenerator(to);
  119       Element root = d.getDefaultRootElement();
  120   
  121       gen.examineElement(root);
  122       gen.writeRTFHeader();
  123       gen.writeDocumentProperties(d);
  124   
  125       /* TODO this assumes a particular element structure; is there
  126          a way to iterate more generically ? */
  127       int max = root.getElementCount();
  128       for(int idx = 0; idx < max; idx++)
  129           gen.writeParagraphElement(root.getElement(idx));
  130   
  131       gen.writeRTFTrailer();
  132   }
  133   
  134   public RTFGenerator(OutputStream to)
  135   {
  136       colorTable = new Hashtable<Object, Integer>();
  137       colorTable.put(defaultRTFColor, Integer.valueOf(0));
  138       colorCount = 1;
  139   
  140       fontTable = new Hashtable<String, Integer>();
  141       fontCount = 0;
  142   
  143       styleTable = new Hashtable<AttributeSet, Integer>();
  144       /* TODO: put default style in style table */
  145       styleCount = 0;
  146   
  147       workingSegment = new Segment();
  148   
  149       outputStream = to;
  150   
  151       unicodeCount = 1;
  152   }
  153   
  154   public void examineElement(Element el)
  155   {
  156       AttributeSet a = el.getAttributes();
  157       String fontName;
  158       Object foregroundColor, backgroundColor;
  159   
  160       tallyStyles(a);
  161   
  162       if (a != null) {
  163           /* TODO: default color must be color 0! */
  164   
  165           foregroundColor = StyleConstants.getForeground(a);
  166           if (foregroundColor != null &&
  167               colorTable.get(foregroundColor) == null) {
  168               colorTable.put(foregroundColor, new Integer(colorCount));
  169               colorCount ++;
  170           }
  171   
  172           backgroundColor = a.getAttribute(StyleConstants.Background);
  173           if (backgroundColor != null &&
  174               colorTable.get(backgroundColor) == null) {
  175               colorTable.put(backgroundColor, new Integer(colorCount));
  176               colorCount ++;
  177           }
  178   
  179           fontName = StyleConstants.getFontFamily(a);
  180   
  181           if (fontName == null)
  182               fontName = defaultFontFamily;
  183   
  184           if (fontName != null &&
  185               fontTable.get(fontName) == null) {
  186               fontTable.put(fontName, new Integer(fontCount));
  187               fontCount ++;
  188           }
  189       }
  190   
  191       int el_count = el.getElementCount();
  192       for(int el_idx = 0; el_idx < el_count; el_idx ++) {
  193           examineElement(el.getElement(el_idx));
  194       }
  195   }
  196   
  197   private void tallyStyles(AttributeSet a) {
  198       while (a != null) {
  199           if (a instanceof Style) {
  200               Integer aNum = styleTable.get(a);
  201               if (aNum == null) {
  202                   styleCount = styleCount + 1;
  203                   aNum = new Integer(styleCount);
  204                   styleTable.put(a, aNum);
  205               }
  206           }
  207           a = a.getResolveParent();
  208       }
  209   }
  210   
  211   private Style findStyle(AttributeSet a)
  212   {
  213       while(a != null) {
  214           if (a instanceof Style) {
  215               Object aNum = styleTable.get(a);
  216               if (aNum != null)
  217                   return (Style)a;
  218           }
  219           a = a.getResolveParent();
  220       }
  221       return null;
  222   }
  223   
  224   private Integer findStyleNumber(AttributeSet a, String domain)
  225   {
  226       while(a != null) {
  227           if (a instanceof Style) {
  228               Integer aNum = styleTable.get(a);
  229               if (aNum != null) {
  230                   if (domain == null ||
  231                       domain.equals(a.getAttribute(Constants.StyleType)))
  232                       return aNum;
  233               }
  234   
  235           }
  236           a = a.getResolveParent();
  237       }
  238       return null;
  239   }
  240   
  241   static private Object attrDiff(MutableAttributeSet oldAttrs,
  242                                  AttributeSet newAttrs,
  243                                  Object key,
  244                                  Object dfl)
  245   {
  246       Object oldValue, newValue;
  247   
  248       oldValue = oldAttrs.getAttribute(key);
  249       newValue = newAttrs.getAttribute(key);
  250   
  251       if (newValue == oldValue)
  252           return null;
  253       if (newValue == null) {
  254           oldAttrs.removeAttribute(key);
  255           if (dfl != null && !dfl.equals(oldValue))
  256               return dfl;
  257           else
  258               return null;
  259       }
  260       if (oldValue == null ||
  261           !equalArraysOK(oldValue, newValue)) {
  262           oldAttrs.addAttribute(key, newValue);
  263           return newValue;
  264       }
  265       return null;
  266   }
  267   
  268   static private boolean equalArraysOK(Object a, Object b)
  269   {
  270       Object[] aa, bb;
  271       if (a == b)
  272           return true;
  273       if (a == null || b == null)
  274           return false;
  275       if (a.equals(b))
  276           return true;
  277       if (!(a.getClass().isArray() && b.getClass().isArray()))
  278           return false;
  279       aa = (Object[])a;
  280       bb = (Object[])b;
  281       if (aa.length != bb.length)
  282           return false;
  283   
  284       int i;
  285       int l = aa.length;
  286       for(i = 0; i < l; i++) {
  287           if (!equalArraysOK(aa[i], bb[i]))
  288               return false;
  289       }
  290   
  291       return true;
  292   }
  293   
  294   /* Writes a line break to the output file, for ease in debugging */
  295   public void writeLineBreak()
  296       throws IOException
  297   {
  298       writeRawString("\n");
  299       afterKeyword = false;
  300   }
  301   
  302   
  303   public void writeRTFHeader()
  304       throws IOException
  305   {
  306       int index;
  307   
  308       /* TODO: Should the writer attempt to examine the text it's writing
  309          and pick a character set which will most compactly represent the
  310          document? (currently the writer always uses the ansi character
  311          set, which is roughly ISO-8859 Latin-1, and uses Unicode escapes
  312          for all other characters. However Unicode is a relatively
  313          recent addition to RTF, and not all readers will understand it.) */
  314       writeBegingroup();
  315       writeControlWord("rtf", 1);
  316       writeControlWord("ansi");
  317       outputConversion = outputConversionForName("ansi");
  318       writeLineBreak();
  319   
  320       /* write font table */
  321       String[] sortedFontTable = new String[fontCount];
  322       Enumeration<String> fonts = fontTable.keys();
  323       String font;
  324       while(fonts.hasMoreElements()) {
  325           font = fonts.nextElement();
  326           Integer num = fontTable.get(font);
  327           sortedFontTable[num.intValue()] = font;
  328       }
  329       writeBegingroup();
  330       writeControlWord("fonttbl");
  331       for(index = 0; index < fontCount; index ++) {
  332           writeControlWord("f", index);
  333           writeControlWord("fnil");  /* TODO: supply correct font style */
  334           writeText(sortedFontTable[index]);
  335           writeText(";");
  336       }
  337       writeEndgroup();
  338       writeLineBreak();
  339   
  340       /* write color table */
  341       if (colorCount > 1) {
  342           Color[] sortedColorTable = new Color[colorCount];
  343           Enumeration colors = colorTable.keys();
  344           Color color;
  345           while(colors.hasMoreElements()) {
  346               color = (Color)colors.nextElement();
  347               Integer num = colorTable.get(color);
  348               sortedColorTable[num.intValue()] = color;
  349           }
  350           writeBegingroup();
  351           writeControlWord("colortbl");
  352           for(index = 0; index < colorCount; index ++) {
  353               color = sortedColorTable[index];
  354               if (color != null) {
  355                   writeControlWord("red", color.getRed());
  356                   writeControlWord("green", color.getGreen());
  357                   writeControlWord("blue", color.getBlue());
  358               }
  359               writeRawString(";");
  360           }
  361           writeEndgroup();
  362           writeLineBreak();
  363       }
  364   
  365       /* write the style sheet */
  366       if (styleCount > 1) {
  367           writeBegingroup();
  368           writeControlWord("stylesheet");
  369           Enumeration<AttributeSet> styles = styleTable.keys();
  370           while(styles.hasMoreElements()) {
  371               Style style = (Style)styles.nextElement();
  372               int styleNumber = styleTable.get(style).intValue();
  373               writeBegingroup();
  374               String styleType = (String)style.getAttribute(Constants.StyleType);
  375               if (styleType == null)
  376                   styleType = Constants.STParagraph;
  377               if (styleType.equals(Constants.STCharacter)) {
  378                   writeControlWord("*");
  379                   writeControlWord("cs", styleNumber);
  380               } else if(styleType.equals(Constants.STSection)) {
  381                   writeControlWord("*");
  382                   writeControlWord("ds", styleNumber);
  383               } else {
  384                   writeControlWord("s", styleNumber);
  385               }
  386   
  387               AttributeSet basis = style.getResolveParent();
  388               MutableAttributeSet goat;
  389               if (basis == null) {
  390                   goat = new SimpleAttributeSet();
  391               } else {
  392                   goat = new SimpleAttributeSet(basis);
  393               }
  394   
  395               updateSectionAttributes(goat, style, false);
  396               updateParagraphAttributes(goat, style, false);
  397               updateCharacterAttributes(goat, style, false);
  398   
  399               basis = style.getResolveParent();
  400               if (basis != null && basis instanceof Style) {
  401                   Integer basedOn = styleTable.get(basis);
  402                   if (basedOn != null) {
  403                       writeControlWord("sbasedon", basedOn.intValue());
  404                   }
  405               }
  406   
  407               Style nextStyle = (Style)style.getAttribute(Constants.StyleNext);
  408               if (nextStyle != null) {
  409                   Integer nextNum = styleTable.get(nextStyle);
  410                   if (nextNum != null) {
  411                       writeControlWord("snext", nextNum.intValue());
  412                   }
  413               }
  414   
  415               Boolean hidden = (Boolean)style.getAttribute(Constants.StyleHidden);
  416               if (hidden != null && hidden.booleanValue())
  417                   writeControlWord("shidden");
  418   
  419               Boolean additive = (Boolean)style.getAttribute(Constants.StyleAdditive);
  420               if (additive != null && additive.booleanValue())
  421                   writeControlWord("additive");
  422   
  423   
  424               writeText(style.getName());
  425               writeText(";");
  426               writeEndgroup();
  427           }
  428           writeEndgroup();
  429           writeLineBreak();
  430       }
  431   
  432       outputAttributes = new SimpleAttributeSet();
  433   }
  434   
  435   void writeDocumentProperties(Document doc)
  436       throws IOException
  437   {
  438       /* Write the document properties */
  439       int i;
  440       boolean wroteSomething = false;
  441   
  442       for(i = 0; i < RTFAttributes.attributes.length; i++) {
  443           RTFAttribute attr = RTFAttributes.attributes[i];
  444           if (attr.domain() != RTFAttribute.D_DOCUMENT)
  445               continue;
  446           Object prop = doc.getProperty(attr.swingName());
  447           boolean ok = attr.writeValue(prop, this, false);
  448           if (ok)
  449               wroteSomething = true;
  450       }
  451   
  452       if (wroteSomething)
  453           writeLineBreak();
  454   }
  455   
  456   public void writeRTFTrailer()
  457       throws IOException
  458   {
  459       writeEndgroup();
  460       writeLineBreak();
  461   }
  462   
  463   protected void checkNumericControlWord(MutableAttributeSet currentAttributes,
  464                                          AttributeSet newAttributes,
  465                                          Object attrName,
  466                                          String controlWord,
  467                                          float dflt, float scale)
  468       throws IOException
  469   {
  470       Object parm;
  471   
  472       if ((parm = attrDiff(currentAttributes, newAttributes,
  473                            attrName, MagicToken)) != null) {
  474           float targ;
  475           if (parm == MagicToken)
  476               targ = dflt;
  477           else
  478               targ = ((Number)parm).floatValue();
  479           writeControlWord(controlWord, Math.round(targ * scale));
  480       }
  481   }
  482   
  483   protected void checkControlWord(MutableAttributeSet currentAttributes,
  484                                   AttributeSet newAttributes,
  485                                   RTFAttribute word)
  486       throws IOException
  487   {
  488       Object parm;
  489   
  490       if ((parm = attrDiff(currentAttributes, newAttributes,
  491                            word.swingName(), MagicToken)) != null) {
  492           if (parm == MagicToken)
  493               parm = null;
  494           word.writeValue(parm, this, true);
  495       }
  496   }
  497   
  498   protected void checkControlWords(MutableAttributeSet currentAttributes,
  499                                    AttributeSet newAttributes,
  500                                    RTFAttribute words[],
  501                                    int domain)
  502       throws IOException
  503   {
  504       int wordIndex;
  505       int wordCount = words.length;
  506       for(wordIndex = 0; wordIndex < wordCount; wordIndex++) {
  507           RTFAttribute attr = words[wordIndex];
  508           if (attr.domain() == domain)
  509               checkControlWord(currentAttributes, newAttributes, attr);
  510       }
  511   }
  512   
  513   void updateSectionAttributes(MutableAttributeSet current,
  514                                AttributeSet newAttributes,
  515                                boolean emitStyleChanges)
  516       throws IOException
  517   {
  518       if (emitStyleChanges) {
  519           Object oldStyle = current.getAttribute("sectionStyle");
  520           Object newStyle = findStyleNumber(newAttributes, Constants.STSection);
  521           if (oldStyle != newStyle) {
  522               if (oldStyle != null) {
  523                   resetSectionAttributes(current);
  524               }
  525               if (newStyle != null) {
  526                   writeControlWord("ds", ((Integer)newStyle).intValue());
  527                   current.addAttribute("sectionStyle", newStyle);
  528               } else {
  529                   current.removeAttribute("sectionStyle");
  530               }
  531           }
  532       }
  533   
  534       checkControlWords(current, newAttributes,
  535                         RTFAttributes.attributes, RTFAttribute.D_SECTION);
  536   }
  537   
  538   protected void resetSectionAttributes(MutableAttributeSet currentAttributes)
  539       throws IOException
  540   {
  541       writeControlWord("sectd");
  542   
  543       int wordIndex;
  544       int wordCount = RTFAttributes.attributes.length;
  545       for(wordIndex = 0; wordIndex < wordCount; wordIndex++) {
  546           RTFAttribute attr = RTFAttributes.attributes[wordIndex];
  547           if (attr.domain() == RTFAttribute.D_SECTION)
  548               attr.setDefault(currentAttributes);
  549       }
  550   
  551       currentAttributes.removeAttribute("sectionStyle");
  552   }
  553   
  554   void updateParagraphAttributes(MutableAttributeSet current,
  555                                  AttributeSet newAttributes,
  556                                  boolean emitStyleChanges)
  557       throws IOException
  558   {
  559       Object parm;
  560       Object oldStyle, newStyle;
  561   
  562       /* The only way to get rid of tabs or styles is with the \pard keyword,
  563          emitted by resetParagraphAttributes(). Ideally we should avoid
  564          emitting \pard if the new paragraph's tabs are a superset of the old
  565          paragraph's tabs. */
  566   
  567       if (emitStyleChanges) {
  568           oldStyle = current.getAttribute("paragraphStyle");
  569           newStyle = findStyleNumber(newAttributes, Constants.STParagraph);
  570           if (oldStyle != newStyle) {
  571               if (oldStyle != null) {
  572                   resetParagraphAttributes(current);
  573                   oldStyle = null;
  574               }
  575           }
  576       } else {
  577           oldStyle = null;
  578           newStyle = null;
  579       }
  580   
  581       Object oldTabs = current.getAttribute(Constants.Tabs);
  582       Object newTabs = newAttributes.getAttribute(Constants.Tabs);
  583       if (oldTabs != newTabs) {
  584           if (oldTabs != null) {
  585               resetParagraphAttributes(current);
  586               oldTabs = null;
  587               oldStyle = null;
  588           }
  589       }
  590   
  591       if (oldStyle != newStyle && newStyle != null) {
  592           writeControlWord("s", ((Integer)newStyle).intValue());
  593           current.addAttribute("paragraphStyle", newStyle);
  594       }
  595   
  596       checkControlWords(current, newAttributes,
  597                         RTFAttributes.attributes, RTFAttribute.D_PARAGRAPH);
  598   
  599       if (oldTabs != newTabs && newTabs != null) {
  600           TabStop tabs[] = (TabStop[])newTabs;
  601           int index;
  602           for(index = 0; index < tabs.length; index ++) {
  603               TabStop tab = tabs[index];
  604               switch (tab.getAlignment()) {
  605                 case TabStop.ALIGN_LEFT:
  606                 case TabStop.ALIGN_BAR:
  607                   break;
  608                 case TabStop.ALIGN_RIGHT:
  609                   writeControlWord("tqr");
  610                   break;
  611                 case TabStop.ALIGN_CENTER:
  612                   writeControlWord("tqc");
  613                   break;
  614                 case TabStop.ALIGN_DECIMAL:
  615                   writeControlWord("tqdec");
  616                   break;
  617               }
  618               switch (tab.getLeader()) {
  619                 case TabStop.LEAD_NONE:
  620                   break;
  621                 case TabStop.LEAD_DOTS:
  622                   writeControlWord("tldot");
  623                   break;
  624                 case TabStop.LEAD_HYPHENS:
  625                   writeControlWord("tlhyph");
  626                   break;
  627                 case TabStop.LEAD_UNDERLINE:
  628                   writeControlWord("tlul");
  629                   break;
  630                 case TabStop.LEAD_THICKLINE:
  631                   writeControlWord("tlth");
  632                   break;
  633                 case TabStop.LEAD_EQUALS:
  634                   writeControlWord("tleq");
  635                   break;
  636               }
  637               int twips = Math.round(20f * tab.getPosition());
  638               if (tab.getAlignment() == TabStop.ALIGN_BAR) {
  639                   writeControlWord("tb", twips);
  640               } else {
  641                   writeControlWord("tx", twips);
  642               }
  643           }
  644           current.addAttribute(Constants.Tabs, tabs);
  645       }
  646   }
  647   
  648   public void writeParagraphElement(Element el)
  649       throws IOException
  650   {
  651       updateParagraphAttributes(outputAttributes, el.getAttributes(), true);
  652   
  653       int sub_count = el.getElementCount();
  654       for(int idx = 0; idx < sub_count; idx ++) {
  655           writeTextElement(el.getElement(idx));
  656       }
  657   
  658       writeControlWord("par");
  659       writeLineBreak();  /* makes the raw file more readable */
  660   }
  661   
  662   /* debugging. TODO: remove.
  663   private static String tabdump(Object tso)
  664   {
  665       String buf;
  666       int i;
  667   
  668       if (tso == null)
  669           return "[none]";
  670   
  671       TabStop[] ts = (TabStop[])tso;
  672   
  673       buf = "[";
  674       for(i = 0; i < ts.length; i++) {
  675           buf = buf + ts[i].toString();
  676           if ((i+1) < ts.length)
  677               buf = buf + ",";
  678       }
  679       return buf + "]";
  680   }
  681   */
  682   
  683   protected void resetParagraphAttributes(MutableAttributeSet currentAttributes)
  684       throws IOException
  685   {
  686       writeControlWord("pard");
  687   
  688       currentAttributes.addAttribute(StyleConstants.Alignment, Integer.valueOf(0));
  689   
  690       int wordIndex;
  691       int wordCount = RTFAttributes.attributes.length;
  692       for(wordIndex = 0; wordIndex < wordCount; wordIndex++) {
  693           RTFAttribute attr = RTFAttributes.attributes[wordIndex];
  694           if (attr.domain() == RTFAttribute.D_PARAGRAPH)
  695               attr.setDefault(currentAttributes);
  696       }
  697   
  698       currentAttributes.removeAttribute("paragraphStyle");
  699       currentAttributes.removeAttribute(Constants.Tabs);
  700   }
  701   
  702   void updateCharacterAttributes(MutableAttributeSet current,
  703                                  AttributeSet newAttributes,
  704                                  boolean updateStyleChanges)
  705       throws IOException
  706   {
  707       Object parm;
  708   
  709       if (updateStyleChanges) {
  710           Object oldStyle = current.getAttribute("characterStyle");
  711           Object newStyle = findStyleNumber(newAttributes,
  712                                             Constants.STCharacter);
  713           if (oldStyle != newStyle) {
  714               if (oldStyle != null) {
  715                   resetCharacterAttributes(current);
  716               }
  717               if (newStyle != null) {
  718                   writeControlWord("cs", ((Integer)newStyle).intValue());
  719                   current.addAttribute("characterStyle", newStyle);
  720               } else {
  721                   current.removeAttribute("characterStyle");
  722               }
  723           }
  724       }
  725   
  726       if ((parm = attrDiff(current, newAttributes,
  727                            StyleConstants.FontFamily, null)) != null) {
  728           Integer fontNum = fontTable.get(parm);
  729           writeControlWord("f", fontNum.intValue());
  730       }
  731   
  732       checkNumericControlWord(current, newAttributes,
  733                               StyleConstants.FontSize, "fs",
  734                               defaultFontSize, 2f);
  735   
  736       checkControlWords(current, newAttributes,
  737                         RTFAttributes.attributes, RTFAttribute.D_CHARACTER);
  738   
  739       checkNumericControlWord(current, newAttributes,
  740                               StyleConstants.LineSpacing, "sl",
  741                               0, 20f); /* TODO: sl wackiness */
  742   
  743       if ((parm = attrDiff(current, newAttributes,
  744                            StyleConstants.Background, MagicToken)) != null) {
  745           int colorNum;
  746           if (parm == MagicToken)
  747               colorNum = 0;
  748           else
  749               colorNum = colorTable.get(parm).intValue();
  750           writeControlWord("cb", colorNum);
  751       }
  752   
  753       if ((parm = attrDiff(current, newAttributes,
  754                            StyleConstants.Foreground, null)) != null) {
  755           int colorNum;
  756           if (parm == MagicToken)
  757               colorNum = 0;
  758           else
  759               colorNum = colorTable.get(parm).intValue();
  760           writeControlWord("cf", colorNum);
  761       }
  762   }
  763   
  764   protected void resetCharacterAttributes(MutableAttributeSet currentAttributes)
  765       throws IOException
  766   {
  767       writeControlWord("plain");
  768   
  769       int wordIndex;
  770       int wordCount = RTFAttributes.attributes.length;
  771       for(wordIndex = 0; wordIndex < wordCount; wordIndex++) {
  772           RTFAttribute attr = RTFAttributes.attributes[wordIndex];
  773           if (attr.domain() == RTFAttribute.D_CHARACTER)
  774               attr.setDefault(currentAttributes);
  775       }
  776   
  777       StyleConstants.setFontFamily(currentAttributes, defaultFontFamily);
  778       currentAttributes.removeAttribute(StyleConstants.FontSize); /* =default */
  779       currentAttributes.removeAttribute(StyleConstants.Background);
  780       currentAttributes.removeAttribute(StyleConstants.Foreground);
  781       currentAttributes.removeAttribute(StyleConstants.LineSpacing);
  782       currentAttributes.removeAttribute("characterStyle");
  783   }
  784   
  785   public void writeTextElement(Element el)
  786       throws IOException
  787   {
  788       updateCharacterAttributes(outputAttributes, el.getAttributes(), true);
  789   
  790       if (el.isLeaf()) {
  791           try {
  792               el.getDocument().getText(el.getStartOffset(),
  793                                        el.getEndOffset() - el.getStartOffset(),
  794                                        this.workingSegment);
  795           } catch (BadLocationException ble) {
  796               /* TODO is this the correct error to raise? */
  797               ble.printStackTrace();
  798               throw new InternalError(ble.getMessage());
  799           }
  800           writeText(this.workingSegment);
  801       } else {
  802           int sub_count = el.getElementCount();
  803           for(int idx = 0; idx < sub_count; idx ++)
  804               writeTextElement(el.getElement(idx));
  805       }
  806   }
  807   
  808   public void writeText(Segment s)
  809       throws IOException
  810   {
  811       int pos, end;
  812       char[] array;
  813   
  814       pos = s.offset;
  815       end = pos + s.count;
  816       array = s.array;
  817       for( ; pos < end; pos ++)
  818           writeCharacter(array[pos]);
  819   }
  820   
  821   public void writeText(String s)
  822       throws IOException
  823   {
  824       int pos, end;
  825   
  826       pos = 0;
  827       end = s.length();
  828       for( ; pos < end; pos ++)
  829           writeCharacter(s.charAt(pos));
  830   }
  831   
  832   public void writeRawString(String str)
  833       throws IOException
  834   {
  835       int strlen = str.length();
  836       for (int offset = 0; offset < strlen; offset ++)
  837           outputStream.write((int)str.charAt(offset));
  838   }
  839   
  840   public void writeControlWord(String keyword)
  841       throws IOException
  842   {
  843       outputStream.write('\\');
  844       writeRawString(keyword);
  845       afterKeyword = true;
  846   }
  847   
  848   public void writeControlWord(String keyword, int arg)
  849       throws IOException
  850   {
  851       outputStream.write('\\');
  852       writeRawString(keyword);
  853       writeRawString(String.valueOf(arg)); /* TODO: correct in all cases? */
  854       afterKeyword = true;
  855   }
  856   
  857   public void writeBegingroup()
  858       throws IOException
  859   {
  860       outputStream.write('{');
  861       afterKeyword = false;
  862   }
  863   
  864   public void writeEndgroup()
  865       throws IOException
  866   {
  867       outputStream.write('}');
  868       afterKeyword = false;
  869   }
  870   
  871   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   
  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.) */
  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   
  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. */
  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   }

Home » openjdk-7 » javax » swing » text » rtf » [javadoc | source]