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

    1   /*
    2    * Copyright (c) 1997, 2011, 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.html;
   26   
   27   import sun.swing.SwingUtilities2;
   28   import java.util;
   29   import java.awt;
   30   import java.io;
   31   import java.net;
   32   import javax.swing.Icon;
   33   import javax.swing.ImageIcon;
   34   import javax.swing.UIManager;
   35   import javax.swing.border;
   36   import javax.swing.event.ChangeListener;
   37   import javax.swing.text;
   38   
   39   /**
   40    * Support for defining the visual characteristics of
   41    * HTML views being rendered.  The StyleSheet is used to
   42    * translate the HTML model into visual characteristics.
   43    * This enables views to be customized by a look-and-feel,
   44    * multiple views over the same model can be rendered
   45    * differently, etc.  This can be thought of as a CSS
   46    * rule repository.  The key for CSS attributes is an
   47    * object of type CSS.Attribute.  The type of the value
   48    * is up to the StyleSheet implementation, but the
   49    * <code>toString</code> method is required
   50    * to return a string representation of CSS value.
   51    * <p>
   52    * The primary entry point for HTML View implementations
   53    * to get their attributes is the
   54    * {@link #getViewAttributes getViewAttributes}
   55    * method.  This should be implemented to establish the
   56    * desired policy used to associate attributes with the view.
   57    * Each HTMLEditorKit (i.e. and therefore each associated
   58    * JEditorPane) can have its own StyleSheet, but by default one
   59    * sheet will be shared by all of the HTMLEditorKit instances.
   60    * HTMLDocument instance can also have a StyleSheet, which
   61    * holds the document-specific CSS specifications.
   62    * <p>
   63    * In order for Views to store less state and therefore be
   64    * more lightweight, the StyleSheet can act as a factory for
   65    * painters that handle some of the rendering tasks.  This allows
   66    * implementations to determine what they want to cache
   67    * and have the sharing potentially at the level that a
   68    * selector is common to multiple views.  Since the StyleSheet
   69    * may be used by views over multiple documents and typically
   70    * the HTML attributes don't effect the selector being used,
   71    * the potential for sharing is significant.
   72    * <p>
   73    * The rules are stored as named styles, and other information
   74    * is stored to translate the context of an element to a
   75    * rule quickly.  The following code fragment will display
   76    * the named styles, and therefore the CSS rules contained.
   77    * <code><pre>
   78    * &nbsp;
   79    * &nbsp; import java.util.*;
   80    * &nbsp; import javax.swing.text.*;
   81    * &nbsp; import javax.swing.text.html.*;
   82    * &nbsp;
   83    * &nbsp; public class ShowStyles {
   84    * &nbsp;
   85    * &nbsp;     public static void main(String[] args) {
   86    * &nbsp;       HTMLEditorKit kit = new HTMLEditorKit();
   87    * &nbsp;       HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
   88    * &nbsp;       StyleSheet styles = doc.getStyleSheet();
   89    * &nbsp;
   90    * &nbsp;       Enumeration rules = styles.getStyleNames();
   91    * &nbsp;       while (rules.hasMoreElements()) {
   92    * &nbsp;           String name = (String) rules.nextElement();
   93    * &nbsp;           Style rule = styles.getStyle(name);
   94    * &nbsp;           System.out.println(rule.toString());
   95    * &nbsp;       }
   96    * &nbsp;       System.exit(0);
   97    * &nbsp;     }
   98    * &nbsp; }
   99    * &nbsp;
  100    * </pre></code>
  101    * <p>
  102    * The semantics for when a CSS style should overide visual attributes
  103    * defined by an element are not well defined. For example, the html
  104    * <code>&lt;body bgcolor=red&gt;</code> makes the body have a red
  105    * background. But if the html file also contains the CSS rule
  106    * <code>body { background: blue }</code> it becomes less clear as to
  107    * what color the background of the body should be. The current
  108    * implemention gives visual attributes defined in the element the
  109    * highest precedence, that is they are always checked before any styles.
  110    * Therefore, in the previous example the background would have a
  111    * red color as the body element defines the background color to be red.
  112    * <p>
  113    * As already mentioned this supports CSS. We don't support the full CSS
  114    * spec. Refer to the javadoc of the CSS class to see what properties
  115    * we support. The two major CSS parsing related
  116    * concepts we do not currently
  117    * support are pseudo selectors, such as <code>A:link { color: red }</code>,
  118    * and the <code>important</code> modifier.
  119    * <p>
  120    * <font color="red">Note: This implementation is currently
  121    * incomplete.  It can be replaced with alternative implementations
  122    * that are complete.  Future versions of this class will provide
  123    * better CSS support.</font>
  124    *
  125    * @author  Timothy Prinzing
  126    * @author  Sunita Mani
  127    * @author  Sara Swanson
  128    * @author  Jill Nakata
  129    */
  130   public class StyleSheet extends StyleContext {
  131       // As the javadoc states, this class maintains a mapping between
  132       // a CSS selector (such as p.bar) and a Style.
  133       // This consists of a number of parts:
  134       // . Each selector is broken down into its constituent simple selectors,
  135       //   and stored in an inverted graph, for example:
  136       //     p { color: red } ol p { font-size: 10pt } ul p { font-size: 12pt }
  137       //   results in the graph:
  138       //          root
  139       //           |
  140       //           p
  141       //          / \
  142       //         ol ul
  143       //   each node (an instance of SelectorMapping) has an associated
  144       //   specificity and potentially a Style.
  145       // . Every rule that is asked for (either by way of getRule(String) or
  146       //   getRule(HTML.Tag, Element)) results in a unique instance of
  147       //   ResolvedStyle. ResolvedStyles contain the AttributeSets from the
  148       //   SelectorMapping.
  149       // . When a new rule is created it is inserted into the graph, and
  150       //   the AttributeSets of each ResolvedStyles are updated appropriately.
  151       // . This class creates special AttributeSets, LargeConversionSet and
  152       //   SmallConversionSet, that maintain a mapping between StyleConstants
  153       //   and CSS so that developers that wish to use the StyleConstants
  154       //   methods can do so.
  155       // . When one of the AttributeSets is mutated by way of a
  156       //   StyleConstants key, all the associated CSS keys are removed. This is
  157       //   done so that the two representations don't get out of sync. For
  158       //   example, if the developer adds StyleConsants.BOLD, FALSE to an
  159       //   AttributeSet that contains HTML.Tag.B, the HTML.Tag.B entry will
  160       //   be removed.
  161   
  162       /**
  163        * Construct a StyleSheet
  164        */
  165       public StyleSheet() {
  166           super();
  167           selectorMapping = new SelectorMapping(0);
  168           resolvedStyles = new Hashtable<String, ResolvedStyle>();
  169           if (css == null) {
  170               css = new CSS();
  171           }
  172       }
  173   
  174       /**
  175        * Fetches the style to use to render the given type
  176        * of HTML tag.  The element given is representing
  177        * the tag and can be used to determine the nesting
  178        * for situations where the attributes will differ
  179        * if nesting inside of elements.
  180        *
  181        * @param t the type to translate to visual attributes
  182        * @param e the element representing the tag; the element
  183        *  can be used to determine the nesting for situations where
  184        *  the attributes will differ if nested inside of other
  185        *  elements
  186        * @return the set of CSS attributes to use to render
  187        *  the tag
  188        */
  189       public Style getRule(HTML.Tag t, Element e) {
  190           SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  191   
  192           try {
  193               // Build an array of all the parent elements.
  194               Vector<Element> searchContext = sb.getVector();
  195   
  196               for (Element p = e; p != null; p = p.getParentElement()) {
  197                   searchContext.addElement(p);
  198               }
  199   
  200               // Build a fully qualified selector.
  201               int              n = searchContext.size();
  202               StringBuffer     cacheLookup = sb.getStringBuffer();
  203               AttributeSet     attr;
  204               String           eName;
  205               Object           name;
  206   
  207               // >= 1 as the HTML.Tag for the 0th element is passed in.
  208               for (int counter = n - 1; counter >= 1; counter--) {
  209                   e = searchContext.elementAt(counter);
  210                   attr = e.getAttributes();
  211                   name = attr.getAttribute(StyleConstants.NameAttribute);
  212                   eName = name.toString();
  213                   cacheLookup.append(eName);
  214                   if (attr != null) {
  215                       if (attr.isDefined(HTML.Attribute.ID)) {
  216                           cacheLookup.append('#');
  217                           cacheLookup.append(attr.getAttribute
  218                                              (HTML.Attribute.ID));
  219                       }
  220                       else if (attr.isDefined(HTML.Attribute.CLASS)) {
  221                           cacheLookup.append('.');
  222                           cacheLookup.append(attr.getAttribute
  223                                              (HTML.Attribute.CLASS));
  224                       }
  225                   }
  226                   cacheLookup.append(' ');
  227               }
  228               cacheLookup.append(t.toString());
  229               e = searchContext.elementAt(0);
  230               attr = e.getAttributes();
  231               if (e.isLeaf()) {
  232                   // For leafs, we use the second tier attributes.
  233                   Object testAttr = attr.getAttribute(t);
  234                   if (testAttr instanceof AttributeSet) {
  235                       attr = (AttributeSet)testAttr;
  236                   }
  237                   else {
  238                       attr = null;
  239                   }
  240               }
  241               if (attr != null) {
  242                   if (attr.isDefined(HTML.Attribute.ID)) {
  243                       cacheLookup.append('#');
  244                       cacheLookup.append(attr.getAttribute(HTML.Attribute.ID));
  245                   }
  246                   else if (attr.isDefined(HTML.Attribute.CLASS)) {
  247                       cacheLookup.append('.');
  248                       cacheLookup.append(attr.getAttribute
  249                                          (HTML.Attribute.CLASS));
  250                   }
  251               }
  252   
  253               Style style = getResolvedStyle(cacheLookup.toString(),
  254                                              searchContext, t);
  255               return style;
  256           }
  257           finally {
  258               SearchBuffer.releaseSearchBuffer(sb);
  259           }
  260       }
  261   
  262       /**
  263        * Fetches the rule that best matches the selector given
  264        * in string form. Where <code>selector</code> is a space separated
  265        * String of the element names. For example, <code>selector</code>
  266        * might be 'html body tr td''<p>
  267        * The attributes of the returned Style will change
  268        * as rules are added and removed. That is if you to ask for a rule
  269        * with a selector "table p" and a new rule was added with a selector
  270        * of "p" the returned Style would include the new attributes from
  271        * the rule "p".
  272        */
  273       public Style getRule(String selector) {
  274           selector = cleanSelectorString(selector);
  275           if (selector != null) {
  276               Style style = getResolvedStyle(selector);
  277               return style;
  278           }
  279           return null;
  280       }
  281   
  282       /**
  283        * Adds a set of rules to the sheet.  The rules are expected to
  284        * be in valid CSS format.  Typically this would be called as
  285        * a result of parsing a &lt;style&gt; tag.
  286        */
  287       public void addRule(String rule) {
  288           if (rule != null) {
  289               //tweaks to control display properties
  290               //see BasicEditorPaneUI
  291               final String baseUnitsDisable = "BASE_SIZE_DISABLE";
  292               final String baseUnits = "BASE_SIZE ";
  293               final String w3cLengthUnitsEnable = "W3C_LENGTH_UNITS_ENABLE";
  294               final String w3cLengthUnitsDisable = "W3C_LENGTH_UNITS_DISABLE";
  295               if (rule == baseUnitsDisable) {
  296                   sizeMap = sizeMapDefault;
  297               } else if (rule.startsWith(baseUnits)) {
  298                   rebaseSizeMap(Integer.
  299                                 parseInt(rule.substring(baseUnits.length())));
  300               } else if (rule == w3cLengthUnitsEnable) {
  301                   w3cLengthUnits = true;
  302               } else if (rule == w3cLengthUnitsDisable) {
  303                   w3cLengthUnits = false;
  304               } else {
  305                   CssParser parser = new CssParser();
  306                   try {
  307                       parser.parse(getBase(), new StringReader(rule), false, false);
  308                   } catch (IOException ioe) { }
  309               }
  310           }
  311       }
  312   
  313       /**
  314        * Translates a CSS declaration to an AttributeSet that represents
  315        * the CSS declaration.  Typically this would be called as a
  316        * result of encountering an HTML style attribute.
  317        */
  318       public AttributeSet getDeclaration(String decl) {
  319           if (decl == null) {
  320               return SimpleAttributeSet.EMPTY;
  321           }
  322           CssParser parser = new CssParser();
  323           return parser.parseDeclaration(decl);
  324       }
  325   
  326       /**
  327        * Loads a set of rules that have been specified in terms of
  328        * CSS1 grammar.  If there are collisions with existing rules,
  329        * the newly specified rule will win.
  330        *
  331        * @param in the stream to read the CSS grammar from
  332        * @param ref the reference URL.  This value represents the
  333        *  location of the stream and may be null.  All relative
  334        *  URLs specified in the stream will be based upon this
  335        *  parameter.
  336        */
  337       public void loadRules(Reader in, URL ref) throws IOException {
  338           CssParser parser = new CssParser();
  339           parser.parse(ref, in, false, false);
  340       }
  341   
  342       /**
  343        * Fetches a set of attributes to use in the view for
  344        * displaying.  This is basically a set of attributes that
  345        * can be used for View.getAttributes.
  346        */
  347       public AttributeSet getViewAttributes(View v) {
  348           return new ViewAttributeSet(v);
  349       }
  350   
  351       /**
  352        * Removes a named style previously added to the document.
  353        *
  354        * @param nm  the name of the style to remove
  355        */
  356       public void removeStyle(String nm) {
  357           Style       aStyle = getStyle(nm);
  358   
  359           if (aStyle != null) {
  360               String selector = cleanSelectorString(nm);
  361               String[] selectors = getSimpleSelectors(selector);
  362               synchronized(this) {
  363                   SelectorMapping mapping = getRootSelectorMapping();
  364                   for (int i = selectors.length - 1; i >= 0; i--) {
  365                       mapping = mapping.getChildSelectorMapping(selectors[i],
  366                                                                 true);
  367                   }
  368                   Style rule = mapping.getStyle();
  369                   if (rule != null) {
  370                       mapping.setStyle(null);
  371                       if (resolvedStyles.size() > 0) {
  372                           Enumeration<ResolvedStyle> values = resolvedStyles.elements();
  373                           while (values.hasMoreElements()) {
  374                               ResolvedStyle style = values.nextElement();
  375                               style.removeStyle(rule);
  376                           }
  377                       }
  378                   }
  379               }
  380           }
  381           super.removeStyle(nm);
  382       }
  383   
  384       /**
  385        * Adds the rules from the StyleSheet <code>ss</code> to those of
  386        * the receiver. <code>ss's</code> rules will override the rules of
  387        * any previously added style sheets. An added StyleSheet will never
  388        * override the rules of the receiving style sheet.
  389        *
  390        * @since 1.3
  391        */
  392       public void addStyleSheet(StyleSheet ss) {
  393           synchronized(this) {
  394               if (linkedStyleSheets == null) {
  395                   linkedStyleSheets = new Vector<StyleSheet>();
  396               }
  397               if (!linkedStyleSheets.contains(ss)) {
  398                   int index = 0;
  399                   if (ss instanceof javax.swing.plaf.UIResource
  400                       && linkedStyleSheets.size() > 1) {
  401                       index = linkedStyleSheets.size() - 1;
  402                   }
  403                   linkedStyleSheets.insertElementAt(ss, index);
  404                   linkStyleSheetAt(ss, index);
  405               }
  406           }
  407       }
  408   
  409       /**
  410        * Removes the StyleSheet <code>ss</code> from those of the receiver.
  411        *
  412        * @since 1.3
  413        */
  414       public void removeStyleSheet(StyleSheet ss) {
  415           synchronized(this) {
  416               if (linkedStyleSheets != null) {
  417                   int index = linkedStyleSheets.indexOf(ss);
  418                   if (index != -1) {
  419                       linkedStyleSheets.removeElementAt(index);
  420                       unlinkStyleSheet(ss, index);
  421                       if (index == 0 && linkedStyleSheets.size() == 0) {
  422                           linkedStyleSheets = null;
  423                       }
  424                   }
  425               }
  426           }
  427       }
  428   
  429       //
  430       // The following is used to import style sheets.
  431       //
  432   
  433       /**
  434        * Returns an array of the linked StyleSheets. Will return null
  435        * if there are no linked StyleSheets.
  436        *
  437        * @since 1.3
  438        */
  439       public StyleSheet[] getStyleSheets() {
  440           StyleSheet[] retValue;
  441   
  442           synchronized(this) {
  443               if (linkedStyleSheets != null) {
  444                   retValue = new StyleSheet[linkedStyleSheets.size()];
  445                   linkedStyleSheets.copyInto(retValue);
  446               }
  447               else {
  448                   retValue = null;
  449               }
  450           }
  451           return retValue;
  452       }
  453   
  454       /**
  455        * Imports a style sheet from <code>url</code>. The resulting rules
  456        * are directly added to the receiver. If you do not want the rules
  457        * to become part of the receiver, create a new StyleSheet and use
  458        * addStyleSheet to link it in.
  459        *
  460        * @since 1.3
  461        */
  462       public void importStyleSheet(URL url) {
  463           try {
  464               InputStream is;
  465   
  466               is = url.openStream();
  467               Reader r = new BufferedReader(new InputStreamReader(is));
  468               CssParser parser = new CssParser();
  469               parser.parse(url, r, false, true);
  470               r.close();
  471               is.close();
  472           } catch (Throwable e) {
  473               // on error we simply have no styles... the html
  474               // will look mighty wrong but still function.
  475           }
  476       }
  477   
  478       /**
  479        * Sets the base. All import statements that are relative, will be
  480        * relative to <code>base</code>.
  481        *
  482        * @since 1.3
  483        */
  484       public void setBase(URL base) {
  485           this.base = base;
  486       }
  487   
  488       /**
  489        * Returns the base.
  490        *
  491        * @since 1.3
  492        */
  493       public URL getBase() {
  494           return base;
  495       }
  496   
  497       /**
  498        * Adds a CSS attribute to the given set.
  499        *
  500        * @since 1.3
  501        */
  502       public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key,
  503                                   String value) {
  504           css.addInternalCSSValue(attr, key, value);
  505       }
  506   
  507       /**
  508        * Adds a CSS attribute to the given set.
  509        *
  510        * @since 1.3
  511        */
  512       public boolean addCSSAttributeFromHTML(MutableAttributeSet attr,
  513                                              CSS.Attribute key, String value) {
  514           Object iValue = css.getCssValue(key, value);
  515           if (iValue != null) {
  516               attr.addAttribute(key, iValue);
  517               return true;
  518           }
  519           return false;
  520       }
  521   
  522       // ---- Conversion functionality ---------------------------------
  523   
  524       /**
  525        * Converts a set of HTML attributes to an equivalent
  526        * set of CSS attributes.
  527        *
  528        * @param htmlAttrSet AttributeSet containing the HTML attributes.
  529        */
  530       public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) {
  531           AttributeSet cssAttrSet = css.translateHTMLToCSS(htmlAttrSet);
  532   
  533           MutableAttributeSet cssStyleSet = addStyle(null, null);
  534           cssStyleSet.addAttributes(cssAttrSet);
  535   
  536           return cssStyleSet;
  537       }
  538   
  539       /**
  540        * Adds an attribute to the given set, and returns
  541        * the new representative set.  This is reimplemented to
  542        * convert StyleConstant attributes to CSS prior to forwarding
  543        * to the superclass behavior.  The StyleConstants attribute
  544        * has no corresponding CSS entry, the StyleConstants attribute
  545        * is stored (but will likely be unused).
  546        *
  547        * @param old the old attribute set
  548        * @param key the non-null attribute key
  549        * @param value the attribute value
  550        * @return the updated attribute set
  551        * @see MutableAttributeSet#addAttribute
  552        */
  553       public AttributeSet addAttribute(AttributeSet old, Object key,
  554                                        Object value) {
  555           if (css == null) {
  556               // supers constructor will call this before returning,
  557               // and we need to make sure CSS is non null.
  558               css = new CSS();
  559           }
  560           if (key instanceof StyleConstants) {
  561               HTML.Tag tag = HTML.getTagForStyleConstantsKey(
  562                                   (StyleConstants)key);
  563   
  564               if (tag != null && old.isDefined(tag)) {
  565                   old = removeAttribute(old, tag);
  566               }
  567   
  568               Object cssValue = css.styleConstantsValueToCSSValue
  569                                 ((StyleConstants)key, value);
  570               if (cssValue != null) {
  571                   Object cssKey = css.styleConstantsKeyToCSSKey
  572                                       ((StyleConstants)key);
  573                   if (cssKey != null) {
  574                       return super.addAttribute(old, cssKey, cssValue);
  575                   }
  576               }
  577           }
  578           return super.addAttribute(old, key, value);
  579       }
  580   
  581       /**
  582        * Adds a set of attributes to the element.  If any of these attributes
  583        * are StyleConstants attributes, they will be converted to CSS prior
  584        * to forwarding to the superclass behavior.
  585        *
  586        * @param old the old attribute set
  587        * @param attr the attributes to add
  588        * @return the updated attribute set
  589        * @see MutableAttributeSet#addAttribute
  590        */
  591       public AttributeSet addAttributes(AttributeSet old, AttributeSet attr) {
  592           if (!(attr instanceof HTMLDocument.TaggedAttributeSet)) {
  593               old = removeHTMLTags(old, attr);
  594           }
  595           return super.addAttributes(old, convertAttributeSet(attr));
  596       }
  597   
  598       /**
  599        * Removes an attribute from the set.  If the attribute is a StyleConstants
  600        * attribute, the request will be converted to a CSS attribute prior to
  601        * forwarding to the superclass behavior.
  602        *
  603        * @param old the old set of attributes
  604        * @param key the non-null attribute name
  605        * @return the updated attribute set
  606        * @see MutableAttributeSet#removeAttribute
  607        */
  608       public AttributeSet removeAttribute(AttributeSet old, Object key) {
  609           if (key instanceof StyleConstants) {
  610               HTML.Tag tag = HTML.getTagForStyleConstantsKey(
  611                                      (StyleConstants)key);
  612               if (tag != null) {
  613                   old = super.removeAttribute(old, tag);
  614               }
  615   
  616               Object cssKey = css.styleConstantsKeyToCSSKey((StyleConstants)key);
  617               if (cssKey != null) {
  618                   return super.removeAttribute(old, cssKey);
  619               }
  620           }
  621           return super.removeAttribute(old, key);
  622       }
  623   
  624       /**
  625        * Removes a set of attributes for the element.  If any of the attributes
  626        * is a StyleConstants attribute, the request will be converted to a CSS
  627        * attribute prior to forwarding to the superclass behavior.
  628        *
  629        * @param old the old attribute set
  630        * @param names the attribute names
  631        * @return the updated attribute set
  632        * @see MutableAttributeSet#removeAttributes
  633        */
  634       public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) {
  635           // PENDING: Should really be doing something similar to
  636           // removeHTMLTags here, but it is rather expensive to have to
  637           // clone names
  638           return super.removeAttributes(old, names);
  639       }
  640   
  641       /**
  642        * Removes a set of attributes. If any of the attributes
  643        * is a StyleConstants attribute, the request will be converted to a CSS
  644        * attribute prior to forwarding to the superclass behavior.
  645        *
  646        * @param old the old attribute set
  647        * @param attrs the attributes
  648        * @return the updated attribute set
  649        * @see MutableAttributeSet#removeAttributes
  650        */
  651       public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs) {
  652           if (old != attrs) {
  653               old = removeHTMLTags(old, attrs);
  654           }
  655           return super.removeAttributes(old, convertAttributeSet(attrs));
  656       }
  657   
  658       /**
  659        * Creates a compact set of attributes that might be shared.
  660        * This is a hook for subclasses that want to alter the
  661        * behavior of SmallAttributeSet.  This can be reimplemented
  662        * to return an AttributeSet that provides some sort of
  663        * attribute conversion.
  664        *
  665        * @param a The set of attributes to be represented in the
  666        *  the compact form.
  667        */
  668       protected SmallAttributeSet createSmallAttributeSet(AttributeSet a) {
  669           return new SmallConversionSet(a);
  670       }
  671   
  672       /**
  673        * Creates a large set of attributes that should trade off
  674        * space for time.  This set will not be shared.  This is
  675        * a hook for subclasses that want to alter the behavior
  676        * of the larger attribute storage format (which is
  677        * SimpleAttributeSet by default).   This can be reimplemented
  678        * to return a MutableAttributeSet that provides some sort of
  679        * attribute conversion.
  680        *
  681        * @param a The set of attributes to be represented in the
  682        *  the larger form.
  683        */
  684       protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) {
  685           return new LargeConversionSet(a);
  686       }
  687   
  688       /**
  689        * For any StyleConstants key in attr that has an associated HTML.Tag,
  690        * it is removed from old. The resulting AttributeSet is then returned.
  691        */
  692       private AttributeSet removeHTMLTags(AttributeSet old, AttributeSet attr) {
  693           if (!(attr instanceof LargeConversionSet) &&
  694               !(attr instanceof SmallConversionSet)) {
  695               Enumeration names = attr.getAttributeNames();
  696   
  697               while (names.hasMoreElements()) {
  698                   Object key = names.nextElement();
  699   
  700                   if (key instanceof StyleConstants) {
  701                       HTML.Tag tag = HTML.getTagForStyleConstantsKey(
  702                           (StyleConstants)key);
  703   
  704                       if (tag != null && old.isDefined(tag)) {
  705                           old = super.removeAttribute(old, tag);
  706                       }
  707                   }
  708               }
  709           }
  710           return old;
  711       }
  712   
  713       /**
  714        * Converts a set of attributes (if necessary) so that
  715        * any attributes that were specified as StyleConstants
  716        * attributes and have a CSS mapping, will be converted
  717        * to CSS attributes.
  718        */
  719       AttributeSet convertAttributeSet(AttributeSet a) {
  720           if ((a instanceof LargeConversionSet) ||
  721               (a instanceof SmallConversionSet)) {
  722               // known to be converted.
  723               return a;
  724           }
  725           // in most cases, there are no StyleConstants attributes
  726           // so we iterate the collection of keys to avoid creating
  727           // a new set.
  728           Enumeration names = a.getAttributeNames();
  729           while (names.hasMoreElements()) {
  730               Object name = names.nextElement();
  731               if (name instanceof StyleConstants) {
  732                   // we really need to do a conversion, iterate again
  733                   // building a new set.
  734                   MutableAttributeSet converted = new LargeConversionSet();
  735                   Enumeration keys = a.getAttributeNames();
  736                   while (keys.hasMoreElements()) {
  737                       Object key = keys.nextElement();
  738                       Object cssValue = null;
  739                       if (key instanceof StyleConstants) {
  740                           // convert the StyleConstants attribute if possible
  741                           Object cssKey = css.styleConstantsKeyToCSSKey
  742                                               ((StyleConstants)key);
  743                           if (cssKey != null) {
  744                               Object value = a.getAttribute(key);
  745                               cssValue = css.styleConstantsValueToCSSValue
  746                                              ((StyleConstants)key, value);
  747                               if (cssValue != null) {
  748                                   converted.addAttribute(cssKey, cssValue);
  749                               }
  750                           }
  751                       }
  752                       if (cssValue == null) {
  753                           converted.addAttribute(key, a.getAttribute(key));
  754                       }
  755                   }
  756                   return converted;
  757               }
  758           }
  759           return a;
  760       }
  761   
  762       /**
  763        * Large set of attributes that does conversion of requests
  764        * for attributes of type StyleConstants.
  765        */
  766       class LargeConversionSet extends SimpleAttributeSet {
  767   
  768           /**
  769            * Creates a new attribute set based on a supplied set of attributes.
  770            *
  771            * @param source the set of attributes
  772            */
  773           public LargeConversionSet(AttributeSet source) {
  774               super(source);
  775           }
  776   
  777           public LargeConversionSet() {
  778               super();
  779           }
  780   
  781           /**
  782            * Checks whether a given attribute is defined.
  783            *
  784            * @param key the attribute key
  785            * @return true if the attribute is defined
  786            * @see AttributeSet#isDefined
  787            */
  788           public boolean isDefined(Object key) {
  789               if (key instanceof StyleConstants) {
  790                   Object cssKey = css.styleConstantsKeyToCSSKey
  791                                       ((StyleConstants)key);
  792                   if (cssKey != null) {
  793                       return super.isDefined(cssKey);
  794                   }
  795               }
  796               return super.isDefined(key);
  797           }
  798   
  799           /**
  800            * Gets the value of an attribute.
  801            *
  802            * @param key the attribute name
  803            * @return the attribute value
  804            * @see AttributeSet#getAttribute
  805            */
  806           public Object getAttribute(Object key) {
  807               if (key instanceof StyleConstants) {
  808                   Object cssKey = css.styleConstantsKeyToCSSKey
  809                                       ((StyleConstants)key);
  810                   if (cssKey != null) {
  811                       Object value = super.getAttribute(cssKey);
  812                       if (value != null) {
  813                           return css.cssValueToStyleConstantsValue
  814                                              ((StyleConstants)key, value);
  815                       }
  816                   }
  817               }
  818               return super.getAttribute(key);
  819           }
  820       }
  821   
  822       /**
  823        * Small set of attributes that does conversion of requests
  824        * for attributes of type StyleConstants.
  825        */
  826       class SmallConversionSet extends SmallAttributeSet {
  827   
  828           /**
  829            * Creates a new attribute set based on a supplied set of attributes.
  830            *
  831            * @param attrs the set of attributes
  832            */
  833           public SmallConversionSet(AttributeSet attrs) {
  834               super(attrs);
  835           }
  836   
  837           /**
  838            * Checks whether a given attribute is defined.
  839            *
  840            * @param key the attribute key
  841            * @return true if the attribute is defined
  842            * @see AttributeSet#isDefined
  843            */
  844           public boolean isDefined(Object key) {
  845               if (key instanceof StyleConstants) {
  846                   Object cssKey = css.styleConstantsKeyToCSSKey
  847                                       ((StyleConstants)key);
  848                   if (cssKey != null) {
  849                       return super.isDefined(cssKey);
  850                   }
  851               }
  852               return super.isDefined(key);
  853           }
  854   
  855           /**
  856            * Gets the value of an attribute.
  857            *
  858            * @param key the attribute name
  859            * @return the attribute value
  860            * @see AttributeSet#getAttribute
  861            */
  862           public Object getAttribute(Object key) {
  863               if (key instanceof StyleConstants) {
  864                   Object cssKey = css.styleConstantsKeyToCSSKey
  865                                       ((StyleConstants)key);
  866                   if (cssKey != null) {
  867                       Object value = super.getAttribute(cssKey);
  868                       if (value != null) {
  869                           return css.cssValueToStyleConstantsValue
  870                                              ((StyleConstants)key, value);
  871                       }
  872                   }
  873               }
  874               return super.getAttribute(key);
  875           }
  876       }
  877   
  878       // ---- Resource handling ----------------------------------------
  879   
  880       /**
  881        * Fetches the font to use for the given set of attributes.
  882        */
  883       public Font getFont(AttributeSet a) {
  884           return css.getFont(this, a, 12, this);
  885       }
  886   
  887       /**
  888        * Takes a set of attributes and turn it into a foreground color
  889        * specification.  This might be used to specify things
  890        * like brighter, more hue, etc.
  891        *
  892        * @param a the set of attributes
  893        * @return the color
  894        */
  895       public Color getForeground(AttributeSet a) {
  896           Color c = css.getColor(a, CSS.Attribute.COLOR);
  897           if (c == null) {
  898               return Color.black;
  899           }
  900           return c;
  901       }
  902   
  903       /**
  904        * Takes a set of attributes and turn it into a background color
  905        * specification.  This might be used to specify things
  906        * like brighter, more hue, etc.
  907        *
  908        * @param a the set of attributes
  909        * @return the color
  910        */
  911       public Color getBackground(AttributeSet a) {
  912           return css.getColor(a, CSS.Attribute.BACKGROUND_COLOR);
  913       }
  914   
  915       /**
  916        * Fetches the box formatter to use for the given set
  917        * of CSS attributes.
  918        */
  919       public BoxPainter getBoxPainter(AttributeSet a) {
  920           return new BoxPainter(a, css, this);
  921       }
  922   
  923       /**
  924        * Fetches the list formatter to use for the given set
  925        * of CSS attributes.
  926        */
  927       public ListPainter getListPainter(AttributeSet a) {
  928           return new ListPainter(a, this);
  929       }
  930   
  931       /**
  932        * Sets the base font size, with valid values between 1 and 7.
  933        */
  934       public void setBaseFontSize(int sz) {
  935           css.setBaseFontSize(sz);
  936       }
  937   
  938       /**
  939        * Sets the base font size from the passed in String. The string
  940        * can either identify a specific font size, with legal values between
  941        * 1 and 7, or identifiy a relative font size such as +1 or -2.
  942        */
  943       public void setBaseFontSize(String size) {
  944           css.setBaseFontSize(size);
  945       }
  946   
  947       public static int getIndexOfSize(float pt) {
  948           return CSS.getIndexOfSize(pt, sizeMapDefault);
  949       }
  950   
  951       /**
  952        * Returns the point size, given a size index.
  953        */
  954       public float getPointSize(int index) {
  955           return css.getPointSize(index, this);
  956       }
  957   
  958       /**
  959        *  Given a string such as "+2", "-2", or "2",
  960        *  returns a point size value.
  961        */
  962       public float getPointSize(String size) {
  963           return css.getPointSize(size, this);
  964       }
  965   
  966       /**
  967        * Converts a color string such as "RED" or "#NNNNNN" to a Color.
  968        * Note: This will only convert the HTML3.2 color strings
  969        *       or a string of length 7;
  970        *       otherwise, it will return null.
  971        */
  972       public Color stringToColor(String string) {
  973           return CSS.stringToColor(string);
  974       }
  975   
  976       /**
  977        * Returns the ImageIcon to draw in the background for
  978        * <code>attr</code>.
  979        */
  980       ImageIcon getBackgroundImage(AttributeSet attr) {
  981           Object value = attr.getAttribute(CSS.Attribute.BACKGROUND_IMAGE);
  982   
  983           if (value != null) {
  984               return ((CSS.BackgroundImage)value).getImage(getBase());
  985           }
  986           return null;
  987       }
  988   
  989       /**
  990        * Adds a rule into the StyleSheet.
  991        *
  992        * @param selector the selector to use for the rule.
  993        *  This will be a set of simple selectors, and must
  994        *  be a length of 1 or greater.
  995        * @param declaration the set of CSS attributes that
  996        *  make up the rule.
  997        */
  998       void addRule(String[] selector, AttributeSet declaration,
  999                    boolean isLinked) {
 1000           int n = selector.length;
 1001           StringBuilder sb = new StringBuilder();
 1002           sb.append(selector[0]);
 1003           for (int counter = 1; counter < n; counter++) {
 1004               sb.append(' ');
 1005               sb.append(selector[counter]);
 1006           }
 1007           String selectorName = sb.toString();
 1008           Style rule = getStyle(selectorName);
 1009           if (rule == null) {
 1010               // Notice how the rule is first created, and it not part of
 1011               // the synchronized block. It is done like this as creating
 1012               // a new rule will fire a ChangeEvent. We do not want to be
 1013               // holding the lock when calling to other objects, it can
 1014               // result in deadlock.
 1015               Style altRule = addStyle(selectorName, null);
 1016               synchronized(this) {
 1017                   SelectorMapping mapping = getRootSelectorMapping();
 1018                   for (int i = n - 1; i >= 0; i--) {
 1019                       mapping = mapping.getChildSelectorMapping
 1020                                         (selector[i], true);
 1021                   }
 1022                   rule = mapping.getStyle();
 1023                   if (rule == null) {
 1024                       rule = altRule;
 1025                       mapping.setStyle(rule);
 1026                       refreshResolvedRules(selectorName, selector, rule,
 1027                                            mapping.getSpecificity());
 1028                   }
 1029               }
 1030           }
 1031           if (isLinked) {
 1032               rule = getLinkedStyle(rule);
 1033           }
 1034           rule.addAttributes(declaration);
 1035       }
 1036   
 1037       //
 1038       // The following gaggle of methods is used in maintaing the rules from
 1039       // the sheet.
 1040       //
 1041   
 1042       /**
 1043        * Updates the attributes of the rules to reference any related
 1044        * rules in <code>ss</code>.
 1045        */
 1046       private synchronized void linkStyleSheetAt(StyleSheet ss, int index) {
 1047           if (resolvedStyles.size() > 0) {
 1048               Enumeration<ResolvedStyle> values = resolvedStyles.elements();
 1049               while (values.hasMoreElements()) {
 1050                   ResolvedStyle rule = values.nextElement();
 1051                   rule.insertExtendedStyleAt(ss.getRule(rule.getName()),
 1052                                              index);
 1053               }
 1054           }
 1055       }
 1056   
 1057       /**
 1058        * Removes references to the rules in <code>ss</code>.
 1059        * <code>index</code> gives the index the StyleSheet was at, that is
 1060        * how many StyleSheets had been added before it.
 1061        */
 1062       private synchronized void unlinkStyleSheet(StyleSheet ss, int index) {
 1063           if (resolvedStyles.size() > 0) {
 1064               Enumeration<ResolvedStyle> values = resolvedStyles.elements();
 1065               while (values.hasMoreElements()) {
 1066                   ResolvedStyle rule = values.nextElement();
 1067                   rule.removeExtendedStyleAt(index);
 1068               }
 1069           }
 1070       }
 1071   
 1072       /**
 1073        * Returns the simple selectors that comprise selector.
 1074        */
 1075       /* protected */
 1076       String[] getSimpleSelectors(String selector) {
 1077           selector = cleanSelectorString(selector);
 1078           SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
 1079           Vector<String> selectors = sb.getVector();
 1080           int lastIndex = 0;
 1081           int length = selector.length();
 1082           while (lastIndex != -1) {
 1083               int newIndex = selector.indexOf(' ', lastIndex);
 1084               if (newIndex != -1) {
 1085                   selectors.addElement(selector.substring(lastIndex, newIndex));
 1086                   if (++newIndex == length) {
 1087                       lastIndex = -1;
 1088                   }
 1089                   else {
 1090                       lastIndex = newIndex;
 1091                   }
 1092               }
 1093               else {
 1094                   selectors.addElement(selector.substring(lastIndex));
 1095                   lastIndex = -1;
 1096               }
 1097           }
 1098           String[] retValue = new String[selectors.size()];
 1099           selectors.copyInto(retValue);
 1100           SearchBuffer.releaseSearchBuffer(sb);
 1101           return retValue;
 1102       }
 1103   
 1104       /**
 1105        * Returns a string that only has one space between simple selectors,
 1106        * which may be the passed in String.
 1107        */
 1108       /*protected*/ String cleanSelectorString(String selector) {
 1109           boolean lastWasSpace = true;
 1110           for (int counter = 0, maxCounter = selector.length();
 1111                counter < maxCounter; counter++) {
 1112               switch(selector.charAt(counter)) {
 1113               case ' ':
 1114                   if (lastWasSpace) {
 1115                       return _cleanSelectorString(selector);
 1116                   }
 1117                   lastWasSpace = true;
 1118                   break;
 1119               case '\n':
 1120               case '\r':
 1121               case '\t':
 1122                   return _cleanSelectorString(selector);
 1123               default:
 1124                   lastWasSpace = false;
 1125               }
 1126           }
 1127           if (lastWasSpace) {
 1128               return _cleanSelectorString(selector);
 1129           }
 1130           // It was fine.
 1131           return selector;
 1132       }
 1133   
 1134       /**
 1135        * Returns a new String that contains only one space between non
 1136        * white space characters.
 1137        */
 1138       private String _cleanSelectorString(String selector) {
 1139           SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
 1140           StringBuffer buff = sb.getStringBuffer();
 1141           boolean lastWasSpace = true;
 1142           int lastIndex = 0;
 1143           char[] chars = selector.toCharArray();
 1144           int numChars = chars.length;
 1145           String retValue = null;
 1146           try {
 1147               for (int counter = 0; counter < numChars; counter++) {
 1148                   switch(chars[counter]) {
 1149                   case ' ':
 1150                       if (!lastWasSpace) {
 1151                           lastWasSpace = true;
 1152                           if (lastIndex < counter) {
 1153                               buff.append(chars, lastIndex,
 1154                                           1 + counter - lastIndex);
 1155                           }
 1156                       }
 1157                       lastIndex = counter + 1;
 1158                       break;
 1159                   case '\n':
 1160                   case '\r':
 1161                   case '\t':
 1162                       if (!lastWasSpace) {
 1163                           lastWasSpace = true;
 1164                           if (lastIndex < counter) {
 1165                               buff.append(chars, lastIndex,
 1166                                           counter - lastIndex);
 1167                               buff.append(' ');
 1168                           }
 1169                       }
 1170                       lastIndex = counter + 1;
 1171                       break;
 1172                   default:
 1173                       lastWasSpace = false;
 1174                       break;
 1175                   }
 1176               }
 1177               if (lastWasSpace && buff.length() > 0) {
 1178                   // Remove last space.
 1179                   buff.setLength(buff.length() - 1);
 1180               }
 1181               else if (lastIndex < numChars) {
 1182                   buff.append(chars, lastIndex, numChars - lastIndex);
 1183               }
 1184               retValue = buff.toString();
 1185           }
 1186           finally {
 1187               SearchBuffer.releaseSearchBuffer(sb);
 1188           }
 1189           return retValue;
 1190       }
 1191   
 1192       /**
 1193        * Returns the root selector mapping that all selectors are relative
 1194        * to. This is an inverted graph of the selectors.
 1195        */
 1196       private SelectorMapping getRootSelectorMapping() {
 1197           return selectorMapping;
 1198       }
 1199   
 1200       /**
 1201        * Returns the specificity of the passed in String. It assumes the
 1202        * passed in string doesn't contain junk, that is each selector is
 1203        * separated by a space and each selector at most contains one . or one
 1204        * #. A simple selector has a weight of 1, an id selector has a weight
 1205        * of 100, and a class selector has a weight of 10000.
 1206        */
 1207       /*protected*/ static int getSpecificity(String selector) {
 1208           int specificity = 0;
 1209           boolean lastWasSpace = true;
 1210   
 1211           for (int counter = 0, maxCounter = selector.length();
 1212                counter < maxCounter; counter++) {
 1213               switch(selector.charAt(counter)) {
 1214               case '.':
 1215                   specificity += 100;
 1216                   break;
 1217               case '#':
 1218                   specificity += 10000;
 1219                   break;
 1220               case ' ':
 1221                   lastWasSpace = true;
 1222                   break;
 1223               default:
 1224                   if (lastWasSpace) {
 1225                       lastWasSpace = false;
 1226                       specificity += 1;
 1227                   }
 1228               }
 1229           }
 1230           return specificity;
 1231       }
 1232   
 1233       /**
 1234        * Returns the style that linked attributes should be added to. This
 1235        * will create the style if necessary.
 1236        */
 1237       private Style getLinkedStyle(Style localStyle) {
 1238           // NOTE: This is not synchronized, and the caller of this does
 1239           // not synchronize. There is the chance for one of the callers to
 1240           // overwrite the existing resolved parent, but it is quite rare.
 1241           // The reason this is left like this is because setResolveParent
 1242           // will fire a ChangeEvent. It is really, REALLY bad for us to
 1243           // hold a lock when calling outside of us, it may cause a deadlock.
 1244           Style retStyle = (Style)localStyle.getResolveParent();
 1245           if (retStyle == null) {
 1246               retStyle = addStyle(null, null);
 1247               localStyle.setResolveParent(retStyle);
 1248           }
 1249           return retStyle;
 1250       }
 1251   
 1252       /**
 1253        * Returns the resolved style for <code>selector</code>. This will
 1254        * create the resolved style, if necessary.
 1255        */
 1256       private synchronized Style getResolvedStyle(String selector,
 1257                                                   Vector elements,
 1258                                                   HTML.Tag t) {
 1259           Style retStyle = resolvedStyles.get(selector);
 1260           if (retStyle == null) {
 1261               retStyle = createResolvedStyle(selector, elements, t);
 1262           }
 1263           return retStyle;
 1264       }
 1265   
 1266       /**
 1267        * Returns the resolved style for <code>selector</code>. This will
 1268        * create the resolved style, if necessary.
 1269        */
 1270       private synchronized Style getResolvedStyle(String selector) {
 1271           Style retStyle = resolvedStyles.get(selector);
 1272           if (retStyle == null) {
 1273               retStyle = createResolvedStyle(selector);
 1274           }
 1275           return retStyle;
 1276       }
 1277   
 1278       /**
 1279        * Adds <code>mapping</code> to <code>elements</code>. It is added
 1280        * such that <code>elements</code> will remain ordered by
 1281        * specificity.
 1282        */
 1283       private void addSortedStyle(SelectorMapping mapping, Vector<SelectorMapping> elements) {
 1284           int       size = elements.size();
 1285   
 1286           if (size > 0) {
 1287               int     specificity = mapping.getSpecificity();
 1288   
 1289               for (int counter = 0; counter < size; counter++) {
 1290                   if (specificity >= elements.elementAt(counter).getSpecificity()) {
 1291                       elements.insertElementAt(mapping, counter);
 1292                       return;
 1293                   }
 1294               }
 1295           }
 1296           elements.addElement(mapping);
 1297       }
 1298   
 1299       /**
 1300        * Adds <code>parentMapping</code> to <code>styles</code>, and
 1301        * recursively calls this method if <code>parentMapping</code> has
 1302        * any child mappings for any of the Elements in <code>elements</code>.
 1303        */
 1304       private synchronized void getStyles(SelectorMapping parentMapping,
 1305                              Vector<SelectorMapping> styles,
 1306                              String[] tags, String[] ids, String[] classes,
 1307                              int index, int numElements,
 1308                              Hashtable<SelectorMapping, SelectorMapping> alreadyChecked) {
 1309           // Avoid desending the same mapping twice.
 1310           if (alreadyChecked.contains(parentMapping)) {
 1311               return;
 1312           }
 1313           alreadyChecked.put(parentMapping, parentMapping);
 1314           Style style = parentMapping.getStyle();
 1315           if (style != null) {
 1316               addSortedStyle(parentMapping, styles);
 1317           }
 1318           for (int counter = index; counter < numElements; counter++) {
 1319               String tagString = tags[counter];
 1320               if (tagString != null) {
 1321                   SelectorMapping childMapping = parentMapping.
 1322                                   getChildSelectorMapping(tagString, false);
 1323                   if (childMapping != null) {
 1324                       getStyles(childMapping, styles, tags, ids, classes,
 1325                                 counter + 1, numElements, alreadyChecked);
 1326                   }
 1327                   if (classes[counter] != null) {
 1328                       String className = classes[counter];
 1329                       childMapping = parentMapping.getChildSelectorMapping(
 1330                                            tagString + "." + className, false);
 1331                       if (childMapping != null) {
 1332                           getStyles(childMapping, styles, tags, ids, classes,
 1333                                     counter + 1, numElements, alreadyChecked);
 1334                       }
 1335                       childMapping = parentMapping.getChildSelectorMapping(
 1336                                            "." + className, false);
 1337                       if (childMapping != null) {
 1338                           getStyles(childMapping, styles, tags, ids, classes,
 1339                                     counter + 1, numElements, alreadyChecked);
 1340                       }
 1341                   }
 1342                   if (ids[counter] != null) {
 1343                       String idName = ids[counter];
 1344                       childMapping = parentMapping.getChildSelectorMapping(
 1345                                            tagString + "#" + idName, false);
 1346                       if (childMapping != null) {
 1347                           getStyles(childMapping, styles, tags, ids, classes,
 1348                                     counter + 1, numElements, alreadyChecked);
 1349                       }
 1350                       childMapping = parentMapping.getChildSelectorMapping(
 1351                                      "#" + idName, false);
 1352                       if (childMapping != null) {
 1353                           getStyles(childMapping, styles, tags, ids, classes,
 1354                                     counter + 1, numElements, alreadyChecked);
 1355                       }
 1356                   }
 1357               }
 1358           }
 1359       }
 1360   
 1361       /**
 1362        * Creates and returns a Style containing all the rules that match
 1363        *  <code>selector</code>.
 1364        */
 1365       private synchronized Style createResolvedStyle(String selector,
 1366                                         String[] tags,
 1367                                         String[] ids, String[] classes) {
 1368           SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
 1369           Vector<SelectorMapping> tempVector = sb.getVector();
 1370           Hashtable<SelectorMapping, SelectorMapping> tempHashtable = sb.getHashtable();
 1371           // Determine all the Styles that are appropriate, placing them
 1372           // in tempVector
 1373           try {
 1374               SelectorMapping mapping = getRootSelectorMapping();
 1375               int numElements = tags.length;
 1376               String tagString = tags[0];
 1377               SelectorMapping childMapping = mapping.getChildSelectorMapping(
 1378                                                      tagString, false);
 1379               if (childMapping != null) {
 1380                   getStyles(childMapping, tempVector, tags, ids, classes, 1,
 1381                             numElements, tempHashtable);
 1382               }
 1383               if (classes[0] != null) {
 1384                   String className = classes[0];
 1385                   childMapping = mapping.getChildSelectorMapping(
 1386                                          tagString + "." + className, false);
 1387                   if (childMapping != null) {
 1388                       getStyles(childMapping, tempVector, tags, ids, classes, 1,
 1389                                 numElements, tempHashtable);
 1390                   }
 1391                   childMapping = mapping.getChildSelectorMapping(
 1392                                          "." + className, false);
 1393                   if (childMapping != null) {
 1394                       getStyles(childMapping, tempVector, tags, ids, classes,
 1395                                 1, numElements, tempHashtable);
 1396                   }
 1397               }
 1398               if (ids[0] != null) {
 1399                   String idName = ids[0];
 1400                   childMapping = mapping.getChildSelectorMapping(
 1401                                          tagString + "#" + idName, false);
 1402                   if (childMapping != null) {
 1403                       getStyles(childMapping, tempVector, tags, ids, classes,
 1404                                 1, numElements, tempHashtable);
 1405                   }
 1406                   childMapping = mapping.getChildSelectorMapping(
 1407                                          "#" + idName, false);
 1408                   if (childMapping != null) {
 1409                       getStyles(childMapping, tempVector, tags, ids, classes,
 1410                                 1, numElements, tempHashtable);
 1411                   }
 1412               }
 1413               // Create a new Style that will delegate to all the matching
 1414               // Styles.
 1415               int numLinkedSS = (linkedStyleSheets != null) ?
 1416                                 linkedStyleSheets.size() : 0;
 1417               int numStyles = tempVector.size();
 1418               AttributeSet[] attrs = new AttributeSet[numStyles + numLinkedSS];
 1419               for (int counter = 0; counter < numStyles; counter++) {
 1420                   attrs[counter] = tempVector.elementAt(counter).getStyle();
 1421               }
 1422               // Get the AttributeSet from linked style sheets.
 1423               for (int counter = 0; counter < numLinkedSS; counter++) {
 1424                   AttributeSet attr = linkedStyleSheets.elementAt(counter).getRule(selector);
 1425                   if (attr == null) {
 1426                       attrs[counter + numStyles] = SimpleAttributeSet.EMPTY;
 1427                   }
 1428                   else {
 1429                       attrs[counter + numStyles] = attr;
 1430                   }
 1431               }
 1432               ResolvedStyle retStyle = new ResolvedStyle(selector, attrs,
 1433                                                          numStyles);
 1434               resolvedStyles.put(selector, retStyle);
 1435               return retStyle;
 1436           }
 1437           finally {
 1438               SearchBuffer.releaseSearchBuffer(sb);
 1439           }
 1440       }
 1441   
 1442       /**
 1443        * Creates and returns a Style containing all the rules that
 1444        * matches <code>selector</code>.
 1445        *
 1446        * @param elements  a Vector of all the Elements
 1447        *                  the style is being asked for. The
 1448        *                  first Element is the deepest Element, with the last Element
 1449        *                  representing the root.
 1450        * @param t         the Tag to use for
 1451        *                  the first Element in <code>elements</code>
 1452        */
 1453       private Style createResolvedStyle(String selector, Vector elements,
 1454                                         HTML.Tag t) {
 1455           int numElements = elements.size();
 1456           // Build three arrays, one for tags, one for class's, and one for
 1457           // id's
 1458           String tags[] = new String[numElements];
 1459           String ids[] = new String[numElements];
 1460           String classes[] = new String[numElements];
 1461           for (int counter = 0; counter < numElements; counter++) {
 1462               Element e = (Element)elements.elementAt(counter);
 1463               AttributeSet attr = e.getAttributes();
 1464               if (counter == 0 && e.isLeaf()) {
 1465                   // For leafs, we use the second tier attributes.
 1466                   Object testAttr = attr.getAttribute(t);
 1467                   if (testAttr instanceof AttributeSet) {
 1468                       attr = (AttributeSet)testAttr;
 1469                   }
 1470                   else {
 1471                       attr = null;
 1472                   }
 1473               }
 1474               if (attr != null) {
 1475                   HTML.Tag tag = (HTML.Tag)attr.getAttribute(StyleConstants.
 1476                                                              NameAttribute);
 1477                   if (tag != null) {
 1478                       tags[counter] = tag.toString();
 1479                   }
 1480                   else {
 1481                       tags[counter] = null;
 1482                   }
 1483                   if (attr.isDefined(HTML.Attribute.CLASS)) {
 1484                       classes[counter] = attr.getAttribute
 1485                                         (HTML.Attribute.CLASS).toString();
 1486                   }
 1487                   else {
 1488                       classes[counter] = null;
 1489                   }
 1490                   if (attr.isDefined(HTML.Attribute.ID)) {
 1491                       ids[counter] = attr.getAttribute(HTML.Attribute.ID).
 1492                                           toString();
 1493                   }
 1494                   else {
 1495                       ids[counter] = null;
 1496                   }
 1497               }
 1498               else {
 1499                   tags[counter] = ids[counter] = classes[counter] = null;
 1500               }
 1501           }
 1502           tags[0] = t.toString();
 1503           return createResolvedStyle(selector, tags, ids, classes);
 1504       }
 1505   
 1506       /**
 1507        * Creates and returns a Style containing all the rules that match
 1508        *  <code>selector</code>. It is assumed that each simple selector
 1509        * in <code>selector</code> is separated by a space.
 1510        */
 1511       private Style createResolvedStyle(String selector) {
 1512           SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
 1513           // Will contain the tags, ids, and classes, in that order.
 1514           Vector<String> elements = sb.getVector();
 1515           try {
 1516               boolean done;
 1517               int dotIndex = 0;
 1518               int spaceIndex;
 1519               int poundIndex = 0;
 1520               int lastIndex = 0;
 1521               int length = selector.length();
 1522               while (lastIndex < length) {
 1523                   if (dotIndex == lastIndex) {
 1524                       dotIndex = selector.indexOf('.', lastIndex);
 1525                   }
 1526                   if (poundIndex == lastIndex) {
 1527                       poundIndex = selector.indexOf('#', lastIndex);
 1528                   }
 1529                   spaceIndex = selector.indexOf(' ', lastIndex);
 1530                   if (spaceIndex == -1) {
 1531                       spaceIndex = length;
 1532                   }
 1533                   if (dotIndex != -1 && poundIndex != -1 &&
 1534                       dotIndex < spaceIndex && poundIndex < spaceIndex) {
 1535                       if (poundIndex < dotIndex) {
 1536                           // #.
 1537                           if (lastIndex == poundIndex) {
 1538                               elements.addElement("");
 1539                           }
 1540                           else {
 1541                               elements.addElement(selector.substring(lastIndex,
 1542                                                                     poundIndex));
 1543                           }
 1544                           if ((dotIndex + 1) < spaceIndex) {
 1545                               elements.addElement(selector.substring
 1546                                                   (dotIndex + 1, spaceIndex));
 1547                           }
 1548                           else {
 1549                               elements.addElement(null);
 1550                           }
 1551                           if ((poundIndex + 1) == dotIndex) {
 1552                               elements.addElement(null);
 1553                           }
 1554                           else {
 1555                               elements.addElement(selector.substring
 1556                                                   (poundIndex + 1, dotIndex));
 1557                           }
 1558                       }
 1559                       else if(poundIndex < spaceIndex) {
 1560                           // .#
 1561                           if (lastIndex == dotIndex) {
 1562                               elements.addElement("");
 1563                           }
 1564                           else {
 1565                               elements.addElement(selector.substring(lastIndex,
 1566                                                                     dotIndex));
 1567                           }
 1568                           if ((dotIndex + 1) < poundIndex) {
 1569                               elements.addElement(selector.substring
 1570                                                   (dotIndex + 1, poundIndex));
 1571                           }
 1572                           else {
 1573                               elements.addElement(null);
 1574                           }
 1575                           if ((poundIndex + 1) == spaceIndex) {
 1576                               elements.addElement(null);
 1577                           }
 1578                           else {
 1579                               elements.addElement(selector.substring
 1580                                                   (poundIndex + 1, spaceIndex));
 1581                           }
 1582                       }
 1583                       dotIndex = poundIndex = spaceIndex + 1;
 1584                   }
 1585                   else if (dotIndex != -1 && dotIndex < spaceIndex) {
 1586                       // .
 1587                       if (dotIndex == lastIndex) {
 1588                           elements.addElement("");
 1589                       }
 1590                       else {
 1591                           elements.addElement(selector.substring(lastIndex,
 1592                                                                  dotIndex));
 1593                       }
 1594                       if ((dotIndex + 1) == spaceIndex) {
 1595                           elements.addElement(null);
 1596                       }
 1597                       else {
 1598                           elements.addElement(selector.substring(dotIndex + 1,
 1599                                                                  spaceIndex));
 1600                       }
 1601                       elements.addElement(null);
 1602                       dotIndex = spaceIndex + 1;
 1603                   }
 1604                   else if (poundIndex != -1 && poundIndex < spaceIndex) {
 1605                       // #
 1606                       if (poundIndex == lastIndex) {
 1607                           elements.addElement("");
 1608                       }
 1609                       else {
 1610                           elements.addElement(selector.substring(lastIndex,
 1611                                                                  poundIndex));
 1612                       }
 1613                       elements.addElement(null);
 1614                       if ((poundIndex + 1) == spaceIndex) {
 1615                           elements.addElement(null);
 1616                       }
 1617                       else {
 1618                           elements.addElement(selector.substring(poundIndex + 1,
 1619                                                                  spaceIndex));
 1620                       }
 1621                       poundIndex = spaceIndex + 1;
 1622                   }
 1623                   else {
 1624                       // id
 1625                       elements.addElement(selector.substring(lastIndex,
 1626                                                              spaceIndex));
 1627                       elements.addElement(null);
 1628                       elements.addElement(null);
 1629                   }
 1630                   lastIndex = spaceIndex + 1;
 1631               }
 1632               // Create the tag, id, and class arrays.
 1633               int total = elements.size();
 1634               int numTags = total / 3;
 1635               String[] tags = new String[numTags];
 1636               String[] ids = new String[numTags];
 1637               String[] classes = new String[numTags];
 1638               for (int index = 0, eIndex = total - 3; index < numTags;
 1639                    index++, eIndex -= 3) {
 1640                   tags[index] = elements.elementAt(eIndex);
 1641                   classes[index] = elements.elementAt(eIndex + 1);
 1642                   ids[index] = elements.elementAt(eIndex + 2);
 1643               }
 1644               return createResolvedStyle(selector, tags, ids, classes);
 1645           }
 1646           finally {
 1647               SearchBuffer.releaseSearchBuffer(sb);
 1648           }
 1649       }
 1650   
 1651       /**
 1652        * Should be invoked when a new rule is added that did not previously
 1653        * exist. Goes through and refreshes the necessary resolved
 1654        * rules.
 1655        */
 1656       private synchronized void refreshResolvedRules(String selectorName,
 1657                                                      String[] selector,
 1658                                                      Style newStyle,
 1659                                                      int specificity) {
 1660           if (resolvedStyles.size() > 0) {
 1661               Enumeration<ResolvedStyle> values = resolvedStyles.elements();
 1662               while (values.hasMoreElements()) {
 1663                   ResolvedStyle style = values.nextElement();
 1664                   if (style.matches(selectorName)) {
 1665                       style.insertStyle(newStyle, specificity);
 1666                   }
 1667               }
 1668           }
 1669       }
 1670   
 1671   
 1672       /**
 1673        * A temporary class used to hold a Vector, a StringBuffer and a
 1674        * Hashtable. This is used to avoid allocing a lot of garbage when
 1675        * searching for rules. Use the static method obtainSearchBuffer and
 1676        * releaseSearchBuffer to get a SearchBuffer, and release it when
 1677        * done.
 1678        */
 1679       private static class SearchBuffer {
 1680           /** A stack containing instances of SearchBuffer. Used in getting
 1681            * rules. */
 1682           static Stack<SearchBuffer> searchBuffers = new Stack<SearchBuffer>();
 1683           // A set of temporary variables that can be used in whatever way.
 1684           Vector vector = null;
 1685           StringBuffer stringBuffer = null;
 1686           Hashtable hashtable = null;
 1687   
 1688           /**
 1689            * Returns an instance of SearchBuffer. Be sure and issue
 1690            * a releaseSearchBuffer when done with it.
 1691            */
 1692           static SearchBuffer obtainSearchBuffer() {
 1693               SearchBuffer sb;
 1694               try {
 1695                   if(!searchBuffers.empty()) {
 1696                      sb = searchBuffers.pop();
 1697                   } else {
 1698                      sb = new SearchBuffer();
 1699                   }
 1700               } catch (EmptyStackException ese) {
 1701                   sb = new SearchBuffer();
 1702               }
 1703               return sb;
 1704           }
 1705   
 1706           /**
 1707            * Adds <code>sb</code> to the stack of SearchBuffers that can
 1708            * be used.
 1709            */
 1710           static void releaseSearchBuffer(SearchBuffer sb) {
 1711               sb.empty();
 1712               searchBuffers.push(sb);
 1713           }
 1714   
 1715           StringBuffer getStringBuffer() {
 1716               if (stringBuffer == null) {
 1717                   stringBuffer = new StringBuffer();
 1718               }
 1719               return stringBuffer;
 1720           }
 1721   
 1722           Vector getVector() {
 1723               if (vector == null) {
 1724                   vector = new Vector();
 1725               }
 1726               return vector;
 1727           }
 1728   
 1729           Hashtable getHashtable() {
 1730               if (hashtable == null) {
 1731                   hashtable = new Hashtable();
 1732               }
 1733               return hashtable;
 1734           }
 1735   
 1736           void empty() {
 1737               if (stringBuffer != null) {
 1738                   stringBuffer.setLength(0);
 1739               }
 1740               if (vector != null) {
 1741                   vector.removeAllElements();
 1742               }
 1743               if (hashtable != null) {
 1744                   hashtable.clear();
 1745               }
 1746           }
 1747       }
 1748   
 1749   
 1750       static final Border noBorder = new EmptyBorder(0,0,0,0);
 1751   
 1752       /**
 1753        * Class to carry out some of the duties of
 1754        * CSS formatting.  Implementations of this
 1755        * class enable views to present the CSS formatting
 1756        * while not knowing anything about how the CSS values
 1757        * are being cached.
 1758        * <p>
 1759        * As a delegate of Views, this object is responsible for
 1760        * the insets of a View and making sure the background
 1761        * is maintained according to the CSS attributes.
 1762        */
 1763       public static class BoxPainter implements Serializable {
 1764   
 1765           BoxPainter(AttributeSet a, CSS css, StyleSheet ss) {
 1766               this.ss = ss;
 1767               this.css = css;
 1768               border = getBorder(a);
 1769               binsets = border.getBorderInsets(null);
 1770               topMargin = getLength(CSS.Attribute.MARGIN_TOP, a);
 1771               bottomMargin = getLength(CSS.Attribute.MARGIN_BOTTOM, a);
 1772               leftMargin = getLength(CSS.Attribute.MARGIN_LEFT, a);
 1773               rightMargin = getLength(CSS.Attribute.MARGIN_RIGHT, a);
 1774               bg = ss.getBackground(a);
 1775               if (ss.getBackgroundImage(a) != null) {
 1776                   bgPainter = new BackgroundImagePainter(a, css, ss);
 1777               }
 1778           }
 1779   
 1780           /**
 1781            * Fetches a border to render for the given attributes.
 1782            * PENDING(prinz) This is pretty badly hacked at the
 1783            * moment.
 1784            */
 1785           Border getBorder(AttributeSet a) {
 1786               return new CSSBorder(a);
 1787           }
 1788   
 1789           /**
 1790            * Fetches the color to use for borders.  This will either be
 1791            * the value specified by the border-color attribute (which
 1792            * is not inherited), or it will default to the color attribute
 1793            * (which is inherited).
 1794            */
 1795           Color getBorderColor(AttributeSet a) {
 1796               Color color = css.getColor(a, CSS.Attribute.BORDER_COLOR);
 1797               if (color == null) {
 1798                   color = css.getColor(a, CSS.Attribute.COLOR);
 1799                   if (color == null) {
 1800                       return Color.black;
 1801                   }
 1802               }
 1803               return color;
 1804           }
 1805   
 1806           /**
 1807            * Fetches the inset needed on a given side to
 1808            * account for the margin, border, and padding.
 1809            *
 1810            * @param side The size of the box to fetch the
 1811            *  inset for.  This can be View.TOP,
 1812            *  View.LEFT, View.BOTTOM, or View.RIGHT.
 1813            * @param v the view making the request.  This is
 1814            *  used to get the AttributeSet, and may be used to
 1815            *  resolve percentage arguments.
 1816            * @exception IllegalArgumentException for an invalid direction
 1817            */
 1818           public float getInset(int side, View v) {
 1819               AttributeSet a = v.getAttributes();
 1820               float inset = 0;
 1821               switch(side) {
 1822               case View.LEFT:
 1823                   inset += getOrientationMargin(HorizontalMargin.LEFT,
 1824                                                 leftMargin, a, isLeftToRight(v));
 1825                   inset += binsets.left;
 1826                   inset += getLength(CSS.Attribute.PADDING_LEFT, a);
 1827                   break;
 1828               case View.RIGHT:
 1829                   inset += getOrientationMargin(HorizontalMargin.RIGHT,
 1830                                                 rightMargin, a, isLeftToRight(v));
 1831                   inset += binsets.right;
 1832                   inset += getLength(CSS.Attribute.PADDING_RIGHT, a);
 1833                   break;
 1834               case View.TOP:
 1835                   inset += topMargin;
 1836                   inset += binsets.top;
 1837                   inset += getLength(CSS.Attribute.PADDING_TOP, a);
 1838                   break;
 1839               case View.BOTTOM:
 1840                   inset += bottomMargin;
 1841                   inset += binsets.bottom;
 1842                   inset += getLength(CSS.Attribute.PADDING_BOTTOM, a);
 1843                   break;
 1844               default:
 1845                   throw new IllegalArgumentException("Invalid side: " + side);
 1846               }
 1847               return inset;
 1848           }
 1849   
 1850           /**
 1851            * Paints the CSS box according to the attributes
 1852            * given.  This should paint the border, padding,
 1853            * and background.
 1854            *
 1855            * @param g the rendering surface.
 1856            * @param x the x coordinate of the allocated area to
 1857            *  render into.
 1858            * @param y the y coordinate of the allocated area to
 1859            *  render into.
 1860            * @param w the width of the allocated area to render into.
 1861            * @param h the height of the allocated area to render into.
 1862            * @param v the view making the request.  This is
 1863            *  used to get the AttributeSet, and may be used to
 1864            *  resolve percentage arguments.
 1865            */
 1866           public void paint(Graphics g, float x, float y, float w, float h, View v) {
 1867               // PENDING(prinz) implement real rendering... which would
 1868               // do full set of border and background capabilities.
 1869               // remove margin
 1870   
 1871               float dx = 0;
 1872               float dy = 0;
 1873               float dw = 0;
 1874               float dh = 0;
 1875               AttributeSet a = v.getAttributes();
 1876               boolean isLeftToRight = isLeftToRight(v);
 1877               float localLeftMargin = getOrientationMargin(HorizontalMargin.LEFT,
 1878                                                            leftMargin,
 1879                                                            a, isLeftToRight);
 1880               float localRightMargin = getOrientationMargin(HorizontalMargin.RIGHT,
 1881                                                             rightMargin,
 1882                                                             a, isLeftToRight);
 1883               if (!(v instanceof HTMLEditorKit.HTMLFactory.BodyBlockView)) {
 1884                   dx = localLeftMargin;
 1885                   dy = topMargin;
 1886                   dw = -(localLeftMargin + localRightMargin);
 1887                   dh = -(topMargin + bottomMargin);
 1888               }
 1889               if (bg != null) {
 1890                   g.setColor(bg);
 1891                   g.fillRect((int) (x + dx),
 1892                              (int) (y + dy),
 1893                              (int) (w + dw),
 1894                              (int) (h + dh));
 1895               }
 1896               if (bgPainter != null) {
 1897                   bgPainter.paint(g, x + dx, y + dy, w + dw, h + dh, v);
 1898               }
 1899               x += localLeftMargin;
 1900               y += topMargin;
 1901               w -= localLeftMargin + localRightMargin;
 1902               h -= topMargin + bottomMargin;
 1903               if (border instanceof BevelBorder) {
 1904                   //BevelBorder does not support border width
 1905                   int bw = (int) getLength(CSS.Attribute.BORDER_TOP_WIDTH, a);
 1906                   for (int i = bw - 1; i >= 0; i--) {
 1907                       border.paintBorder(null, g, (int) x + i, (int) y + i,
 1908                                          (int) w - 2 * i, (int) h - 2 * i);
 1909                   }
 1910               } else {
 1911                   border.paintBorder(null, g, (int) x, (int) y, (int) w, (int) h);
 1912               }
 1913           }
 1914   
 1915           float getLength(CSS.Attribute key, AttributeSet a) {
 1916               return css.getLength(a, key, ss);
 1917           }
 1918   
 1919           static boolean isLeftToRight(View v) {
 1920               boolean ret = true;
 1921               if (isOrientationAware(v)) {
 1922                   Container container;
 1923                   if (v != null && (container = v.getContainer()) != null) {
 1924                       ret = container.getComponentOrientation().isLeftToRight();
 1925                   }
 1926               }
 1927               return ret;
 1928           }
 1929   
 1930           /*
 1931            * only certain tags are concerned about orientation
 1932            * <dir>, <menu>, <ul>, <ol>
 1933            * for all others we return true. It is implemented this way
 1934            * for performance purposes
 1935            */
 1936           static boolean isOrientationAware(View v) {
 1937               boolean ret = false;
 1938               AttributeSet attr;
 1939               Object obj;
 1940               if (v != null
 1941                   && (attr = v.getElement().getAttributes()) != null
 1942                   && (obj = attr.getAttribute(StyleConstants.NameAttribute)) instanceof HTML.Tag
 1943                   && (obj == HTML.Tag.DIR
 1944                       || obj == HTML.Tag.MENU
 1945                       || obj == HTML.Tag.UL
 1946                       || obj == HTML.Tag.OL)) {
 1947                   ret = true;
 1948               }
 1949   
 1950               return ret;
 1951           }
 1952   
 1953           static enum HorizontalMargin { LEFT, RIGHT }
 1954   
 1955           /**
 1956            * for <dir>, <menu>, <ul> etc.
 1957            * margins are Left-To-Right/Right-To-Left depended.
 1958            * see 5088268 for more details
 1959            * margin-(left|right)-(ltr|rtl) were introduced to describe it
 1960            * if margin-(left|right) is present we are to use it.
 1961            *
 1962            * @param side The horizontal side to fetch margin for
 1963            *  This can be HorizontalMargin.LEFT or HorizontalMargin.RIGHT
 1964            * @param cssMargin margin from css
 1965            * @param a AttributeSet for the View we getting margin for
 1966            * @param isLeftToRight
 1967            * @return orientation depended margin
 1968            */
 1969           float getOrientationMargin(HorizontalMargin side, float cssMargin,
 1970                                      AttributeSet a, boolean isLeftToRight) {
 1971               float margin = cssMargin;
 1972               float orientationMargin = cssMargin;
 1973               Object cssMarginValue = null;
 1974               switch (side) {
 1975               case RIGHT:
 1976                   {
 1977                       orientationMargin = (isLeftToRight) ?
 1978                           getLength(CSS.Attribute.MARGIN_RIGHT_LTR, a) :
 1979                           getLength(CSS.Attribute.MARGIN_RIGHT_RTL, a);
 1980                       cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_RIGHT);
 1981                   }
 1982                   break;
 1983               case LEFT :
 1984                   {
 1985                       orientationMargin = (isLeftToRight) ?
 1986                           getLength(CSS.Attribute.MARGIN_LEFT_LTR, a) :
 1987                           getLength(CSS.Attribute.MARGIN_LEFT_RTL, a);
 1988                       cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_LEFT);
 1989                   }
 1990                   break;
 1991               }
 1992   
 1993               if (cssMarginValue == null
 1994                   && orientationMargin != Integer.MIN_VALUE) {
 1995                   margin = orientationMargin;
 1996               }
 1997               return margin;
 1998           }
 1999   
 2000           float topMargin;
 2001           float bottomMargin;
 2002           float leftMargin;
 2003           float rightMargin;
 2004           // Bitmask, used to indicate what margins are relative:
 2005           // bit 0 for top, 1 for bottom, 2 for left and 3 for right.
 2006           short marginFlags;
 2007           Border border;
 2008           Insets binsets;
 2009           CSS css;
 2010           StyleSheet ss;
 2011           Color bg;
 2012           BackgroundImagePainter bgPainter;
 2013       }
 2014   
 2015       /**
 2016        * Class to carry out some of the duties of CSS list
 2017        * formatting.  Implementations of this
 2018        * class enable views to present the CSS formatting
 2019        * while not knowing anything about how the CSS values
 2020        * are being cached.
 2021        */
 2022       public static class ListPainter implements Serializable {
 2023   
 2024           ListPainter(AttributeSet attr, StyleSheet ss) {
 2025               this.ss = ss;
 2026               /* Get the image to use as a list bullet */
 2027               String imgstr = (String)attr.getAttribute(CSS.Attribute.
 2028                                                         LIST_STYLE_IMAGE);
 2029               type = null;
 2030               if (imgstr != null && !imgstr.equals("none")) {
 2031                   String tmpstr = null;
 2032                   try {
 2033                       StringTokenizer st = new StringTokenizer(imgstr, "()");
 2034                       if (st.hasMoreTokens())
 2035                           tmpstr = st.nextToken();
 2036                       if (st.hasMoreTokens())
 2037                           tmpstr = st.nextToken();
 2038                       URL u = new URL(tmpstr);
 2039                       img = new ImageIcon(u);
 2040                   } catch (MalformedURLException e) {
 2041                       if (tmpstr != null && ss != null && ss.getBase() != null) {
 2042                           try {
 2043                               URL u = new URL(ss.getBase(), tmpstr);
 2044                               img = new ImageIcon(u);
 2045                           } catch (MalformedURLException murle) {
 2046                               img = null;
 2047                           }
 2048                       }
 2049                       else {
 2050                           img = null;
 2051                       }
 2052                   }
 2053               }
 2054   
 2055               /* Get the type of bullet to use in the list */
 2056               if (img == null) {
 2057                   type = (CSS.Value)attr.getAttribute(CSS.Attribute.
 2058                                                       LIST_STYLE_TYPE);
 2059               }
 2060               start = 1;
 2061   
 2062               paintRect = new Rectangle();
 2063           }
 2064   
 2065           /**
 2066            * Returns a string that represents the value
 2067            * of the HTML.Attribute.TYPE attribute.
 2068            * If this attributes is not defined, then
 2069            * then the type defaults to "disc" unless
 2070            * the tag is on Ordered list.  In the case
 2071            * of the latter, the default type is "decimal".
 2072            */
 2073           private CSS.Value getChildType(View childView) {
 2074               CSS.Value childtype = (CSS.Value)childView.getAttributes().
 2075                                     getAttribute(CSS.Attribute.LIST_STYLE_TYPE);
 2076   
 2077               if (childtype == null) {
 2078                   if (type == null) {
 2079                       // Parent view.
 2080                       View v = childView.getParent();
 2081                       HTMLDocument doc = (HTMLDocument)v.getDocument();
 2082                       if (doc.matchNameAttribute(v.getElement().getAttributes(),
 2083                                                  HTML.Tag.OL)) {
 2084                           childtype = CSS.Value.DECIMAL;
 2085                       } else {
 2086                           childtype = CSS.Value.DISC;
 2087                       }
 2088                   } else {
 2089                       childtype = type;
 2090                   }
 2091               }
 2092               return childtype;
 2093           }
 2094   
 2095           /**
 2096            * Obtains the starting index from <code>parent</code>.
 2097            */
 2098           private void getStart(View parent) {
 2099               checkedForStart = true;
 2100               Element element = parent.getElement();
 2101               if (element != null) {
 2102                   AttributeSet attr = element.getAttributes();
 2103                   Object startValue;
 2104                   if (attr != null && attr.isDefined(HTML.Attribute.START) &&
 2105                       (startValue = attr.getAttribute
 2106                        (HTML.Attribute.START)) != null &&
 2107                       (startValue instanceof String)) {
 2108   
 2109                       try {
 2110                           start = Integer.parseInt((String)startValue);
 2111                       }
 2112                       catch (NumberFormatException nfe) {}
 2113                   }
 2114               }
 2115           }
 2116   
 2117           /**
 2118            * Returns an integer that should be used to render the child at
 2119            * <code>childIndex</code> with. The retValue will usually be
 2120            * <code>childIndex</code> + 1, unless <code>parentView</code>
 2121            * has some Views that do not represent LI's, or one of the views
 2122            * has a HTML.Attribute.START specified.
 2123            */
 2124           private int getRenderIndex(View parentView, int childIndex) {
 2125               if (!checkedForStart) {
 2126                   getStart(parentView);
 2127               }
 2128               int retIndex = childIndex;
 2129               for (int counter = childIndex; counter >= 0; counter--) {
 2130                   AttributeSet as = parentView.getElement().getElement(counter).
 2131                                     getAttributes();
 2132                   if (as.getAttribute(StyleConstants.NameAttribute) !=
 2133                       HTML.Tag.LI) {
 2134                       retIndex--;
 2135                   } else if (as.isDefined(HTML.Attribute.VALUE)) {
 2136                       Object value = as.getAttribute(HTML.Attribute.VALUE);
 2137                       if (value != null &&
 2138                           (value instanceof String)) {
 2139                           try {
 2140                               int iValue = Integer.parseInt((String)value);
 2141                               return retIndex - counter + iValue;
 2142                           }
 2143                           catch (NumberFormatException nfe) {}
 2144                       }
 2145                   }
 2146               }
 2147               return retIndex + start;
 2148           }
 2149   
 2150           /**
 2151            * Paints the CSS list decoration according to the
 2152            * attributes given.
 2153            *
 2154            * @param g the rendering surface.
 2155            * @param x the x coordinate of the list item allocation
 2156            * @param y the y coordinate of the list item allocation
 2157            * @param w the width of the list item allocation
 2158            * @param h the height of the list item allocation
 2159            * @param v the allocated area to paint into.
 2160            * @param item which list item is being painted.  This
 2161            *  is a number greater than or equal to 0.
 2162            */
 2163           public void paint(Graphics g, float x, float y, float w, float h, View v, int item) {
 2164               View cv = v.getView(item);
 2165               Container host = v.getContainer();
 2166               Object name = cv.getElement().getAttributes().getAttribute
 2167                            (StyleConstants.NameAttribute);
 2168               // Only draw something if the View is a list item. This won't
 2169               // be the case for comments.
 2170               if (!(name instanceof HTML.Tag) ||
 2171                   name != HTML.Tag.LI) {
 2172                   return;
 2173               }
 2174               // deside on what side draw bullets, etc.
 2175               isLeftToRight =
 2176                   host.getComponentOrientation().isLeftToRight();
 2177   
 2178               // How the list indicator is aligned is not specified, it is
 2179               // left up to the UA. IE and NS differ on this behavior.
 2180               // This is closer to NS where we align to the first line of text.
 2181               // If the child is not text we draw the indicator at the
 2182               // origin (0).
 2183               float align = 0;
 2184               if (cv.getViewCount() > 0) {
 2185                   View pView = cv.getView(0);
 2186                   Object cName = pView.getElement().getAttributes().
 2187                                  getAttribute(StyleConstants.NameAttribute);
 2188                   if ((cName == HTML.Tag.P || cName == HTML.Tag.IMPLIED) &&
 2189                                 pView.getViewCount() > 0) {
 2190                       paintRect.setBounds((int)x, (int)y, (int)w, (int)h);
 2191                       Shape shape = cv.getChildAllocation(0, paintRect);
 2192                       if (shape != null && (shape = pView.getView(0).
 2193                                    getChildAllocation(0, shape)) != null) {
 2194                           Rectangle rect = (shape instanceof Rectangle) ?
 2195                                            (Rectangle)shape : shape.getBounds();
 2196   
 2197                           align = pView.getView(0).getAlignment(View.Y_AXIS);
 2198                           y = rect.y;
 2199                           h = rect.height;
 2200                       }
 2201                   }
 2202               }
 2203   
 2204               // set the color of a decoration
 2205               Color c = (host.isEnabled()
 2206                   ? (ss != null
 2207                       ? ss.getForeground(cv.getAttributes())
 2208                       : host.getForeground())
 2209                   : UIManager.getColor("textInactiveText"));
 2210               g.setColor(c);
 2211   
 2212               if (img != null) {
 2213                   drawIcon(g, (int) x, (int) y, (int) w, (int) h, align, host);
 2214                   return;
 2215               }
 2216               CSS.Value childtype = getChildType(cv);
 2217               Font font = ((StyledDocument)cv.getDocument()).
 2218                                            getFont(cv.getAttributes());
 2219               if (font != null) {
 2220                   g.setFont(font);
 2221               }
 2222               if (childtype == CSS.Value.SQUARE || childtype == CSS.Value.CIRCLE
 2223                   || childtype == CSS.Value.DISC) {
 2224                   drawShape(g, childtype, (int) x, (int) y,
 2225                             (int) w, (int) h, align);
 2226               } else if (childtype == CSS.Value.DECIMAL) {
 2227                   drawLetter(g, '1', (int) x, (int) y, (int) w, (int) h, align,
 2228                              getRenderIndex(v, item));
 2229               } else if (childtype == CSS.Value.LOWER_ALPHA) {
 2230                   drawLetter(g, 'a', (int) x, (int) y, (int) w, (int) h, align,
 2231                              getRenderIndex(v, item));
 2232               } else if (childtype == CSS.Value.UPPER_ALPHA) {
 2233                   drawLetter(g, 'A', (int) x, (int) y, (int) w, (int) h, align,
 2234                              getRenderIndex(v, item));
 2235               } else if (childtype == CSS.Value.LOWER_ROMAN) {
 2236                   drawLetter(g, 'i', (int) x, (int) y, (int) w, (int) h, align,
 2237                              getRenderIndex(v, item));
 2238               } else if (childtype == CSS.Value.UPPER_ROMAN) {
 2239                   drawLetter(g, 'I', (int) x, (int) y, (int) w, (int) h, align,
 2240                              getRenderIndex(v, item));
 2241               }
 2242           }
 2243   
 2244           /**
 2245            * Draws the bullet icon specified by the list-style-image argument.
 2246            *
 2247            * @param g     the graphics context
 2248            * @param ax    x coordinate to place the bullet
 2249            * @param ay    y coordinate to place the bullet
 2250            * @param aw    width of the container the bullet is placed in
 2251            * @param ah    height of the container the bullet is placed in
 2252            * @param align preferred alignment factor for the child view
 2253            */
 2254           void drawIcon(Graphics g, int ax, int ay, int aw, int ah,
 2255                         float align, Component c) {
 2256               // Align to bottom of icon.
 2257               int gap = isLeftToRight ? - (img.getIconWidth() + bulletgap) :
 2258                                           (aw + bulletgap);
 2259               int x = ax + gap;
 2260               int y = Math.max(ay, ay + (int)(align * ah) -img.getIconHeight());
 2261   
 2262               img.paintIcon(c, g, x, y);
 2263           }
 2264   
 2265           /**
 2266            * Draws the graphical bullet item specified by the type argument.
 2267            *
 2268            * @param g     the graphics context
 2269            * @param type  type of bullet to draw (circle, square, disc)
 2270            * @param ax    x coordinate to place the bullet
 2271            * @param ay    y coordinate to place the bullet
 2272            * @param aw    width of the container the bullet is placed in
 2273            * @param ah    height of the container the bullet is placed in
 2274            * @param align preferred alignment factor for the child view
 2275            */
 2276           void drawShape(Graphics g, CSS.Value type, int ax, int ay, int aw,
 2277                          int ah, float align) {
 2278               // Align to bottom of shape.
 2279               int gap = isLeftToRight ? - (bulletgap + 8) : (aw + bulletgap);
 2280               int x = ax + gap;
 2281               int y = Math.max(ay, ay + (int)(align * ah) - 8);
 2282   
 2283               if (type == CSS.Value.SQUARE) {
 2284                   g.drawRect(x, y, 8, 8);
 2285               } else if (type == CSS.Value.CIRCLE) {
 2286                   g.drawOval(x, y, 8, 8);
 2287               } else {
 2288                   g.fillOval(x, y, 8, 8);
 2289               }
 2290           }
 2291   
 2292           /**
 2293            * Draws the letter or number for an ordered list.
 2294            *
 2295            * @param g     the graphics context
 2296            * @param letter type of ordered list to draw
 2297            * @param ax    x coordinate to place the bullet
 2298            * @param ay    y coordinate to place the bullet
 2299            * @param aw    width of the container the bullet is placed in
 2300            * @param ah    height of the container the bullet is placed in
 2301            * @param index position of the list item in the list
 2302            */
 2303           void drawLetter(Graphics g, char letter, int ax, int ay, int aw,
 2304                           int ah, float align, int index) {
 2305               String str = formatItemNum(index, letter);
 2306               str = isLeftToRight ? str + "." : "." + str;
 2307               FontMetrics fm = SwingUtilities2.getFontMetrics(null, g);
 2308               int stringwidth = SwingUtilities2.stringWidth(null, fm, str);
 2309               int gap = isLeftToRight ? - (stringwidth + bulletgap) :
 2310                                           (aw + bulletgap);
 2311               int x = ax + gap;
 2312               int y = Math.max(ay + fm.getAscent(), ay + (int)(ah * align));
 2313               SwingUtilities2.drawString(null, g, str, x, y);
 2314           }
 2315   
 2316           /**
 2317            * Converts the item number into the ordered list number
 2318            * (i.e.  1 2 3, i ii iii, a b c, etc.
 2319            *
 2320            * @param itemNum number to format
 2321            * @param type    type of ordered list
 2322            */
 2323           String formatItemNum(int itemNum, char type) {
 2324               String numStyle = "1";
 2325   
 2326               boolean uppercase = false;
 2327   
 2328               String formattedNum;
 2329   
 2330               switch (type) {
 2331               case '1':
 2332               default:
 2333                   formattedNum = String.valueOf(itemNum);
 2334                   break;
 2335   
 2336               case 'A':
 2337                   uppercase = true;
 2338                   // fall through
 2339               case 'a':
 2340                   formattedNum = formatAlphaNumerals(itemNum);
 2341                   break;
 2342   
 2343               case 'I':
 2344                   uppercase = true;
 2345                   // fall through
 2346               case 'i':
 2347                   formattedNum = formatRomanNumerals(itemNum);
 2348               }
 2349   
 2350               if (uppercase) {
 2351                   formattedNum = formattedNum.toUpperCase();
 2352               }
 2353   
 2354               return formattedNum;
 2355           }
 2356   
 2357           /**
 2358            * Converts the item number into an alphabetic character
 2359            *
 2360            * @param itemNum number to format
 2361            */
 2362           String formatAlphaNumerals(int itemNum) {
 2363               String result;
 2364   
 2365               if (itemNum > 26) {
 2366                   result = formatAlphaNumerals(itemNum / 26) +
 2367                       formatAlphaNumerals(itemNum % 26);
 2368               } else {
 2369                   // -1 because item is 1 based.
 2370                   result = String.valueOf((char)('a' + itemNum - 1));
 2371               }
 2372   
 2373               return result;
 2374           }
 2375   
 2376           /* list of roman numerals */
 2377           static final char romanChars[][] = {
 2378               {'i', 'v'},
 2379               {'x', 'l' },
 2380               {'c', 'd' },
 2381               {'m', '?' },
 2382           };
 2383   
 2384           /**
 2385            * Converts the item number into a roman numeral
 2386            *
 2387            * @param num  number to format
 2388            */
 2389           String formatRomanNumerals(int num) {
 2390               return formatRomanNumerals(0, num);
 2391           }
 2392   
 2393           /**
 2394            * Converts the item number into a roman numeral
 2395            *
 2396            * @param num  number to format
 2397            */
 2398           String formatRomanNumerals(int level, int num) {
 2399               if (num < 10) {
 2400                   return formatRomanDigit(level, num);
 2401               } else {
 2402                   return formatRomanNumerals(level + 1, num / 10) +
 2403                       formatRomanDigit(level, num % 10);
 2404               }
 2405           }
 2406   
 2407   
 2408           /**
 2409            * Converts the item number into a roman numeral
 2410            *
 2411            * @param level position
 2412            * @param digit digit to format
 2413            */
 2414           String formatRomanDigit(int level, int digit) {
 2415               String result = "";
 2416               if (digit == 9) {
 2417                   result = result + romanChars[level][0];
 2418                   result = result + romanChars[level + 1][0];
 2419                   return result;
 2420               } else if (digit == 4) {
 2421                   result = result + romanChars[level][0];
 2422                   result = result + romanChars[level][1];
 2423                   return result;
 2424               } else if (digit >= 5) {
 2425                   result = result + romanChars[level][1];
 2426                   digit -= 5;
 2427               }
 2428   
 2429               for (int i = 0; i < digit; i++) {
 2430                   result = result + romanChars[level][0];
 2431               }
 2432   
 2433               return result;
 2434           }
 2435   
 2436           private Rectangle paintRect;
 2437           private boolean checkedForStart;
 2438           private int start;
 2439           private CSS.Value type;
 2440           URL imageurl;
 2441           private StyleSheet ss = null;
 2442           Icon img = null;
 2443           private int bulletgap = 5;
 2444           private boolean isLeftToRight;
 2445       }
 2446   
 2447   
 2448       /**
 2449        * Paints the background image.
 2450        */
 2451       static class BackgroundImagePainter implements Serializable {
 2452           ImageIcon   backgroundImage;
 2453           float       hPosition;
 2454           float       vPosition;
 2455           // bit mask: 0 for repeat x, 1 for repeat y, 2 for horiz relative,
 2456           // 3 for vert relative
 2457           short       flags;
 2458           // These are used when painting, updatePaintCoordinates updates them.
 2459           private int paintX;
 2460           private int paintY;
 2461           private int paintMaxX;
 2462           private int paintMaxY;
 2463   
 2464           BackgroundImagePainter(AttributeSet a, CSS css, StyleSheet ss) {
 2465               backgroundImage = ss.getBackgroundImage(a);
 2466               // Determine the position.
 2467               CSS.BackgroundPosition pos = (CSS.BackgroundPosition)a.getAttribute
 2468                                              (CSS.Attribute.BACKGROUND_POSITION);
 2469               if (pos != null) {
 2470                   hPosition = pos.getHorizontalPosition();
 2471                   vPosition = pos.getVerticalPosition();
 2472                   if (pos.isHorizontalPositionRelativeToSize()) {
 2473                       flags |= 4;
 2474                   }
 2475                   else if (pos.isHorizontalPositionRelativeToSize()) {
 2476                       hPosition *= css.getFontSize(a, 12, ss);
 2477                   }
 2478                   if (pos.isVerticalPositionRelativeToSize()) {
 2479                       flags |= 8;
 2480                   }
 2481                   else if (pos.isVerticalPositionRelativeToFontSize()) {
 2482                       vPosition *= css.getFontSize(a, 12, ss);
 2483                   }
 2484               }
 2485               // Determine any repeating values.
 2486               CSS.Value repeats = (CSS.Value)a.getAttribute(CSS.Attribute.
 2487                                                             BACKGROUND_REPEAT);
 2488               if (repeats == null || repeats == CSS.Value.BACKGROUND_REPEAT) {
 2489                   flags |= 3;
 2490               }
 2491               else if (repeats == CSS.Value.BACKGROUND_REPEAT_X) {
 2492                   flags |= 1;
 2493               }
 2494               else if (repeats == CSS.Value.BACKGROUND_REPEAT_Y) {
 2495                   flags |= 2;
 2496               }
 2497           }
 2498   
 2499           void paint(Graphics g, float x, float y, float w, float h, View v) {
 2500               Rectangle clip = g.getClipRect();
 2501               if (clip != null) {
 2502                   // Constrain the clip so that images don't draw outside the
 2503                   // legal bounds.
 2504                   g.clipRect((int)x, (int)y, (int)w, (int)h);
 2505               }
 2506               if ((flags & 3) == 0) {
 2507                   // no repeating
 2508                   int width = backgroundImage.getIconWidth();
 2509                   int height = backgroundImage.getIconWidth();
 2510                   if ((flags & 4) == 4) {
 2511                       paintX = (int)(x + w * hPosition -
 2512                                     (float)width * hPosition);
 2513                   }
 2514                   else {
 2515                       paintX = (int)x + (int)hPosition;
 2516                   }
 2517                   if ((flags & 8) == 8) {
 2518                       paintY = (int)(y + h * vPosition -
 2519                                     (float)height * vPosition);
 2520                   }
 2521                   else {
 2522                       paintY = (int)y + (int)vPosition;
 2523                   }
 2524                   if (clip == null ||
 2525                       !((paintX + width <= clip.x) ||
 2526                         (paintY + height <= clip.y) ||
 2527                         (paintX >= clip.x + clip.width) ||
 2528                         (paintY >= clip.y + clip.height))) {
 2529                       backgroundImage.paintIcon(null, g, paintX, paintY);
 2530                   }
 2531               }
 2532               else {
 2533                   int width = backgroundImage.getIconWidth();
 2534                   int height = backgroundImage.getIconHeight();
 2535                   if (width > 0 && height > 0) {
 2536                       paintX = (int)x;
 2537                       paintY = (int)y;
 2538                       paintMaxX = (int)(x + w);
 2539                       paintMaxY = (int)(y + h);
 2540                       if (updatePaintCoordinates(clip, width, height)) {
 2541                           while (paintX < paintMaxX) {
 2542                               int ySpot = paintY;
 2543                               while (ySpot < paintMaxY) {
 2544                                   backgroundImage.paintIcon(null, g, paintX,
 2545                                                             ySpot);
 2546                                   ySpot += height;
 2547                               }
 2548                               paintX += width;
 2549                           }
 2550                       }
 2551                   }
 2552               }
 2553               if (clip != null) {
 2554                   // Reset clip.
 2555                   g.setClip(clip.x, clip.y, clip.width, clip.height);
 2556               }
 2557           }
 2558   
 2559           private boolean updatePaintCoordinates
 2560                    (Rectangle clip, int width, int height){
 2561               if ((flags & 3) == 1) {
 2562                   paintMaxY = paintY + 1;
 2563               }
 2564               else if ((flags & 3) == 2) {
 2565                   paintMaxX = paintX + 1;
 2566               }
 2567               if (clip != null) {
 2568                   if ((flags & 3) == 1 && ((paintY + height <= clip.y) ||
 2569                                            (paintY > clip.y + clip.height))) {
 2570                       // not visible.
 2571                       return false;
 2572                   }
 2573                   if ((flags & 3) == 2 && ((paintX + width <= clip.x) ||
 2574                                            (paintX > clip.x + clip.width))) {
 2575                       // not visible.
 2576                       return false;
 2577                   }
 2578                   if ((flags & 1) == 1) {
 2579                       if ((clip.x + clip.width) < paintMaxX) {
 2580                           if ((clip.x + clip.width - paintX) % width == 0) {
 2581                               paintMaxX = clip.x + clip.width;
 2582                           }
 2583                           else {
 2584                               paintMaxX = ((clip.x + clip.width - paintX) /
 2585                                            width + 1) * width + paintX;
 2586                           }
 2587                       }
 2588                       if (clip.x > paintX) {
 2589                           paintX = (clip.x - paintX) / width * width + paintX;
 2590                       }
 2591                   }
 2592                   if ((flags & 2) == 2) {
 2593                       if ((clip.y + clip.height) < paintMaxY) {
 2594                           if ((clip.y + clip.height - paintY) % height == 0) {
 2595                               paintMaxY = clip.y + clip.height;
 2596                           }
 2597                           else {
 2598                               paintMaxY = ((clip.y + clip.height - paintY) /
 2599                                            height + 1) * height + paintY;
 2600                           }
 2601                       }
 2602                       if (clip.y > paintY) {
 2603                           paintY = (clip.y - paintY) / height * height + paintY;
 2604                       }
 2605                   }
 2606               }
 2607               // Valid
 2608               return true;
 2609           }
 2610       }
 2611   
 2612   
 2613       /**
 2614        * A subclass of MuxingAttributeSet that translates between
 2615        * CSS and HTML and StyleConstants. The AttributeSets used are
 2616        * the CSS rules that match the Views Elements.
 2617        */
 2618       class ViewAttributeSet extends MuxingAttributeSet {
 2619           ViewAttributeSet(View v) {
 2620               host = v;
 2621   
 2622               // PENDING(prinz) fix this up to be a more realistic
 2623               // implementation.
 2624               Document doc = v.getDocument();
 2625               SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
 2626               Vector<AttributeSet> muxList = sb.getVector();
 2627               try {
 2628                   if (doc instanceof HTMLDocument) {
 2629                       StyleSheet styles = StyleSheet.this;
 2630                       Element elem = v.getElement();
 2631                       AttributeSet a = elem.getAttributes();
 2632                       AttributeSet htmlAttr = styles.translateHTMLToCSS(a);
 2633   
 2634                       if (htmlAttr.getAttributeCount() != 0) {
 2635                           muxList.addElement(htmlAttr);
 2636                       }
 2637                       if (elem.isLeaf()) {
 2638                           Enumeration keys = a.getAttributeNames();
 2639                           while (keys.hasMoreElements()) {
 2640                               Object key = keys.nextElement();
 2641                               if (key instanceof HTML.Tag) {
 2642                                   if (key == HTML.Tag.A) {
 2643                                       Object o = a.getAttribute(key);
 2644                                   /**
 2645                                      In the case of an A tag, the css rules
 2646                                      apply only for tags that have their
 2647                                      href attribute defined and not for
 2648                                      anchors that only have their name attributes
 2649                                      defined, i.e anchors that function as
 2650                                      destinations.  Hence we do not add the
 2651                                      attributes for that latter kind of
 2652                                      anchors.  When CSS2 support is added,
 2653                                      it will be possible to specificity this
 2654                                      kind of conditional behaviour in the
 2655                                      stylesheet.
 2656                                    **/
 2657                                       if (o != null && o instanceof AttributeSet) {
 2658                                           AttributeSet attr = (AttributeSet)o;
 2659                                           if (attr.getAttribute(HTML.Attribute.HREF) == null) {
 2660                                               continue;
 2661                                           }
 2662                                       }
 2663                                   }
 2664                                   AttributeSet cssRule = styles.getRule((HTML.Tag) key, elem);
 2665                                   if (cssRule != null) {
 2666                                       muxList.addElement(cssRule);
 2667                                   }
 2668                               }
 2669                           }
 2670                       } else {
 2671                           HTML.Tag t = (HTML.Tag) a.getAttribute
 2672                                        (StyleConstants.NameAttribute);
 2673                           AttributeSet cssRule = styles.getRule(t, elem);
 2674                           if (cssRule != null) {
 2675                               muxList.addElement(cssRule);
 2676                           }
 2677                       }
 2678                   }
 2679                   AttributeSet[] attrs = new AttributeSet[muxList.size()];
 2680                   muxList.copyInto(attrs);
 2681                   setAttributes(attrs);
 2682               }
 2683               finally {
 2684                   SearchBuffer.releaseSearchBuffer(sb);
 2685               }
 2686           }
 2687   
 2688           //  --- AttributeSet methods ----------------------------
 2689   
 2690           /**
 2691            * Checks whether a given attribute is defined.
 2692            * This will convert the key over to CSS if the
 2693            * key is a StyleConstants key that has a CSS
 2694            * mapping.
 2695            *
 2696            * @param key the attribute key
 2697            * @return true if the attribute is defined
 2698            * @see AttributeSet#isDefined
 2699            */
 2700           public boolean isDefined(Object key) {
 2701               if (key instanceof StyleConstants) {
 2702                   Object cssKey = css.styleConstantsKeyToCSSKey
 2703                                       ((StyleConstants)key);
 2704                   if (cssKey != null) {
 2705                       key = cssKey;
 2706                   }
 2707               }
 2708               return super.isDefined(key);
 2709           }
 2710   
 2711           /**
 2712            * Gets the value of an attribute.  If the requested
 2713            * attribute is a StyleConstants attribute that has
 2714            * a CSS mapping, the request will be converted.
 2715            *
 2716            * @param key the attribute name
 2717            * @return the attribute value
 2718            * @see AttributeSet#getAttribute
 2719            */
 2720           public Object getAttribute(Object key) {
 2721               if (key instanceof StyleConstants) {
 2722                   Object cssKey = css.styleConstantsKeyToCSSKey
 2723                                  ((StyleConstants)key);
 2724                   if (cssKey != null) {
 2725                       Object value = doGetAttribute(cssKey);
 2726                       if (value instanceof CSS.CssValue) {
 2727                           return ((CSS.CssValue)value).toStyleConstants
 2728                                        ((StyleConstants)key, host);
 2729                       }
 2730                   }
 2731               }
 2732               return doGetAttribute(key);
 2733           }
 2734   
 2735           Object doGetAttribute(Object key) {
 2736               Object retValue = super.getAttribute(key);
 2737               if (retValue != null) {
 2738                   return retValue;
 2739               }
 2740               // didn't find it... try parent if it's a css attribute
 2741               // that is inherited.
 2742               if (key instanceof CSS.Attribute) {
 2743                   CSS.Attribute css = (CSS.Attribute) key;
 2744                   if (css.isInherited()) {
 2745                       AttributeSet parent = getResolveParent();
 2746                       if (parent != null)
 2747                           return parent.getAttribute(key);
 2748                   }
 2749               }
 2750               return null;
 2751           }
 2752   
 2753           /**
 2754            * If not overriden, the resolving parent defaults to
 2755            * the parent element.
 2756            *
 2757            * @return the attributes from the parent
 2758            * @see AttributeSet#getResolveParent
 2759            */
 2760           public AttributeSet getResolveParent() {
 2761               if (host == null) {
 2762                   return null;
 2763               }
 2764               View parent = host.getParent();
 2765               return (parent != null) ? parent.getAttributes() : null;
 2766           }
 2767   
 2768           /** View created for. */
 2769           View host;
 2770       }
 2771   
 2772   
 2773       /**
 2774        * A subclass of MuxingAttributeSet that implements Style. Currently
 2775        * the MutableAttributeSet methods are unimplemented, that is they
 2776        * do nothing.
 2777        */
 2778       // PENDING(sky): Decide what to do with this. Either make it
 2779       // contain a SimpleAttributeSet that modify methods are delegated to,
 2780       // or change getRule to return an AttributeSet and then don't make this
 2781       // implement Style.
 2782       static class ResolvedStyle extends MuxingAttributeSet implements
 2783                     Serializable, Style {
 2784           ResolvedStyle(String name, AttributeSet[] attrs, int extendedIndex) {
 2785               super(attrs);
 2786               this.name = name;
 2787               this.extendedIndex = extendedIndex;
 2788           }
 2789   
 2790           /**
 2791            * Inserts a Style into the receiver so that the styles the
 2792            * receiver represents are still ordered by specificity.
 2793            * <code>style</code> will be added before any extended styles, that
 2794            * is before extendedIndex.
 2795            */
 2796           synchronized void insertStyle(Style style, int specificity) {
 2797               AttributeSet[] attrs = getAttributes();
 2798               int maxCounter = attrs.length;
 2799               int counter = 0;
 2800               for (;counter < extendedIndex; counter++) {
 2801                   if (specificity > getSpecificity(((Style)attrs[counter]).
 2802                                                    getName())) {
 2803                       break;
 2804                   }
 2805               }
 2806               insertAttributeSetAt(style, counter);
 2807               extendedIndex++;
 2808           }
 2809   
 2810           /**
 2811            * Removes a previously added style. This will do nothing if
 2812            * <code>style</code> is not referenced by the receiver.
 2813            */
 2814           synchronized void removeStyle(Style style) {
 2815               AttributeSet[] attrs = getAttributes();
 2816   
 2817               for (int counter = attrs.length - 1; counter >= 0; counter--) {
 2818                   if (attrs[counter] == style) {
 2819                       removeAttributeSetAt(counter);
 2820                       if (counter < extendedIndex) {
 2821                           extendedIndex--;
 2822                       }
 2823                       break;
 2824                   }
 2825               }
 2826           }
 2827   
 2828           /**
 2829            * Adds <code>s</code> as one of the Attributesets to look up
 2830            * attributes in.
 2831            */
 2832           synchronized void insertExtendedStyleAt(Style attr, int index) {
 2833               insertAttributeSetAt(attr, extendedIndex + index);
 2834           }
 2835   
 2836           /**
 2837            * Adds <code>s</code> as one of the AttributeSets to look up
 2838            * attributes in. It will be the AttributeSet last checked.
 2839            */
 2840           synchronized void addExtendedStyle(Style attr) {
 2841               insertAttributeSetAt(attr, getAttributes().length);
 2842           }
 2843   
 2844           /**
 2845            * Removes the style at <code>index</code> +
 2846            * <code>extendedIndex</code>.
 2847            */
 2848           synchronized void removeExtendedStyleAt(int index) {
 2849               removeAttributeSetAt(extendedIndex + index);
 2850           }
 2851   
 2852           /**
 2853            * Returns true if the receiver matches <code>selector</code>, where
 2854            * a match is defined by the CSS rule matching.
 2855            * Each simple selector must be separated by a single space.
 2856            */
 2857           protected boolean matches(String selector) {
 2858               int sLast = selector.length();
 2859   
 2860               if (sLast == 0) {
 2861                   return false;
 2862               }
 2863               int thisLast = name.length();
 2864               int sCurrent = selector.lastIndexOf(' ');
 2865               int thisCurrent = name.lastIndexOf(' ');
 2866               if (sCurrent >= 0) {
 2867                   sCurrent++;
 2868               }
 2869               if (thisCurrent >= 0) {
 2870                   thisCurrent++;
 2871               }
 2872               if (!matches(selector, sCurrent, sLast, thisCurrent, thisLast)) {
 2873                   return false;
 2874               }
 2875               while (sCurrent != -1) {
 2876                   sLast = sCurrent - 1;
 2877                   sCurrent = selector.lastIndexOf(' ', sLast - 1);
 2878                   if (sCurrent >= 0) {
 2879                       sCurrent++;
 2880                   }
 2881                   boolean match = false;
 2882                   while (!match && thisCurrent != -1) {
 2883                       thisLast = thisCurrent - 1;
 2884                       thisCurrent = name.lastIndexOf(' ', thisLast - 1);
 2885                       if (thisCurrent >= 0) {
 2886                           thisCurrent++;
 2887                       }
 2888                       match = matches(selector, sCurrent, sLast, thisCurrent,
 2889                                       thisLast);
 2890                   }
 2891                   if (!match) {
 2892                       return false;
 2893                   }
 2894               }
 2895               return true;
 2896           }
 2897   
 2898           /**
 2899            * Returns true if the substring of the receiver, in the range
 2900            * thisCurrent, thisLast matches the substring of selector in
 2901            * the ranme sCurrent to sLast based on CSS selector matching.
 2902            */
 2903           boolean matches(String selector, int sCurrent, int sLast,
 2904                          int thisCurrent, int thisLast) {
 2905               sCurrent = Math.max(sCurrent, 0);
 2906               thisCurrent = Math.max(thisCurrent, 0);
 2907               int thisDotIndex = boundedIndexOf(name, '.', thisCurrent,
 2908                                                 thisLast);
 2909               int thisPoundIndex = boundedIndexOf(name, '#', thisCurrent,
 2910                                                   thisLast);
 2911               int sDotIndex = boundedIndexOf(selector, '.', sCurrent, sLast);
 2912               int sPoundIndex = boundedIndexOf(selector, '#', sCurrent, sLast);
 2913               if (sDotIndex != -1) {
 2914                   // Selector has a '.', which indicates name must match it,
 2915                   // or if the '.' starts the selector than name must have
 2916                   // the same class (doesn't matter what element name).
 2917                   if (thisDotIndex == -1) {
 2918                       return false;
 2919                   }
 2920                   if (sCurrent == sDotIndex) {
 2921                       if ((thisLast - thisDotIndex) != (sLast - sDotIndex) ||
 2922                           !selector.regionMatches(sCurrent, name, thisDotIndex,
 2923                                                   (thisLast - thisDotIndex))) {
 2924                           return false;
 2925                       }
 2926                   }
 2927                   else {
 2928                       // Has to fully match.
 2929                       if ((sLast - sCurrent) != (thisLast - thisCurrent) ||
 2930                           !selector.regionMatches(sCurrent, name, thisCurrent,
 2931                                                   (thisLast - thisCurrent))) {
 2932                           return false;
 2933                       }
 2934                   }
 2935                   return true;
 2936               }
 2937               if (sPoundIndex != -1) {
 2938                   // Selector has a '#', which indicates name must match it,
 2939                   // or if the '#' starts the selector than name must have
 2940                   // the same id (doesn't matter what element name).
 2941                   if (thisPoundIndex == -1) {
 2942                       return false;
 2943                   }
 2944                   if (sCurrent == sPoundIndex) {
 2945                       if ((thisLast - thisPoundIndex) !=(sLast - sPoundIndex) ||
 2946                           !selector.regionMatches(sCurrent, name, thisPoundIndex,
 2947                                                   (thisLast - thisPoundIndex))) {
 2948                           return false;
 2949                       }
 2950                   }
 2951                   else {
 2952                       // Has to fully match.
 2953                       if ((sLast - sCurrent) != (thisLast - thisCurrent) ||
 2954                           !selector.regionMatches(sCurrent, name, thisCurrent,
 2955                                                  (thisLast - thisCurrent))) {
 2956                           return false;
 2957                       }
 2958                   }
 2959                   return true;
 2960               }
 2961               if (thisDotIndex != -1) {
 2962                   // Reciever references a class, just check element name.
 2963                   return (((thisDotIndex - thisCurrent) == (sLast - sCurrent)) &&
 2964                           selector.regionMatches(sCurrent, name, thisCurrent,
 2965                                                  thisDotIndex - thisCurrent));
 2966               }
 2967               if (thisPoundIndex != -1) {
 2968                   // Reciever references an id, just check element name.
 2969                   return (((thisPoundIndex - thisCurrent) ==(sLast - sCurrent))&&
 2970                           selector.regionMatches(sCurrent, name, thisCurrent,
 2971                                                  thisPoundIndex - thisCurrent));
 2972               }
 2973               // Fail through, no classes or ides, just check string.
 2974               return (((thisLast - thisCurrent) == (sLast - sCurrent)) &&
 2975                       selector.regionMatches(sCurrent, name, thisCurrent,
 2976                                              thisLast - thisCurrent));
 2977           }
 2978   
 2979           /**
 2980            * Similiar to String.indexOf, but allows an upper bound
 2981            * (this is slower in that it will still check string starting at
 2982            * start.
 2983            */
 2984           int boundedIndexOf(String string, char search, int start,
 2985                              int end) {
 2986               int retValue = string.indexOf(search, start);
 2987               if (retValue >= end) {
 2988                   return -1;
 2989               }
 2990               return retValue;
 2991           }
 2992   
 2993           public void addAttribute(Object name, Object value) {}
 2994           public void addAttributes(AttributeSet attributes) {}
 2995           public void removeAttribute(Object name) {}
 2996           public void removeAttributes(Enumeration<?> names) {}
 2997           public void removeAttributes(AttributeSet attributes) {}
 2998           public void setResolveParent(AttributeSet parent) {}
 2999           public String getName() {return name;}
 3000           public void addChangeListener(ChangeListener l) {}
 3001           public void removeChangeListener(ChangeListener l) {}
 3002           public ChangeListener[] getChangeListeners() {
 3003               return new ChangeListener[0];
 3004           }
 3005   
 3006           /** The name of the Style, which is the selector.
 3007            * This will NEVER change!
 3008            */
 3009           String name;
 3010           /** Start index of styles coming from other StyleSheets. */
 3011           private int extendedIndex;
 3012       }
 3013   
 3014   
 3015       /**
 3016        * SelectorMapping contains a specifitiy, as an integer, and an associated
 3017        * Style. It can also reference children <code>SelectorMapping</code>s,
 3018        * so that it behaves like a tree.
 3019        * <p>
 3020        * This is not thread safe, it is assumed the caller will take the
 3021        * necessary precations if this is to be used in a threaded environment.
 3022        */
 3023       static class SelectorMapping implements Serializable {
 3024           public SelectorMapping(int specificity) {
 3025               this.specificity = specificity;
 3026           }
 3027   
 3028           /**
 3029            * Returns the specificity this mapping represents.
 3030            */
 3031           public int getSpecificity() {
 3032               return specificity;
 3033           }
 3034   
 3035           /**
 3036            * Sets the Style associated with this mapping.
 3037            */
 3038           public void setStyle(Style style) {
 3039               this.style = style;
 3040           }
 3041   
 3042           /**
 3043            * Returns the Style associated with this mapping.
 3044            */
 3045           public Style getStyle() {
 3046               return style;
 3047           }
 3048   
 3049           /**
 3050            * Returns the child mapping identified by the simple selector
 3051            * <code>selector</code>. If a child mapping does not exist for
 3052            *<code>selector</code>, and <code>create</code> is true, a new
 3053            * one will be created.
 3054            */
 3055           public SelectorMapping getChildSelectorMapping(String selector,
 3056                                                          boolean create) {
 3057               SelectorMapping retValue = null;
 3058   
 3059               if (children != null) {
 3060                   retValue = children.get(selector);
 3061               }
 3062               else if (create) {
 3063                   children = new HashMap<String, SelectorMapping>(7);
 3064               }
 3065               if (retValue == null && create) {
 3066                   int specificity = getChildSpecificity(selector);
 3067   
 3068                   retValue = createChildSelectorMapping(specificity);
 3069                   children.put(selector, retValue);
 3070               }
 3071               return retValue;
 3072           }
 3073   
 3074           /**
 3075            * Creates a child <code>SelectorMapping</code> with the specified
 3076            * <code>specificity</code>.
 3077            */
 3078           protected SelectorMapping createChildSelectorMapping(int specificity) {
 3079               return new SelectorMapping(specificity);
 3080           }
 3081   
 3082           /**
 3083            * Returns the specificity for the child selector
 3084            * <code>selector</code>.
 3085            */
 3086           protected int getChildSpecificity(String selector) {
 3087               // class (.) 100
 3088               // id (#)    10000
 3089               char    firstChar = selector.charAt(0);
 3090               int     specificity = getSpecificity();
 3091   
 3092               if (firstChar == '.') {
 3093                   specificity += 100;
 3094               }
 3095               else if (firstChar == '#') {
 3096                   specificity += 10000;
 3097               }
 3098               else {
 3099                   specificity += 1;
 3100                   if (selector.indexOf('.') != -1) {
 3101                       specificity += 100;
 3102                   }
 3103                   if (selector.indexOf('#') != -1) {
 3104                       specificity += 10000;
 3105                   }
 3106               }
 3107               return specificity;
 3108           }
 3109   
 3110           /**
 3111            * The specificity for this selector.
 3112            */
 3113           private int specificity;
 3114           /**
 3115            * Style for this selector.
 3116            */
 3117           private Style style;
 3118           /**
 3119            * Any sub selectors. Key will be String, and value will be
 3120            * another SelectorMapping.
 3121            */
 3122           private HashMap<String, SelectorMapping> children;
 3123       }
 3124   
 3125   
 3126       // ---- Variables ---------------------------------------------
 3127   
 3128       final static int DEFAULT_FONT_SIZE = 3;
 3129   
 3130       private CSS css;
 3131   
 3132       /**
 3133        * An inverted graph of the selectors.
 3134        */
 3135       private SelectorMapping selectorMapping;
 3136   
 3137       /** Maps from selector (as a string) to Style that includes all
 3138        * relevant styles. */
 3139       private Hashtable<String, ResolvedStyle> resolvedStyles;
 3140   
 3141       /** Vector of StyleSheets that the rules are to reference.
 3142        */
 3143       private Vector<StyleSheet> linkedStyleSheets;
 3144   
 3145       /** Where the style sheet was found. Used for relative imports. */
 3146       private URL base;
 3147   
 3148   
 3149       /**
 3150        * Default parser for CSS specifications that get loaded into
 3151        * the StyleSheet.<p>
 3152        * This class is NOT thread safe, do not ask it to parse while it is
 3153        * in the middle of parsing.
 3154        */
 3155       class CssParser implements CSSParser.CSSParserCallback {
 3156   
 3157           /**
 3158            * Parses the passed in CSS declaration into an AttributeSet.
 3159            */
 3160           public AttributeSet parseDeclaration(String string) {
 3161               try {
 3162                   return parseDeclaration(new StringReader(string));
 3163               } catch (IOException ioe) {}
 3164               return null;
 3165           }
 3166   
 3167           /**
 3168            * Parses the passed in CSS declaration into an AttributeSet.
 3169            */
 3170           public AttributeSet parseDeclaration(Reader r) throws IOException {
 3171               parse(base, r, true, false);
 3172               return declaration.copyAttributes();
 3173           }
 3174   
 3175           /**
 3176            * Parse the given CSS stream
 3177            */
 3178           public void parse(URL base, Reader r, boolean parseDeclaration,
 3179                             boolean isLink) throws IOException {
 3180               this.base = base;
 3181               this.isLink = isLink;
 3182               this.parsingDeclaration = parseDeclaration;
 3183               declaration.removeAttributes(declaration);
 3184               selectorTokens.removeAllElements();
 3185               selectors.removeAllElements();
 3186               propertyName = null;
 3187               parser.parse(r, this, parseDeclaration);
 3188           }
 3189   
 3190           //
 3191           // CSSParserCallback methods, public to implement the interface.
 3192           //
 3193   
 3194           /**
 3195            * Invoked when a valid @import is encountered, will call
 3196            * <code>importStyleSheet</code> if a
 3197            * <code>MalformedURLException</code> is not thrown in creating
 3198            * the URL.
 3199            */
 3200           public void handleImport(String importString) {
 3201               URL url = CSS.getURL(base, importString);
 3202               if (url != null) {
 3203                   importStyleSheet(url);
 3204               }
 3205           }
 3206   
 3207           /**
 3208            * A selector has been encountered.
 3209            */
 3210           public void handleSelector(String selector) {
 3211               //class and index selectors are case sensitive
 3212               if (!(selector.startsWith(".")
 3213                     || selector.startsWith("#"))) {
 3214                   selector = selector.toLowerCase();
 3215               }
 3216               int length = selector.length();
 3217   
 3218               if (selector.endsWith(",")) {
 3219                   if (length > 1) {
 3220                       selector = selector.substring(0, length - 1);
 3221                       selectorTokens.addElement(selector);
 3222                   }
 3223                   addSelector();
 3224               }
 3225               else if (length > 0) {
 3226                   selectorTokens.addElement(selector);
 3227               }
 3228           }
 3229   
 3230           /**
 3231            * Invoked when the start of a rule is encountered.
 3232            */
 3233           public void startRule() {
 3234               if (selectorTokens.size() > 0) {
 3235                   addSelector();
 3236               }
 3237               propertyName = null;
 3238           }
 3239   
 3240           /**
 3241            * Invoked when a property name is encountered.
 3242            */
 3243           public void handleProperty(String property) {
 3244               propertyName = property;
 3245           }
 3246   
 3247           /**
 3248            * Invoked when a property value is encountered.
 3249            */
 3250           public void handleValue(String value) {
 3251               if (propertyName != null && value != null && value.length() > 0) {
 3252                   CSS.Attribute cssKey = CSS.getAttribute(propertyName);
 3253                   if (cssKey != null) {
 3254                       // There is currently no mechanism to determine real
 3255                       // base that style sheet was loaded from. For the time
 3256                       // being, this maps for LIST_STYLE_IMAGE, which appear
 3257                       // to be the only one that currently matters. A more
 3258                       // general mechanism is definately needed.
 3259                       if (cssKey == CSS.Attribute.LIST_STYLE_IMAGE) {
 3260                           if (value != null && !value.equals("none")) {
 3261                               URL url = CSS.getURL(base, value);
 3262   
 3263                               if (url != null) {
 3264                                   value = url.toString();
 3265                               }
 3266                           }
 3267                       }
 3268                       addCSSAttribute(declaration, cssKey, value);
 3269                   }
 3270                   propertyName = null;
 3271               }
 3272           }
 3273   
 3274           /**
 3275            * Invoked when the end of a rule is encountered.
 3276            */
 3277           public void endRule() {
 3278               int n = selectors.size();
 3279               for (int i = 0; i < n; i++) {
 3280                   String[] selector = selectors.elementAt(i);
 3281                   if (selector.length > 0) {
 3282                       StyleSheet.this.addRule(selector, declaration, isLink);
 3283                   }
 3284               }
 3285               declaration.removeAttributes(declaration);
 3286               selectors.removeAllElements();
 3287           }
 3288   
 3289           private void addSelector() {
 3290               String[] selector = new String[selectorTokens.size()];
 3291               selectorTokens.copyInto(selector);
 3292               selectors.addElement(selector);
 3293               selectorTokens.removeAllElements();
 3294           }
 3295   
 3296   
 3297           Vector<String[]> selectors = new Vector<String[]>();
 3298           Vector<String> selectorTokens = new Vector<String>();
 3299           /** Name of the current property. */
 3300           String propertyName;
 3301           MutableAttributeSet declaration = new SimpleAttributeSet();
 3302           /** True if parsing a declaration, that is the Reader will not
 3303            * contain a selector. */
 3304           boolean parsingDeclaration;
 3305           /** True if the attributes are coming from a linked/imported style. */
 3306           boolean isLink;
 3307           /** Where the CSS stylesheet lives. */
 3308           URL base;
 3309           CSSParser parser = new CSSParser();
 3310       }
 3311   
 3312       void rebaseSizeMap(int base) {
 3313           final int minimalFontSize = 4;
 3314           sizeMap = new int[sizeMapDefault.length];
 3315           for (int i = 0; i < sizeMapDefault.length; i++) {
 3316               sizeMap[i] = Math.max(base * sizeMapDefault[i] /
 3317                                     sizeMapDefault[CSS.baseFontSizeIndex],
 3318                                     minimalFontSize);
 3319           }
 3320   
 3321       }
 3322   
 3323       int[] getSizeMap() {
 3324           return sizeMap;
 3325       }
 3326       boolean isW3CLengthUnits() {
 3327           return w3cLengthUnits;
 3328       }
 3329   
 3330       /**
 3331        * The HTML/CSS size model has seven slots
 3332        * that one can assign sizes to.
 3333        */
 3334       static final int sizeMapDefault[] = { 8, 10, 12, 14, 18, 24, 36 };
 3335   
 3336       private int sizeMap[] = sizeMapDefault;
 3337       private boolean w3cLengthUnits = false;
 3338   }

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