Save This Page
Home » openjdk-7 » javax » swing » text » rtf » [javadoc | source]
    1   /*
    2    * Copyright 1997-2004 Sun Microsystems, Inc.  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.  Sun designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
   22    * CA 95054 USA or visit www.sun.com if you need additional information or
   23    * have any questions.
   24    */
   25   package javax.swing.text.rtf;
   26   
   27   import java.lang;
   28   import java.util;
   29   import java.io;
   30   import java.awt.Font;
   31   import java.awt.Color;
   32   
   33   import javax.swing.text;
   34   
   35   /**
   36    * Takes a sequence of RTF tokens and text and appends the text
   37    * described by the RTF to a <code>StyledDocument</code> (the <em>target</em>).
   38    * The RTF is lexed
   39    * from the character stream by the <code>RTFParser</code> which is this class's
   40    * superclass.
   41    *
   42    * This class is an indirect subclass of OutputStream. It must be closed
   43    * in order to guarantee that all of the text has been sent to
   44    * the text acceptor.
   45    *
   46    *   @see RTFParser
   47    *   @see java.io.OutputStream
   48    */
   49   class RTFReader extends RTFParser
   50   {
   51     /** The object to which the parsed text is sent. */
   52     StyledDocument target;
   53   
   54     /** Miscellaneous information about the parser's state. This
   55      *  dictionary is saved and restored when an RTF group begins
   56      *  or ends. */
   57     Dictionary parserState;   /* Current parser state */
   58     /** This is the "dst" item from parserState. rtfDestination
   59      *  is the current rtf destination. It is cached in an instance
   60      *  variable for speed. */
   61     Destination rtfDestination;
   62     /** This holds the current document attributes. */
   63     MutableAttributeSet documentAttributes;
   64   
   65     /** This Dictionary maps Integer font numbers to String font names. */
   66     Dictionary fontTable;
   67     /** This array maps color indices to Color objects. */
   68     Color[] colorTable;
   69     /** This array maps character style numbers to Style objects. */
   70     Style[] characterStyles;
   71     /** This array maps paragraph style numbers to Style objects. */
   72     Style[] paragraphStyles;
   73     /** This array maps section style numbers to Style objects. */
   74     Style[] sectionStyles;
   75   
   76     /** This is the RTF version number, extracted from the \rtf keyword.
   77      *  The version information is currently not used. */
   78     int rtfversion;
   79   
   80     /** <code>true</code> to indicate that if the next keyword is unknown,
   81      *  the containing group should be ignored. */
   82     boolean ignoreGroupIfUnknownKeyword;
   83   
   84     /** The parameter of the most recently parsed \\ucN keyword,
   85      *  used for skipping alternative representations after a
   86      *  Unicode character. */
   87     int skippingCharacters;
   88   
   89     static private Dictionary straightforwardAttributes;
   90     static {
   91         straightforwardAttributes = RTFAttributes.attributesByKeyword();
   92     }
   93   
   94     private MockAttributeSet mockery;
   95   
   96     /* this should be final, but there's a bug in javac... */
   97     /** textKeywords maps RTF keywords to single-character strings,
   98      *  for those keywords which simply insert some text. */
   99     static Dictionary textKeywords = null;
  100     static {
  101         textKeywords = new Hashtable();
  102         textKeywords.put("\\",         "\\");
  103         textKeywords.put("{",          "{");
  104         textKeywords.put("}",          "}");
  105         textKeywords.put(" ",          "\u00A0");  /* not in the spec... */
  106         textKeywords.put("~",          "\u00A0");  /* nonbreaking space */
  107         textKeywords.put("_",          "\u2011");  /* nonbreaking hyphen */
  108         textKeywords.put("bullet",     "\u2022");
  109         textKeywords.put("emdash",     "\u2014");
  110         textKeywords.put("emspace",    "\u2003");
  111         textKeywords.put("endash",     "\u2013");
  112         textKeywords.put("enspace",    "\u2002");
  113         textKeywords.put("ldblquote",  "\u201C");
  114         textKeywords.put("lquote",     "\u2018");
  115         textKeywords.put("ltrmark",    "\u200E");
  116         textKeywords.put("rdblquote",  "\u201D");
  117         textKeywords.put("rquote",     "\u2019");
  118         textKeywords.put("rtlmark",    "\u200F");
  119         textKeywords.put("tab",        "\u0009");
  120         textKeywords.put("zwj",        "\u200D");
  121         textKeywords.put("zwnj",       "\u200C");
  122   
  123         /* There is no Unicode equivalent to an optional hyphen, as far as
  124            I can tell. */
  125         textKeywords.put("-",          "\u2027");  /* TODO: optional hyphen */
  126     }
  127   
  128     /* some entries in parserState */
  129     static final String TabAlignmentKey = "tab_alignment";
  130     static final String TabLeaderKey = "tab_leader";
  131   
  132     static Dictionary characterSets;
  133     static boolean useNeXTForAnsi = false;
  134     static {
  135         characterSets = new Hashtable();
  136     }
  137   
  138   /* TODO: per-font font encodings ( \fcharset control word ) ? */
  139   
  140   /**
  141    * Creates a new RTFReader instance. Text will be sent to
  142    * the specified TextAcceptor.
  143    *
  144    * @param destination The TextAcceptor which is to receive the text.
  145    */
  146   public RTFReader(StyledDocument destination)
  147   {
  148       int i;
  149   
  150       target = destination;
  151       parserState = new Hashtable();
  152       fontTable = new Hashtable();
  153   
  154       rtfversion = -1;
  155   
  156       mockery = new MockAttributeSet();
  157       documentAttributes = new SimpleAttributeSet();
  158   }
  159   
  160   /** Called when the RTFParser encounters a bin keyword in the
  161    *  RTF stream.
  162    *
  163    *  @see RTFParser
  164    */
  165   public void handleBinaryBlob(byte[] data)
  166   {
  167       if (skippingCharacters > 0) {
  168           /* a blob only counts as one character for skipping purposes */
  169           skippingCharacters --;
  170           return;
  171       }
  172   
  173       /* someday, someone will want to do something with blobs */
  174   }
  175   
  176   
  177   /**
  178    * Handles any pure text (containing no control characters) in the input
  179    * stream. Called by the superclass. */
  180   public void handleText(String text)
  181   {
  182       if (skippingCharacters > 0) {
  183           if (skippingCharacters >= text.length()) {
  184               skippingCharacters -= text.length();
  185               return;
  186           } else {
  187               text = text.substring(skippingCharacters);
  188               skippingCharacters = 0;
  189           }
  190       }
  191   
  192       if (rtfDestination != null) {
  193           rtfDestination.handleText(text);
  194           return;
  195       }
  196   
  197       warning("Text with no destination. oops.");
  198   }
  199   
  200   /** The default color for text which has no specified color. */
  201   Color defaultColor()
  202   {
  203       return Color.black;
  204   }
  205   
  206   /** Called by the superclass when a new RTF group is begun.
  207    *  This implementation saves the current <code>parserState</code>, and gives
  208    *  the current destination a chance to save its own state.
  209    * @see RTFParser#begingroup
  210    */
  211   public void begingroup()
  212   {
  213       if (skippingCharacters > 0) {
  214           /* TODO this indicates an error in the RTF. Log it? */
  215           skippingCharacters = 0;
  216       }
  217   
  218       /* we do this little dance to avoid cloning the entire state stack and
  219          immediately throwing it away. */
  220       Object oldSaveState = parserState.get("_savedState");
  221       if (oldSaveState != null)
  222           parserState.remove("_savedState");
  223       Dictionary saveState = (Dictionary)((Hashtable)parserState).clone();
  224       if (oldSaveState != null)
  225           saveState.put("_savedState", oldSaveState);
  226       parserState.put("_savedState", saveState);
  227   
  228       if (rtfDestination != null)
  229           rtfDestination.begingroup();
  230   }
  231   
  232   /** Called by the superclass when the current RTF group is closed.
  233    *  This restores the parserState saved by <code>begingroup()</code>
  234    *  as well as invoking the endgroup method of the current
  235    *  destination.
  236    * @see RTFParser#endgroup
  237    */
  238   public void endgroup()
  239   {
  240       if (skippingCharacters > 0) {
  241           /* NB this indicates an error in the RTF. Log it? */
  242           skippingCharacters = 0;
  243       }
  244   
  245       Dictionary restoredState = (Dictionary)parserState.get("_savedState");
  246       Destination restoredDestination = (Destination)restoredState.get("dst");
  247       if (restoredDestination != rtfDestination) {
  248           rtfDestination.close(); /* allow the destination to clean up */
  249           rtfDestination = restoredDestination;
  250       }
  251       Dictionary oldParserState = parserState;
  252       parserState = restoredState;
  253       if (rtfDestination != null)
  254           rtfDestination.endgroup(oldParserState);
  255   }
  256   
  257   protected void setRTFDestination(Destination newDestination)
  258   {
  259       /* Check that setting the destination won't close the
  260          current destination (should never happen) */
  261       Dictionary previousState = (Dictionary)parserState.get("_savedState");
  262       if (previousState != null) {
  263           if (rtfDestination != previousState.get("dst")) {
  264               warning("Warning, RTF destination overridden, invalid RTF.");
  265               rtfDestination.close();
  266           }
  267       }
  268       rtfDestination = newDestination;
  269       parserState.put("dst", rtfDestination);
  270   }
  271   
  272   /** Called by the user when there is no more input (<i>i.e.</i>,
  273    * at the end of the RTF file.)
  274    *
  275    * @see OutputStream#close
  276    */
  277   public void close()
  278       throws IOException
  279   {
  280       Enumeration docProps = documentAttributes.getAttributeNames();
  281       while(docProps.hasMoreElements()) {
  282           Object propName = docProps.nextElement();
  283           target.putProperty(propName,
  284                              documentAttributes.getAttribute((String)propName));
  285       }
  286   
  287       /* RTFParser should have ensured that all our groups are closed */
  288   
  289       warning("RTF filter done.");
  290   
  291       super.close();
  292   }
  293   
  294   /**
  295    * Handles a parameterless RTF keyword. This is called by the superclass
  296    * (RTFParser) when a keyword is found in the input stream.
  297    *
  298    * @returns <code>true</code> if the keyword is recognized and handled;
  299    *          <code>false</code> otherwise
  300    * @see RTFParser#handleKeyword
  301    */
  302   public boolean handleKeyword(String keyword)
  303   {
  304       Object item;
  305       boolean ignoreGroupIfUnknownKeywordSave = ignoreGroupIfUnknownKeyword;
  306   
  307       if (skippingCharacters > 0) {
  308           skippingCharacters --;
  309           return true;
  310       }
  311   
  312       ignoreGroupIfUnknownKeyword = false;
  313   
  314       if ((item = textKeywords.get(keyword)) != null) {
  315           handleText((String)item);
  316           return true;
  317       }
  318   
  319       if (keyword.equals("fonttbl")) {
  320           setRTFDestination(new FonttblDestination());
  321           return true;
  322       }
  323   
  324       if (keyword.equals("colortbl")) {
  325           setRTFDestination(new ColortblDestination());
  326           return true;
  327       }
  328   
  329       if (keyword.equals("stylesheet")) {
  330           setRTFDestination(new StylesheetDestination());
  331           return true;
  332       }
  333   
  334       if (keyword.equals("info")) {
  335           setRTFDestination(new InfoDestination());
  336           return false;
  337       }
  338   
  339       if (keyword.equals("mac")) {
  340           setCharacterSet("mac");
  341           return true;
  342       }
  343   
  344       if (keyword.equals("ansi")) {
  345           if (useNeXTForAnsi)
  346               setCharacterSet("NeXT");
  347           else
  348               setCharacterSet("ansi");
  349           return true;
  350       }
  351   
  352       if (keyword.equals("next")) {
  353           setCharacterSet("NeXT");
  354           return true;
  355       }
  356   
  357       if (keyword.equals("pc")) {
  358           setCharacterSet("cpg437"); /* IBM Code Page 437 */
  359           return true;
  360       }
  361   
  362       if (keyword.equals("pca")) {
  363           setCharacterSet("cpg850"); /* IBM Code Page 850 */
  364           return true;
  365       }
  366   
  367       if (keyword.equals("*")) {
  368           ignoreGroupIfUnknownKeyword = true;
  369           return true;
  370       }
  371   
  372       if (rtfDestination != null) {
  373           if(rtfDestination.handleKeyword(keyword))
  374               return true;
  375       }
  376   
  377       /* this point is reached only if the keyword is unrecognized */
  378   
  379       /* other destinations we don't understand and therefore ignore */
  380       if (keyword.equals("aftncn") ||
  381           keyword.equals("aftnsep") ||
  382           keyword.equals("aftnsepc") ||
  383           keyword.equals("annotation") ||
  384           keyword.equals("atnauthor") ||
  385           keyword.equals("atnicn") ||
  386           keyword.equals("atnid") ||
  387           keyword.equals("atnref") ||
  388           keyword.equals("atntime") ||
  389           keyword.equals("atrfend") ||
  390           keyword.equals("atrfstart") ||
  391           keyword.equals("bkmkend") ||
  392           keyword.equals("bkmkstart") ||
  393           keyword.equals("datafield") ||
  394           keyword.equals("do") ||
  395           keyword.equals("dptxbxtext") ||
  396           keyword.equals("falt") ||
  397           keyword.equals("field") ||
  398           keyword.equals("file") ||
  399           keyword.equals("filetbl") ||
  400           keyword.equals("fname") ||
  401           keyword.equals("fontemb") ||
  402           keyword.equals("fontfile") ||
  403           keyword.equals("footer") ||
  404           keyword.equals("footerf") ||
  405           keyword.equals("footerl") ||
  406           keyword.equals("footerr") ||
  407           keyword.equals("footnote") ||
  408           keyword.equals("ftncn") ||
  409           keyword.equals("ftnsep") ||
  410           keyword.equals("ftnsepc") ||
  411           keyword.equals("header") ||
  412           keyword.equals("headerf") ||
  413           keyword.equals("headerl") ||
  414           keyword.equals("headerr") ||
  415           keyword.equals("keycode") ||
  416           keyword.equals("nextfile") ||
  417           keyword.equals("object") ||
  418           keyword.equals("pict") ||
  419           keyword.equals("pn") ||
  420           keyword.equals("pnseclvl") ||
  421           keyword.equals("pntxtb") ||
  422           keyword.equals("pntxta") ||
  423           keyword.equals("revtbl") ||
  424           keyword.equals("rxe") ||
  425           keyword.equals("tc") ||
  426           keyword.equals("template") ||
  427           keyword.equals("txe") ||
  428           keyword.equals("xe")) {
  429           ignoreGroupIfUnknownKeywordSave = true;
  430       }
  431   
  432       if (ignoreGroupIfUnknownKeywordSave) {
  433           setRTFDestination(new DiscardingDestination());
  434       }
  435   
  436       return false;
  437   }
  438   
  439   /**
  440    * Handles an RTF keyword and its integer parameter.
  441    * This is called by the superclass
  442    * (RTFParser) when a keyword is found in the input stream.
  443    *
  444    * @returns <code>true</code> if the keyword is recognized and handled;
  445    *          <code>false</code> otherwise
  446    * @see RTFParser#handleKeyword
  447    */
  448   public boolean handleKeyword(String keyword, int parameter)
  449   {
  450       boolean ignoreGroupIfUnknownKeywordSave = ignoreGroupIfUnknownKeyword;
  451   
  452       if (skippingCharacters > 0) {
  453           skippingCharacters --;
  454           return true;
  455       }
  456   
  457       ignoreGroupIfUnknownKeyword = false;
  458   
  459       if (keyword.equals("uc")) {
  460           /* count of characters to skip after a unicode character */
  461           parserState.put("UnicodeSkip", Integer.valueOf(parameter));
  462           return true;
  463       }
  464       if (keyword.equals("u")) {
  465           if (parameter < 0)
  466               parameter = parameter + 65536;
  467           handleText((char)parameter);
  468           Number skip = (Number)(parserState.get("UnicodeSkip"));
  469           if (skip != null) {
  470               skippingCharacters = skip.intValue();
  471           } else {
  472               skippingCharacters = 1;
  473           }
  474           return true;
  475       }
  476   
  477       if (keyword.equals("rtf")) {
  478           rtfversion = parameter;
  479           setRTFDestination(new DocumentDestination());
  480           return true;
  481       }
  482   
  483       if (keyword.startsWith("NeXT") ||
  484           keyword.equals("private"))
  485           ignoreGroupIfUnknownKeywordSave = true;
  486   
  487       if (rtfDestination != null) {
  488           if(rtfDestination.handleKeyword(keyword, parameter))
  489               return true;
  490       }
  491   
  492       /* this point is reached only if the keyword is unrecognized */
  493   
  494       if (ignoreGroupIfUnknownKeywordSave) {
  495           setRTFDestination(new DiscardingDestination());
  496       }
  497   
  498       return false;
  499   }
  500   
  501   private void setTargetAttribute(String name, Object value)
  502   {
  503   //    target.changeAttributes(new LFDictionary(LFArray.arrayWithObject(value), LFArray.arrayWithObject(name)));
  504   }
  505   
  506   /**
  507    * setCharacterSet sets the current translation table to correspond with
  508    * the named character set. The character set is loaded if necessary.
  509    *
  510    * @see AbstractFilter
  511    */
  512   public void setCharacterSet(String name)
  513   {
  514       Object set;
  515   
  516       try {
  517           set = getCharacterSet(name);
  518       } catch (Exception e) {
  519           warning("Exception loading RTF character set \"" + name + "\": " + e);
  520           set = null;
  521       }
  522   
  523       if (set != null) {
  524           translationTable = (char[])set;
  525       } else {
  526           warning("Unknown RTF character set \"" + name + "\"");
  527           if (!name.equals("ansi")) {
  528               try {
  529                   translationTable = (char[])getCharacterSet("ansi");
  530               } catch (IOException e) {
  531                   throw new InternalError("RTFReader: Unable to find character set resources (" + e + ")");
  532               }
  533           }
  534       }
  535   
  536       setTargetAttribute(Constants.RTFCharacterSet, name);
  537   }
  538   
  539   /** Adds a character set to the RTFReader's list
  540    *  of known character sets */
  541   public static void
  542   defineCharacterSet(String name, char[] table)
  543   {
  544       if (table.length < 256)
  545           throw new IllegalArgumentException("Translation table must have 256 entries.");
  546       characterSets.put(name, table);
  547   }
  548   
  549   /** Looks up a named character set. A character set is a 256-entry
  550    *  array of characters, mapping unsigned byte values to their Unicode
  551    *  equivalents. The character set is loaded if necessary.
  552    *
  553    *  @returns the character set
  554    */
  555   public static Object
  556   getCharacterSet(final String name)
  557       throws IOException
  558   {
  559       char[] set;
  560   
  561       set = (char [])characterSets.get(name);
  562       if (set == null) {
  563         InputStream charsetStream;
  564         charsetStream = (InputStream)java.security.AccessController.
  565                         doPrivileged(new java.security.PrivilegedAction() {
  566             public Object run() {
  567                 return RTFReader.class.getResourceAsStream
  568                                        ("charsets/" + name + ".txt");
  569             }
  570         });
  571         set = readCharset(charsetStream);
  572         defineCharacterSet(name, set);
  573       }
  574       return set;
  575   }
  576   
  577   /** Parses a character set from an InputStream. The character set
  578    * must contain 256 decimal integers, separated by whitespace, with
  579    * no punctuation. B- and C- style comments are allowed.
  580    *
  581    * @returns the newly read character set
  582    */
  583   static char[] readCharset(InputStream strm)
  584        throws IOException
  585   {
  586       char[] values = new char[256];
  587       int i;
  588       StreamTokenizer in = new StreamTokenizer(new BufferedReader(
  589               new InputStreamReader(strm, "ISO-8859-1")));
  590   
  591       in.eolIsSignificant(false);
  592       in.commentChar('#');
  593       in.slashSlashComments(true);
  594       in.slashStarComments(true);
  595   
  596       i = 0;
  597       while (i < 256) {
  598           int ttype;
  599           try {
  600               ttype = in.nextToken();
  601           } catch (Exception e) {
  602               throw new IOException("Unable to read from character set file (" + e + ")");
  603           }
  604           if (ttype != in.TT_NUMBER) {
  605   //          System.out.println("Bad token: type=" + ttype + " tok=" + in.sval);
  606               throw new IOException("Unexpected token in character set file");
  607   //          continue;
  608           }
  609           values[i] = (char)(in.nval);
  610           i++;
  611       }
  612   
  613       return values;
  614   }
  615   
  616   static char[] readCharset(java.net.URL href)
  617        throws IOException
  618   {
  619       return readCharset(href.openStream());
  620   }
  621   
  622   /** An interface (could be an entirely abstract class) describing
  623    *  a destination. The RTF reader always has a current destination
  624    *  which is where text is sent.
  625    *
  626    *  @see RTFReader
  627    */
  628   interface Destination {
  629       void handleBinaryBlob(byte[] data);
  630       void handleText(String text);
  631       boolean handleKeyword(String keyword);
  632       boolean handleKeyword(String keyword, int parameter);
  633   
  634       void begingroup();
  635       void endgroup(Dictionary oldState);
  636   
  637       void close();
  638   }
  639   
  640   /** This data-sink class is used to implement ignored destinations
  641    *  (e.g. {\*\blegga blah blah blah} )
  642    *  It accepts all keywords and text but does nothing with them. */
  643   class DiscardingDestination implements Destination
  644   {
  645       public void handleBinaryBlob(byte[] data)
  646       {
  647           /* Discard binary blobs. */
  648       }
  649   
  650       public void handleText(String text)
  651       {
  652           /* Discard text. */
  653       }
  654   
  655       public boolean handleKeyword(String text)
  656       {
  657           /* Accept and discard keywords. */
  658           return true;
  659       }
  660   
  661       public boolean handleKeyword(String text, int parameter)
  662       {
  663           /* Accept and discard parameterized keywords. */
  664           return true;
  665       }
  666   
  667       public void begingroup()
  668       {
  669           /* Ignore groups --- the RTFReader will keep track of the
  670              current group level as necessary */
  671       }
  672   
  673       public void endgroup(Dictionary oldState)
  674       {
  675           /* Ignore groups */
  676       }
  677   
  678       public void close()
  679       {
  680           /* No end-of-destination cleanup needed */
  681       }
  682   }
  683   
  684   /** Reads the fonttbl group, inserting fonts into the RTFReader's
  685    *  fontTable dictionary. */
  686   class FonttblDestination implements Destination
  687   {
  688       int nextFontNumber;
  689       Object fontNumberKey = null;
  690       String nextFontFamily;
  691   
  692       public void handleBinaryBlob(byte[] data)
  693       { /* Discard binary blobs. */ }
  694   
  695       public void handleText(String text)
  696       {
  697           int semicolon = text.indexOf(';');
  698           String fontName;
  699   
  700           if (semicolon > -1)
  701               fontName = text.substring(0, semicolon);
  702           else
  703               fontName = text;
  704   
  705   
  706           /* TODO: do something with the font family. */
  707   
  708           if (nextFontNumber == -1
  709               && fontNumberKey != null) {
  710               //font name might be broken across multiple calls
  711               fontName = fontTable.get(fontNumberKey) + fontName;
  712           } else {
  713               fontNumberKey = Integer.valueOf(nextFontNumber);
  714           }
  715           fontTable.put(fontNumberKey, fontName);
  716   
  717           nextFontNumber = -1;
  718           nextFontFamily = null;
  719           return;
  720       }
  721   
  722       public boolean handleKeyword(String keyword)
  723       {
  724           if (keyword.charAt(0) == 'f') {
  725               nextFontFamily = keyword.substring(1);
  726               return true;
  727           }
  728   
  729           return false;
  730       }
  731   
  732       public boolean handleKeyword(String keyword, int parameter)
  733       {
  734           if (keyword.equals("f")) {
  735               nextFontNumber = parameter;
  736               return true;
  737           }
  738   
  739           return false;
  740       }
  741   
  742       /* Groups are irrelevant. */
  743       public void begingroup() {}
  744       public void endgroup(Dictionary oldState) {}
  745   
  746       /* currently, the only thing we do when the font table ends is
  747          dump its contents to the debugging log. */
  748       public void close()
  749       {
  750           Enumeration nums = fontTable.keys();
  751           warning("Done reading font table.");
  752           while(nums.hasMoreElements()) {
  753               Integer num = (Integer)nums.nextElement();
  754               warning("Number " + num + ": " + fontTable.get(num));
  755           }
  756       }
  757   }
  758   
  759   /** Reads the colortbl group. Upon end-of-group, the RTFReader's
  760    *  color table is set to an array containing the read colors. */
  761   class ColortblDestination implements Destination
  762   {
  763       int red, green, blue;
  764       Vector proTemTable;
  765   
  766       public ColortblDestination()
  767       {
  768           red = 0;
  769           green = 0;
  770           blue = 0;
  771           proTemTable = new Vector();
  772       }
  773   
  774       public void handleText(String text)
  775       {
  776           int index = 0;
  777   
  778           for (index = 0; index < text.length(); index ++) {
  779               if (text.charAt(index) == ';') {
  780                   Color newColor;
  781                   newColor = new Color(red, green, blue);
  782                   proTemTable.addElement(newColor);
  783               }
  784           }
  785       }
  786   
  787       public void close()
  788       {
  789           int count = proTemTable.size();
  790           warning("Done reading color table, " + count + " entries.");
  791           colorTable = new Color[count];
  792           proTemTable.copyInto(colorTable);
  793       }
  794   
  795       public boolean handleKeyword(String keyword, int parameter)
  796       {
  797           if (keyword.equals("red"))
  798               red = parameter;
  799           else if (keyword.equals("green"))
  800               green = parameter;
  801           else if (keyword.equals("blue"))
  802               blue = parameter;
  803           else
  804               return false;
  805   
  806           return true;
  807       }
  808   
  809       /* Colortbls don't understand any parameterless keywords */
  810       public boolean handleKeyword(String keyword) { return false; }
  811   
  812       /* Groups are irrelevant. */
  813       public void begingroup() {}
  814       public void endgroup(Dictionary oldState) {}
  815   
  816       /* Shouldn't see any binary blobs ... */
  817       public void handleBinaryBlob(byte[] data) {}
  818   }
  819   
  820   /** Handles the stylesheet keyword. Styles are read and sorted
  821    *  into the three style arrays in the RTFReader. */
  822   class StylesheetDestination
  823       extends DiscardingDestination
  824       implements Destination
  825   {
  826       Dictionary definedStyles;
  827   
  828       public StylesheetDestination()
  829       {
  830           definedStyles = new Hashtable();
  831       }
  832   
  833       public void begingroup()
  834       {
  835           setRTFDestination(new StyleDefiningDestination());
  836       }
  837   
  838       public void close()
  839       {
  840           Vector chrStyles, pgfStyles, secStyles;
  841           chrStyles = new Vector();
  842           pgfStyles = new Vector();
  843           secStyles = new Vector();
  844           Enumeration styles = definedStyles.elements();
  845           while(styles.hasMoreElements()) {
  846               StyleDefiningDestination style;
  847               Style defined;
  848               style = (StyleDefiningDestination)styles.nextElement();
  849               defined = style.realize();
  850               warning("Style "+style.number+" ("+style.styleName+"): "+defined);
  851               String stype = (String)defined.getAttribute(Constants.StyleType);
  852               Vector toSet;
  853               if (stype.equals(Constants.STSection)) {
  854                   toSet = secStyles;
  855               } else if (stype.equals(Constants.STCharacter)) {
  856                   toSet = chrStyles;
  857               } else {
  858                   toSet = pgfStyles;
  859               }
  860               if (toSet.size() <= style.number)
  861                   toSet.setSize(style.number + 1);
  862               toSet.setElementAt(defined, style.number);
  863           }
  864           if (!(chrStyles.isEmpty())) {
  865               Style[] styleArray = new Style[chrStyles.size()];
  866               chrStyles.copyInto(styleArray);
  867               characterStyles = styleArray;
  868           }
  869           if (!(pgfStyles.isEmpty())) {
  870               Style[] styleArray = new Style[pgfStyles.size()];
  871               pgfStyles.copyInto(styleArray);
  872               paragraphStyles = styleArray;
  873           }
  874           if (!(secStyles.isEmpty())) {
  875               Style[] styleArray = new Style[secStyles.size()];
  876               secStyles.copyInto(styleArray);
  877               sectionStyles = styleArray;
  878           }
  879   
  880   /* (old debugging code)
  881           int i, m;
  882           if (characterStyles != null) {
  883             m = characterStyles.length;
  884             for(i=0;i<m;i++)
  885               warnings.println("chrStyle["+i+"]="+characterStyles[i]);
  886           } else warnings.println("No character styles.");
  887           if (paragraphStyles != null) {
  888             m = paragraphStyles.length;
  889             for(i=0;i<m;i++)
  890               warnings.println("pgfStyle["+i+"]="+paragraphStyles[i]);
  891           } else warnings.println("No paragraph styles.");
  892           if (sectionStyles != null) {
  893             m = characterStyles.length;
  894             for(i=0;i<m;i++)
  895               warnings.println("secStyle["+i+"]="+sectionStyles[i]);
  896           } else warnings.println("No section styles.");
  897   */
  898       }
  899   
  900       /** This subclass handles an individual style */
  901       class StyleDefiningDestination
  902           extends AttributeTrackingDestination
  903           implements Destination
  904       {
  905           final int STYLENUMBER_NONE = 222;
  906           boolean additive;
  907           boolean characterStyle;
  908           boolean sectionStyle;
  909           public String styleName;
  910           public int number;
  911           int basedOn;
  912           int nextStyle;
  913           boolean hidden;
  914   
  915           Style realizedStyle;
  916   
  917           public StyleDefiningDestination()
  918           {
  919               additive = false;
  920               characterStyle = false;
  921               sectionStyle = false;
  922               styleName = null;
  923               number = 0;
  924               basedOn = STYLENUMBER_NONE;
  925               nextStyle = STYLENUMBER_NONE;
  926               hidden = false;
  927           }
  928   
  929           public void handleText(String text)
  930           {
  931               if (styleName != null)
  932                   styleName = styleName + text;
  933               else
  934                   styleName = text;
  935           }
  936   
  937           public void close() {
  938               int semicolon = (styleName == null) ? 0 : styleName.indexOf(';');
  939               if (semicolon > 0)
  940                   styleName = styleName.substring(0, semicolon);
  941               definedStyles.put(Integer.valueOf(number), this);
  942               super.close();
  943           }
  944   
  945           public boolean handleKeyword(String keyword)
  946           {
  947               if (keyword.equals("additive")) {
  948                   additive = true;
  949                   return true;
  950               }
  951               if (keyword.equals("shidden")) {
  952                   hidden = true;
  953                   return true;
  954               }
  955               return super.handleKeyword(keyword);
  956           }
  957   
  958           public boolean handleKeyword(String keyword, int parameter)
  959           {
  960               if (keyword.equals("s")) {
  961                   characterStyle = false;
  962                   sectionStyle = false;
  963                   number = parameter;
  964               } else if (keyword.equals("cs")) {
  965                   characterStyle = true;
  966                   sectionStyle = false;
  967                   number = parameter;
  968               } else if (keyword.equals("ds")) {
  969                   characterStyle = false;
  970                   sectionStyle = true;
  971                   number = parameter;
  972               } else if (keyword.equals("sbasedon")) {
  973                   basedOn = parameter;
  974               } else if (keyword.equals("snext")) {
  975                   nextStyle = parameter;
  976               } else {
  977                   return super.handleKeyword(keyword, parameter);
  978               }
  979               return true;
  980           }
  981   
  982           public Style realize()
  983           {
  984               Style basis = null;
  985               Style next = null;
  986   
  987               if (realizedStyle != null)
  988                   return realizedStyle;
  989   
  990               if (basedOn != STYLENUMBER_NONE) {
  991                   StyleDefiningDestination styleDest;
  992                   styleDest = (StyleDefiningDestination)definedStyles.get(Integer.valueOf(basedOn));
  993                   if (styleDest != null && styleDest != this) {
  994                       basis = styleDest.realize();
  995                   }
  996               }
  997   
  998               /* NB: Swing StyleContext doesn't allow distinct styles with
  999                  the same name; RTF apparently does. This may confuse the
 1000                  user. */
 1001               realizedStyle = target.addStyle(styleName, basis);
 1002   
 1003               if (characterStyle) {
 1004                   realizedStyle.addAttributes(currentTextAttributes());
 1005                   realizedStyle.addAttribute(Constants.StyleType,
 1006                                              Constants.STCharacter);
 1007               } else if (sectionStyle) {
 1008                   realizedStyle.addAttributes(currentSectionAttributes());
 1009                   realizedStyle.addAttribute(Constants.StyleType,
 1010                                              Constants.STSection);
 1011               } else { /* must be a paragraph style */
 1012                   realizedStyle.addAttributes(currentParagraphAttributes());
 1013                   realizedStyle.addAttribute(Constants.StyleType,
 1014                                              Constants.STParagraph);
 1015               }
 1016   
 1017               if (nextStyle != STYLENUMBER_NONE) {
 1018                   StyleDefiningDestination styleDest;
 1019                   styleDest = (StyleDefiningDestination)definedStyles.get(Integer.valueOf(nextStyle));
 1020                   if (styleDest != null) {
 1021                       next = styleDest.realize();
 1022                   }
 1023               }
 1024   
 1025               if (next != null)
 1026                   realizedStyle.addAttribute(Constants.StyleNext, next);
 1027               realizedStyle.addAttribute(Constants.StyleAdditive,
 1028                                          Boolean.valueOf(additive));
 1029               realizedStyle.addAttribute(Constants.StyleHidden,
 1030                                          Boolean.valueOf(hidden));
 1031   
 1032               return realizedStyle;
 1033           }
 1034       }
 1035   }
 1036   
 1037   /** Handles the info group. Currently no info keywords are recognized
 1038    *  so this is a subclass of DiscardingDestination. */
 1039   class InfoDestination
 1040       extends DiscardingDestination
 1041       implements Destination
 1042   {
 1043   }
 1044   
 1045   /** RTFReader.TextHandlingDestination is an abstract RTF destination
 1046    *  which simply tracks the attributes specified by the RTF control words
 1047    *  in internal form and can produce acceptable AttributeSets for the
 1048    *  current character, paragraph, and section attributes. It is up
 1049    *  to the subclasses to determine what is done with the actual text. */
 1050   abstract class AttributeTrackingDestination implements Destination
 1051   {
 1052       /** This is the "chr" element of parserState, cached for
 1053        *  more efficient use */
 1054       MutableAttributeSet characterAttributes;
 1055       /** This is the "pgf" element of parserState, cached for
 1056        *  more efficient use */
 1057       MutableAttributeSet paragraphAttributes;
 1058       /** This is the "sec" element of parserState, cached for
 1059        *  more efficient use */
 1060       MutableAttributeSet sectionAttributes;
 1061   
 1062       public AttributeTrackingDestination()
 1063       {
 1064           characterAttributes = rootCharacterAttributes();
 1065           parserState.put("chr", characterAttributes);
 1066           paragraphAttributes = rootParagraphAttributes();
 1067           parserState.put("pgf", paragraphAttributes);
 1068           sectionAttributes = rootSectionAttributes();
 1069           parserState.put("sec", sectionAttributes);
 1070       }
 1071   
 1072       abstract public void handleText(String text);
 1073   
 1074       public void handleBinaryBlob(byte[] data)
 1075       {
 1076           /* This should really be in TextHandlingDestination, but
 1077            * since *nobody* does anything with binary blobs, this
 1078            * is more convenient. */
 1079           warning("Unexpected binary data in RTF file.");
 1080       }
 1081   
 1082       public void begingroup()
 1083       {
 1084           AttributeSet characterParent = currentTextAttributes();
 1085           AttributeSet paragraphParent = currentParagraphAttributes();
 1086           AttributeSet sectionParent = currentSectionAttributes();
 1087   
 1088           /* It would probably be more efficient to use the
 1089            * resolver property of the attributes set for
 1090            * implementing rtf groups,
 1091            * but that's needed for styles. */
 1092   
 1093           /* update the cached attribute dictionaries */
 1094           characterAttributes = new SimpleAttributeSet();
 1095           characterAttributes.addAttributes(characterParent);
 1096           parserState.put("chr", characterAttributes);
 1097   
 1098           paragraphAttributes = new SimpleAttributeSet();
 1099           paragraphAttributes.addAttributes(paragraphParent);
 1100           parserState.put("pgf", paragraphAttributes);
 1101   
 1102           sectionAttributes = new SimpleAttributeSet();
 1103           sectionAttributes.addAttributes(sectionParent);
 1104           parserState.put("sec", sectionAttributes);
 1105       }
 1106   
 1107       public void endgroup(Dictionary oldState)
 1108       {
 1109           characterAttributes = (MutableAttributeSet)parserState.get("chr");
 1110           paragraphAttributes = (MutableAttributeSet)parserState.get("pgf");
 1111           sectionAttributes   = (MutableAttributeSet)parserState.get("sec");
 1112       }
 1113   
 1114       public void close()
 1115       {
 1116       }
 1117   
 1118       public boolean handleKeyword(String keyword)
 1119       {
 1120           if (keyword.equals("ulnone")) {
 1121               return handleKeyword("ul", 0);
 1122           }
 1123   
 1124           {
 1125               Object item = straightforwardAttributes.get(keyword);
 1126               if (item != null) {
 1127                   RTFAttribute attr = (RTFAttribute)item;
 1128                   boolean ok;
 1129   
 1130                   switch(attr.domain()) {
 1131                     case RTFAttribute.D_CHARACTER:
 1132                       ok = attr.set(characterAttributes);
 1133                       break;
 1134                     case RTFAttribute.D_PARAGRAPH:
 1135                       ok = attr.set(paragraphAttributes);
 1136                       break;
 1137                     case RTFAttribute.D_SECTION:
 1138                       ok = attr.set(sectionAttributes);
 1139                       break;
 1140                     case RTFAttribute.D_META:
 1141                       mockery.backing = parserState;
 1142                       ok = attr.set(mockery);
 1143                       mockery.backing = null;
 1144                       break;
 1145                     case RTFAttribute.D_DOCUMENT:
 1146                       ok = attr.set(documentAttributes);
 1147                       break;
 1148                     default:
 1149                       /* should never happen */
 1150                       ok = false;
 1151                       break;
 1152                   }
 1153                   if (ok)
 1154                       return true;
 1155               }
 1156           }
 1157   
 1158   
 1159           if (keyword.equals("plain")) {
 1160               resetCharacterAttributes();
 1161               return true;
 1162           }
 1163   
 1164           if (keyword.equals("pard")) {
 1165               resetParagraphAttributes();
 1166               return true;
 1167           }
 1168   
 1169           if (keyword.equals("sectd")) {
 1170               resetSectionAttributes();
 1171               return true;
 1172           }
 1173   
 1174           return false;
 1175       }
 1176   
 1177       public boolean handleKeyword(String keyword, int parameter)
 1178       {
 1179           boolean booleanParameter = (parameter != 0);
 1180   
 1181           if (keyword.equals("fc"))
 1182               keyword = "cf"; /* whatEVER, dude. */
 1183   
 1184           if (keyword.equals("f")) {
 1185               parserState.put(keyword, Integer.valueOf(parameter));
 1186               return true;
 1187           }
 1188           if (keyword.equals("cf")) {
 1189               parserState.put(keyword, Integer.valueOf(parameter));
 1190               return true;
 1191           }
 1192   
 1193           {
 1194               Object item = straightforwardAttributes.get(keyword);
 1195               if (item != null) {
 1196                   RTFAttribute attr = (RTFAttribute)item;
 1197                   boolean ok;
 1198   
 1199                   switch(attr.domain()) {
 1200                     case RTFAttribute.D_CHARACTER:
 1201                       ok = attr.set(characterAttributes, parameter);
 1202                       break;
 1203                     case RTFAttribute.D_PARAGRAPH:
 1204                       ok = attr.set(paragraphAttributes, parameter);
 1205                       break;
 1206                     case RTFAttribute.D_SECTION:
 1207                       ok = attr.set(sectionAttributes, parameter);
 1208                       break;
 1209                     case RTFAttribute.D_META:
 1210                       mockery.backing = parserState;
 1211                       ok = attr.set(mockery, parameter);
 1212                       mockery.backing = null;
 1213                       break;
 1214                     case RTFAttribute.D_DOCUMENT:
 1215                       ok = attr.set(documentAttributes, parameter);
 1216                       break;
 1217                     default:
 1218                       /* should never happen */
 1219                       ok = false;
 1220                       break;
 1221                   }
 1222                   if (ok)
 1223                       return true;
 1224               }
 1225           }
 1226   
 1227           if (keyword.equals("fs")) {
 1228               StyleConstants.setFontSize(characterAttributes, (parameter / 2));
 1229               return true;
 1230           }
 1231   
 1232           /* TODO: superscript/subscript */
 1233   
 1234           if (keyword.equals("sl")) {
 1235               if (parameter == 1000) {  /* magic value! */
 1236                   characterAttributes.removeAttribute(StyleConstants.LineSpacing);
 1237               } else {
 1238                   /* TODO: The RTF sl attribute has special meaning if it's
 1239                      negative. Make sure that SwingText has the same special
 1240                      meaning, or find a way to imitate that. When SwingText
 1241                      handles this, also recognize the slmult keyword. */
 1242                   StyleConstants.setLineSpacing(characterAttributes,
 1243                                                 parameter / 20f);
 1244               }
 1245               return true;
 1246           }
 1247   
 1248           /* TODO: Other kinds of underlining */
 1249   
 1250           if (keyword.equals("tx") || keyword.equals("tb")) {
 1251               float tabPosition = parameter / 20f;
 1252               int tabAlignment, tabLeader;
 1253               Number item;
 1254   
 1255               tabAlignment = TabStop.ALIGN_LEFT;
 1256               item = (Number)(parserState.get("tab_alignment"));
 1257               if (item != null)
 1258                   tabAlignment = item.intValue();
 1259               tabLeader = TabStop.LEAD_NONE;
 1260               item = (Number)(parserState.get("tab_leader"));
 1261               if (item != null)
 1262                   tabLeader = item.intValue();
 1263               if (keyword.equals("tb"))
 1264                   tabAlignment = TabStop.ALIGN_BAR;
 1265   
 1266               parserState.remove("tab_alignment");
 1267               parserState.remove("tab_leader");
 1268   
 1269               TabStop newStop = new TabStop(tabPosition, tabAlignment, tabLeader);
 1270               Dictionary tabs;
 1271               Integer stopCount;
 1272   
 1273               tabs = (Dictionary)parserState.get("_tabs");
 1274               if (tabs == null) {
 1275                   tabs = new Hashtable();
 1276                   parserState.put("_tabs", tabs);
 1277                   stopCount = Integer.valueOf(1);
 1278               } else {
 1279                   stopCount = (Integer)tabs.get("stop count");
 1280                   stopCount = Integer.valueOf(1 + stopCount.intValue());
 1281               }
 1282               tabs.put(stopCount, newStop);
 1283               tabs.put("stop count", stopCount);
 1284               parserState.remove("_tabs_immutable");
 1285   
 1286               return true;
 1287           }
 1288   
 1289           if (keyword.equals("s") &&
 1290               paragraphStyles != null) {
 1291               parserState.put("paragraphStyle", paragraphStyles[parameter]);
 1292               return true;
 1293           }
 1294   
 1295           if (keyword.equals("cs") &&
 1296               characterStyles != null) {
 1297               parserState.put("characterStyle", characterStyles[parameter]);
 1298               return true;
 1299           }
 1300   
 1301           if (keyword.equals("ds") &&
 1302               sectionStyles != null) {
 1303               parserState.put("sectionStyle", sectionStyles[parameter]);
 1304               return true;
 1305           }
 1306   
 1307           return false;
 1308       }
 1309   
 1310       /** Returns a new MutableAttributeSet containing the
 1311        *  default character attributes */
 1312       protected MutableAttributeSet rootCharacterAttributes()
 1313       {
 1314           MutableAttributeSet set = new SimpleAttributeSet();
 1315   
 1316           /* TODO: default font */
 1317   
 1318           StyleConstants.setItalic(set, false);
 1319           StyleConstants.setBold(set, false);
 1320           StyleConstants.setUnderline(set, false);
 1321           StyleConstants.setForeground(set, defaultColor());
 1322   
 1323           return set;
 1324       }
 1325   
 1326       /** Returns a new MutableAttributeSet containing the
 1327        *  default paragraph attributes */
 1328       protected MutableAttributeSet rootParagraphAttributes()
 1329       {
 1330           MutableAttributeSet set = new SimpleAttributeSet();
 1331   
 1332           StyleConstants.setLeftIndent(set, 0f);
 1333           StyleConstants.setRightIndent(set, 0f);
 1334           StyleConstants.setFirstLineIndent(set, 0f);
 1335   
 1336           /* TODO: what should this be, really? */
 1337           set.setResolveParent(target.getStyle(StyleContext.DEFAULT_STYLE));
 1338   
 1339           return set;
 1340       }
 1341   
 1342       /** Returns a new MutableAttributeSet containing the
 1343        *  default section attributes */
 1344       protected MutableAttributeSet rootSectionAttributes()
 1345       {
 1346           MutableAttributeSet set = new SimpleAttributeSet();
 1347   
 1348           return set;
 1349       }
 1350   
 1351       /**
 1352        * Calculates the current text (character) attributes in a form suitable
 1353        * for SwingText from the current parser state.
 1354        *
 1355        * @returns a new MutableAttributeSet containing the text attributes.
 1356        */
 1357       MutableAttributeSet currentTextAttributes()
 1358       {
 1359           MutableAttributeSet attributes =
 1360               new SimpleAttributeSet(characterAttributes);
 1361           Integer fontnum;
 1362           Integer stateItem;
 1363   
 1364           /* figure out the font name */
 1365           /* TODO: catch exceptions for undefined attributes,
 1366              bad font indices, etc.? (as it stands, it is the caller's
 1367              job to clean up after corrupt RTF) */
 1368           fontnum = (Integer)parserState.get("f");
 1369           /* note setFontFamily() can not handle a null font */
 1370           String fontFamily;
 1371           if (fontnum != null)
 1372               fontFamily = (String)fontTable.get(fontnum);
 1373           else
 1374               fontFamily = null;
 1375           if (fontFamily != null)
 1376               StyleConstants.setFontFamily(attributes, fontFamily);
 1377           else
 1378               attributes.removeAttribute(StyleConstants.FontFamily);
 1379   
 1380           if (colorTable != null) {
 1381               stateItem = (Integer)parserState.get("cf");
 1382               if (stateItem != null) {
 1383                   Color fg = colorTable[stateItem.intValue()];
 1384                   StyleConstants.setForeground(attributes, fg);
 1385               } else {
 1386                   /* AttributeSet dies if you set a value to null */
 1387                   attributes.removeAttribute(StyleConstants.Foreground);
 1388               }
 1389           }
 1390   
 1391           if (colorTable != null) {
 1392               stateItem = (Integer)parserState.get("cb");
 1393               if (stateItem != null) {
 1394                   Color bg = colorTable[stateItem.intValue()];
 1395                   attributes.addAttribute(StyleConstants.Background,
 1396                                           bg);
 1397               } else {
 1398                   /* AttributeSet dies if you set a value to null */
 1399                   attributes.removeAttribute(StyleConstants.Background);
 1400               }
 1401           }
 1402   
 1403           Style characterStyle = (Style)parserState.get("characterStyle");
 1404           if (characterStyle != null)
 1405               attributes.setResolveParent(characterStyle);
 1406   
 1407           /* Other attributes are maintained directly in "attributes" */
 1408   
 1409           return attributes;
 1410       }
 1411   
 1412       /**
 1413        * Calculates the current paragraph attributes (with keys
 1414        * as given in StyleConstants) from the current parser state.
 1415        *
 1416        * @returns a newly created MutableAttributeSet.
 1417        * @see StyleConstants
 1418        */
 1419       MutableAttributeSet currentParagraphAttributes()
 1420       {
 1421           /* NB if there were a mutableCopy() method we should use it */
 1422           MutableAttributeSet bld = new SimpleAttributeSet(paragraphAttributes);
 1423   
 1424           Integer stateItem;
 1425   
 1426           /*** Tab stops ***/
 1427           TabStop tabs[];
 1428   
 1429           tabs = (TabStop[])parserState.get("_tabs_immutable");
 1430           if (tabs == null) {
 1431               Dictionary workingTabs = (Dictionary)parserState.get("_tabs");
 1432               if (workingTabs != null) {
 1433                   int count = ((Integer)workingTabs.get("stop count")).intValue();
 1434                   tabs = new TabStop[count];
 1435                   for (int ix = 1; ix <= count; ix ++)
 1436                       tabs[ix-1] = (TabStop)workingTabs.get(Integer.valueOf(ix));
 1437                   parserState.put("_tabs_immutable", tabs);
 1438               }
 1439           }
 1440           if (tabs != null)
 1441               bld.addAttribute(Constants.Tabs, tabs);
 1442   
 1443           Style paragraphStyle = (Style)parserState.get("paragraphStyle");
 1444           if (paragraphStyle != null)
 1445               bld.setResolveParent(paragraphStyle);
 1446   
 1447           return bld;
 1448       }
 1449   
 1450       /**
 1451        * Calculates the current section attributes
 1452        * from the current parser state.
 1453        *
 1454        * @returns a newly created MutableAttributeSet.
 1455        */
 1456       public AttributeSet currentSectionAttributes()
 1457       {
 1458           MutableAttributeSet attributes = new SimpleAttributeSet(sectionAttributes);
 1459   
 1460           Style sectionStyle = (Style)parserState.get("sectionStyle");
 1461           if (sectionStyle != null)
 1462               attributes.setResolveParent(sectionStyle);
 1463   
 1464           return attributes;
 1465       }
 1466   
 1467       /** Resets the filter's internal notion of the current character
 1468        *  attributes to their default values. Invoked to handle the
 1469        *  \plain keyword. */
 1470       protected void resetCharacterAttributes()
 1471       {
 1472           handleKeyword("f", 0);
 1473           handleKeyword("cf", 0);
 1474   
 1475           handleKeyword("fs", 24);  /* 12 pt. */
 1476   
 1477           Enumeration attributes = straightforwardAttributes.elements();
 1478           while(attributes.hasMoreElements()) {
 1479               RTFAttribute attr = (RTFAttribute)attributes.nextElement();
 1480               if (attr.domain() == RTFAttribute.D_CHARACTER)
 1481                   attr.setDefault(characterAttributes);
 1482           }
 1483   
 1484           handleKeyword("sl", 1000);
 1485   
 1486           parserState.remove("characterStyle");
 1487       }
 1488   
 1489       /** Resets the filter's internal notion of the current paragraph's
 1490        *  attributes to their default values. Invoked to handle the
 1491        *  \pard keyword. */
 1492       protected void resetParagraphAttributes()
 1493       {
 1494           parserState.remove("_tabs");
 1495           parserState.remove("_tabs_immutable");
 1496           parserState.remove("paragraphStyle");
 1497   
 1498           StyleConstants.setAlignment(paragraphAttributes,
 1499                                       StyleConstants.ALIGN_LEFT);
 1500   
 1501           Enumeration attributes = straightforwardAttributes.elements();
 1502           while(attributes.hasMoreElements()) {
 1503               RTFAttribute attr = (RTFAttribute)attributes.nextElement();
 1504               if (attr.domain() == RTFAttribute.D_PARAGRAPH)
 1505                   attr.setDefault(characterAttributes);
 1506           }
 1507       }
 1508   
 1509       /** Resets the filter's internal notion of the current section's
 1510        *  attributes to their default values. Invoked to handle the
 1511        *  \sectd keyword. */
 1512       protected void resetSectionAttributes()
 1513       {
 1514           Enumeration attributes = straightforwardAttributes.elements();
 1515           while(attributes.hasMoreElements()) {
 1516               RTFAttribute attr = (RTFAttribute)attributes.nextElement();
 1517               if (attr.domain() == RTFAttribute.D_SECTION)
 1518                   attr.setDefault(characterAttributes);
 1519           }
 1520   
 1521           parserState.remove("sectionStyle");
 1522       }
 1523   }
 1524   
 1525   /** RTFReader.TextHandlingDestination provides basic text handling
 1526    *  functionality. Subclasses must implement: <dl>
 1527    *  <dt>deliverText()<dd>to handle a run of text with the same
 1528    *                       attributes
 1529    *  <dt>finishParagraph()<dd>to end the current paragraph and
 1530    *                           set the paragraph's attributes
 1531    *  <dt>endSection()<dd>to end the current section
 1532    *  </dl>
 1533    */
 1534   abstract class TextHandlingDestination
 1535       extends AttributeTrackingDestination
 1536       implements Destination
 1537   {
 1538       /** <code>true</code> if the reader has not just finished
 1539        *  a paragraph; false upon startup */
 1540       boolean inParagraph;
 1541   
 1542       public TextHandlingDestination()
 1543       {
 1544           super();
 1545           inParagraph = false;
 1546       }
 1547   
 1548       public void handleText(String text)
 1549       {
 1550           if (! inParagraph)
 1551               beginParagraph();
 1552   
 1553           deliverText(text, currentTextAttributes());
 1554       }
 1555   
 1556       abstract void deliverText(String text, AttributeSet characterAttributes);
 1557   
 1558       public void close()
 1559       {
 1560           if (inParagraph)
 1561               endParagraph();
 1562   
 1563           super.close();
 1564       }
 1565   
 1566       public boolean handleKeyword(String keyword)
 1567       {
 1568           if (keyword.equals("\r") || keyword.equals("\n")) {
 1569               keyword = "par";
 1570           }
 1571   
 1572           if (keyword.equals("par")) {
 1573   //          warnings.println("Ending paragraph.");
 1574               endParagraph();
 1575               return true;
 1576           }
 1577   
 1578           if (keyword.equals("sect")) {
 1579   //          warnings.println("Ending section.");
 1580               endSection();
 1581               return true;
 1582           }
 1583   
 1584           return super.handleKeyword(keyword);
 1585       }
 1586   
 1587       protected void beginParagraph()
 1588       {
 1589           inParagraph = true;
 1590       }
 1591   
 1592       protected void endParagraph()
 1593       {
 1594           AttributeSet pgfAttributes = currentParagraphAttributes();
 1595           AttributeSet chrAttributes = currentTextAttributes();
 1596           finishParagraph(pgfAttributes, chrAttributes);
 1597           inParagraph = false;
 1598       }
 1599   
 1600       abstract void finishParagraph(AttributeSet pgfA, AttributeSet chrA);
 1601   
 1602       abstract void endSection();
 1603   }
 1604   
 1605   /** RTFReader.DocumentDestination is a concrete subclass of
 1606    *  TextHandlingDestination which appends the text to the
 1607    *  StyledDocument given by the <code>target</code> ivar of the
 1608    *  containing RTFReader.
 1609    */
 1610   class DocumentDestination
 1611       extends TextHandlingDestination
 1612       implements Destination
 1613   {
 1614       public void deliverText(String text, AttributeSet characterAttributes)
 1615       {
 1616           try {
 1617               target.insertString(target.getLength(),
 1618                                   text,
 1619                                   currentTextAttributes());
 1620           } catch (BadLocationException ble) {
 1621               /* This shouldn't be able to happen, of course */
 1622               /* TODO is InternalError the correct error to throw? */
 1623               throw new InternalError(ble.getMessage());
 1624           }
 1625       }
 1626   
 1627       public void finishParagraph(AttributeSet pgfAttributes,
 1628                                   AttributeSet chrAttributes)
 1629       {
 1630           int pgfEndPosition = target.getLength();
 1631           try {
 1632               target.insertString(pgfEndPosition, "\n", chrAttributes);
 1633               target.setParagraphAttributes(pgfEndPosition, 1, pgfAttributes, true);
 1634           } catch (BadLocationException ble) {
 1635               /* This shouldn't be able to happen, of course */
 1636               /* TODO is InternalError the correct error to throw? */
 1637               throw new InternalError(ble.getMessage());
 1638           }
 1639       }
 1640   
 1641       public void endSection()
 1642       {
 1643           /* If we implemented sections, we'd end 'em here */
 1644       }
 1645   }
 1646   
 1647   }

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