Save This Page
Home » openjdk-7 » javax » swing » text » html » [javadoc | source]
    1   /*
    2    * Copyright 1997-2005 Sun Microsystems, Inc.  All Rights Reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Sun designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Sun in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
   22    * CA 95054 USA or visit www.sun.com if you need additional information or
   23    * have any questions.
   24    */
   25   package javax.swing.text.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.border;
   35   import javax.swing.event.ChangeListener;
   36   import javax.swing.text;
   37   
   38   /**
   39    * Support for defining the visual characteristics of
   40    * HTML views being rendered.  The StyleSheet is used to
   41    * translate the HTML model into visual characteristics.
   42    * This enables views to be customized by a look-and-feel,
   43    * multiple views over the same model can be rendered
   44    * differently, etc.  This can be thought of as a CSS
   45    * rule repository.  The key for CSS attributes is an
   46    * object of type CSS.Attribute.  The type of the value
   47    * is up to the StyleSheet implementation, but the
   48    * <code>toString</code> method is required
   49    * to return a string representation of CSS value.
   50    * <p>
   51    * The primary entry point for HTML View implementations
   52    * to get their attributes is the
   53    * <a href="#getViewAttributes">getViewAttributes</a>
   54    * method.  This should be implemented to establish the
   55    * desired policy used to associate attributes with the view.
   56    * Each HTMLEditorKit (i.e. and therefore each associated
   57    * JEditorPane) can have its own StyleSheet, but by default one
   58    * sheet will be shared by all of the HTMLEditorKit instances.
   59    * HTMLDocument instance can also have a StyleSheet, which
   60    * holds the document-specific CSS specifications.
   61    * <p>
   62    * In order for Views to store less state and therefore be
   63    * more lightweight, the StyleSheet can act as a factory for
   64    * painters that handle some of the rendering tasks.  This allows
   65    * implementations to determine what they want to cache
   66    * and have the sharing potentially at the level that a
   67    * selector is common to multiple views.  Since the StyleSheet
   68    * may be used by views over multiple documents and typically
   69    * the HTML attributes don't effect the selector being used,
   70    * the potential for sharing is significant.
   71    * <p>
   72    * The rules are stored as named styles, and other information
   73    * is stored to translate the context of an element to a
   74    * rule quickly.  The following code fragment will display
   75    * the named styles, and therefore the CSS rules contained.
   76    * <code><pre>
   77    * &nbsp;
   78    * &nbsp; import java.util.*;
   79    * &nbsp; import javax.swing.text.*;
   80    * &nbsp; import javax.swing.text.html.*;
   81    * &nbsp;
   82    * &nbsp; public class ShowStyles {
   83    * &nbsp;
   84    * &nbsp;     public static void main(String[] args) {
   85    * &nbsp;       HTMLEditorKit kit = new HTMLEditorKit();
   86    * &nbsp;       HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
   87    * &nbsp;       StyleSheet styles = doc.getStyleSheet();
   88    * &nbsp;
   89    * &nbsp;       Enumeration rules = styles.getStyleNames();
   90    * &nbsp;       while (rules.hasMoreElements()) {
   91    * &nbsp;           String name = (String) rules.nextElement();
   92    * &nbsp;           Style rule = styles.getStyle(name);
   93    * &nbsp;           System.out.println(rule.toString());
   94    * &nbsp;       }
   95    * &nbsp;       System.exit(0);
   96    * &nbsp;     }
   97    * &nbsp; }
   98    * &nbsp;
   99    * </pre></code>
  100    * <p>
  101    * The semantics for when a CSS style should overide visual attributes
  102    * defined by an element are not well defined. For example, the html
  103    * <code>&lt;body bgcolor=red&gt;</code> makes the body have a red
  104    * background. But if the html file also contains the CSS rule
  105    * <code>body { background: blue }</code> it becomes less clear as to
  106    * what color the background of the body should be. The current
  107    * implemention gives visual attributes defined in the element the
  108    * highest precedence, that is they are always checked before any styles.
  109    * Therefore, in the previous example the background would have a
  110    * red color as the body element defines the background color to be red.
  111    * <p>
  112    * As already mentioned this supports CSS. We don't support the full CSS
  113    * spec. Refer to the javadoc of the CSS class to see what properties
  114    * we support. The two major CSS parsing related
  115    * concepts we do not currently
  116    * support are pseudo selectors, such as <code>A:link { color: red }</code>,
  117    * and the <code>important</code> modifier.
  118    * <p>
  119    * <font color="red">Note: This implementation is currently
  120    * incomplete.  It can be replaced with alternative implementations
  121    * that are complete.  Future versions of this class will provide
  122    * better CSS support.</font>
  123    *
  124    * @author  Timothy Prinzing
  125    * @author  Sunita Mani
  126    * @author  Sara Swanson
  127    * @author  Jill Nakata
  128    */
  129   public class StyleSheet extends StyleContext {
  130       // As the javadoc states, this class maintains a mapping between
  131       // a CSS selector (such as p.bar) and a Style.
  132       // This consists of a number of parts:
  133       // . Each selector is broken down into its constituent simple selectors,
  134       //   and stored in an inverted graph, for example:
  135       //     p { color: red } ol p { font-size: 10pt } ul p { font-size: 12pt }
  136       //   results in the graph:
  137       //          root
  138       //           |
  139       //           p
  140       //          / \
  141       //         ol ul
  142       //   each node (an instance of SelectorMapping) has an associated
  143       //   specificity and potentially a Style.
  144       // . Every rule that is asked for (either by way of getRule(String) or
  145       //   getRule(HTML.Tag, Element)) results in a unique instance of
  146       //   ResolvedStyle. ResolvedStyles contain the AttributeSets from the
  147       //   SelectorMapping.
  148       // . When a new rule is created it is inserted into the graph, and
  149       //   the AttributeSets of each ResolvedStyles are updated appropriately.
  150       // . This class creates special AttributeSets, LargeConversionSet and
  151       //   SmallConversionSet, that maintain a mapping between StyleConstants
  152       //   and CSS so that developers that wish to use the StyleConstants
  153       //   methods can do so.
  154       // . When one of the AttributeSets is mutated by way of a
  155       //   StyleConstants key, all the associated CSS keys are removed. This is
  156       //   done so that the two representations don't get out of sync. For
  157       //   example, if the developer adds StyleConsants.BOLD, FALSE to an
  158       //   AttributeSet that contains HTML.Tag.B, the HTML.Tag.B entry will
  159       //   be removed.
  160   
  161       /**
  162        * Construct a StyleSheet
  163        */
  164       public StyleSheet() {
  165           super();
  166           selectorMapping = new SelectorMapping(0);
  167           resolvedStyles = new Hashtable();
  168           if (css == null) {
  169               css = new CSS();
  170           }
  171       }
  172   
  173       /**
  174        * Fetches the style to use to render the given type
  175        * of HTML tag.  The element given is representing
  176        * the tag and can be used to determine the nesting
  177        * for situations where the attributes will differ
  178        * if nesting inside of elements.
  179        *
  180        * @param t the type to translate to visual attributes
  181        * @param e the element representing the tag; the element
  182        *  can be used to determine the nesting for situations where
  183        *  the attributes will differ if nested inside of other
  184        *  elements
  185        * @return the set of CSS attributes to use to render
  186        *  the tag
  187        */
  188       public Style getRule(HTML.Tag t, Element e) {
  189           SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  190   
  191           try {
  192               // Build an array of all the parent elements.
  193               Vector searchContext = sb.getVector();
  194   
  195               for (Element p = e; p != null; p = p.getParentElement()) {
  196                   searchContext.addElement(p);
  197               }
  198   
  199               // Build a fully qualified selector.
  200               int              n = searchContext.size();
  201               StringBuffer     cacheLookup = sb.getStringBuffer();
  202               AttributeSet     attr;
  203               String           eName;
  204               Object           name;
  205   
  206               // >= 1 as the HTML.Tag for the 0th element is passed in.
  207               for (int counter = n - 1; counter >= 1; counter--) {
  208                   e = (Element)searchContext.elementAt(counter);
  209                   attr = e.getAttributes();
  210                   name = attr.getAttribute(StyleConstants.NameAttribute);
  211                   eName = name.toString();
  212                   cacheLookup.append(eName);
  213                   if (attr != null) {
  214                       if (attr.isDefined(HTML.Attribute.ID)) {
  215                           cacheLookup.append('#');
  216                           cacheLookup.append(attr.getAttribute
  217                                              (HTML.Attribute.ID));
  218                       }
  219                       else if (attr.isDefined(HTML.Attribute.CLASS)) {
  220                           cacheLookup.append('.');
  221                           cacheLookup.append(attr.getAttribute
  222                                              (HTML.Attribute.CLASS));
  223                       }
  224                   }
  225                   cacheLookup.append(' ');
  226               }
  227               cacheLookup.append(t.toString());
  228               e = (Element)searchContext.elementAt(0);
  229               attr = e.getAttributes();
  230               if (e.isLeaf()) {
  231                   // For leafs, we use the second tier attributes.
  232                   Object testAttr = attr.getAttribute(t);
  233                   if (testAttr instanceof AttributeSet) {
  234                       attr = (AttributeSet)testAttr;
  235                   }
  236                   else {
  237                       attr = null;
  238                   }
  239               }
  240               if (attr != null) {
  241                   if (attr.isDefined(HTML.Attribute.ID)) {
  242                       cacheLookup.append('#');
  243                       cacheLookup.append(attr.getAttribute(HTML.Attribute.ID));
  244                   }
  245                   else if (attr.isDefined(HTML.Attribute.CLASS)) {
  246                       cacheLookup.append('.');
  247                       cacheLookup.append(attr.getAttribute
  248                                          (HTML.Attribute.CLASS));
  249                   }
  250               }
  251   
  252               Style style = getResolvedStyle(cacheLookup.toString(),
  253                                              searchContext, t);
  254               return style;
  255           }
  256           finally {
  257               SearchBuffer.releaseSearchBuffer(sb);
  258           }
  259       }
  260   
  261       /**
  262        * Fetches the rule that best matches the selector given
  263        * in string form. Where <code>selector</code> is a space separated
  264        * String of the element names. For example, <code>selector</code>
  265        * might be 'html body tr td''<p>
  266        * The attributes of the returned Style will change
  267        * as rules are added and removed. That is if you to ask for a rule
  268        * with a selector "table p" and a new rule was added with a selector
  269        * of "p" the returned Style would include the new attributes from
  270        * the rule "p".
  271        */
  272       public Style getRule(String selector) {
  273           selector = cleanSelectorString(selector);
  274           if (selector != null) {
  275               Style style = getResolvedStyle(selector);
  276               return style;
  277           }
  278           return null;
  279       }
  280   
  281       /**
  282        * Adds a set of rules to the sheet.  The rules are expected to
  283        * be in valid CSS format.  Typically this would be called as
  284        * a result of parsing a &lt;style&gt; tag.
  285        */
  286       public void addRule(String rule) {
  287           if (rule != null) {
  288               //tweaks to control display properties
  289               //see BasicEditorPaneUI
  290               final String baseUnitsDisable = "BASE_SIZE_DISABLE";
  291               final String baseUnits = "BASE_SIZE ";
  292               final String w3cLengthUnitsEnable = "W3C_LENGTH_UNITS_ENABLE";
  293               final String w3cLengthUnitsDisable = "W3C_LENGTH_UNITS_DISABLE";
  294               if (rule == baseUnitsDisable) {
  295                   sizeMap = sizeMapDefault;
  296               } else if (rule.startsWith(baseUnits)) {
  297                   rebaseSizeMap(Integer.
  298                                 parseInt(rule.substring(baseUnits.length())));
  299               } else if (rule == w3cLengthUnitsEnable) {
  300                   w3cLengthUnits = true;
  301               } else if (rule == w3cLengthUnitsDisable) {
  302                   w3cLengthUnits = false;
  303               } else {
  304                   CssParser parser = new CssParser();
  305                   try {
  306                       parser.parse(getBase(), new StringReader(rule), false, false);
  307                   } catch (IOException ioe) { }
  308               }
  309           }
  310       }
  311   
  312       /**
  313        * Translates a CSS declaration to an AttributeSet that represents
  314        * the CSS declaration.  Typically this would be called as a
  315        * result of encountering an HTML style attribute.
  316        */
  317       public AttributeSet getDeclaration(String decl) {
  318           if (decl == null) {
  319               return SimpleAttributeSet.EMPTY;
  320           }
  321           CssParser parser = new CssParser();
  322           return parser.parseDeclaration(decl);
  323       }
  324   
  325       /**
  326        * Loads a set of rules that have been specified in terms of
  327        * CSS1 grammar.  If there are collisions with existing rules,
  328        * the newly specified rule will win.
  329        *
  330        * @param in the stream to read the CSS grammar from
  331        * @param ref the reference URL.  This value represents the
  332        *  location of the stream and may be null.  All relative
  333        *  URLs specified in the stream will be based upon this
  334        *  parameter.
  335        */
  336       public void loadRules(Reader in, URL ref) throws IOException {
  337           CssParser parser = new CssParser();
  338           parser.parse(ref, in, false, false);
  339       }
  340   
  341       /**
  342        * Fetches a set of attributes to use in the view for
  343        * displaying.  This is basically a set of attributes that
  344        * can be used for View.getAttributes.
  345        */
  346       public AttributeSet getViewAttributes(View v) {
  347           return new ViewAttributeSet(v);
  348       }
  349   
  350       /**
  351        * Removes a named style previously added to the document.
  352        *
  353        * @param nm  the name of the style to remove
  354        */
  355       public void removeStyle(String nm) {
  356           Style       aStyle = getStyle(nm);
  357   
  358           if (aStyle != null) {
  359               String selector = cleanSelectorString(nm);
  360               String[] selectors = getSimpleSelectors(selector);
  361               synchronized(this) {
  362                   SelectorMapping mapping = getRootSelectorMapping();
  363                   for (int i = selectors.length - 1; i >= 0; i--) {
  364                       mapping = mapping.getChildSelectorMapping(selectors[i],
  365                                                                 true);
  366                   }
  367                   Style rule = mapping.getStyle();
  368                   if (rule != null) {
  369                       mapping.setStyle(null);
  370                       if (resolvedStyles.size() > 0) {
  371                           Enumeration values = resolvedStyles.elements();
  372                           while (values.hasMoreElements()) {
  373                               ResolvedStyle style = (ResolvedStyle)values.
  374                                                       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();
  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 source 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           StringBuffer sb = new StringBuffer();
 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 values = resolvedStyles.elements();
 1049               while (values.hasMoreElements()) {
 1050                   ResolvedStyle rule = (ResolvedStyle)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 values = resolvedStyles.elements();
 1065               while (values.hasMoreElements()) {
 1066                   ResolvedStyle rule = (ResolvedStyle)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 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 = (Style)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 = (Style)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 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 >= ((SelectorMapping)elements.elementAt
 1291                                       (counter)).getSpecificity()) {
 1292                       elements.insertElementAt(mapping, counter);
 1293                       return;
 1294                   }
 1295               }
 1296           }
 1297           elements.addElement(mapping);
 1298       }
 1299   
 1300       /**
 1301        * Adds <code>parentMapping</code> to <code>styles</code>, and
 1302        * recursively calls this method if <code>parentMapping</code> has
 1303        * any child mappings for any of the Elements in <code>elements</code>.
 1304        */
 1305       private synchronized void getStyles(SelectorMapping parentMapping,
 1306                              Vector styles,
 1307                              String[] tags, String[] ids, String[] classes,
 1308                              int index, int numElements,
 1309                              Hashtable alreadyChecked) {
 1310           // Avoid desending the same mapping twice.
 1311           if (alreadyChecked.contains(parentMapping)) {
 1312               return;
 1313           }
 1314           alreadyChecked.put(parentMapping, parentMapping);
 1315           Style style = parentMapping.getStyle();
 1316           if (style != null) {
 1317               addSortedStyle(parentMapping, styles);
 1318           }
 1319           for (int counter = index; counter < numElements; counter++) {
 1320               String tagString = tags[counter];
 1321               if (tagString != null) {
 1322                   SelectorMapping childMapping = parentMapping.
 1323                                   getChildSelectorMapping(tagString, false);
 1324                   if (childMapping != null) {
 1325                       getStyles(childMapping, styles, tags, ids, classes,
 1326                                 counter + 1, numElements, alreadyChecked);
 1327                   }
 1328                   if (classes[counter] != null) {
 1329                       String className = classes[counter];
 1330                       childMapping = parentMapping.getChildSelectorMapping(
 1331                                            tagString + "." + className, false);
 1332                       if (childMapping != null) {
 1333                           getStyles(childMapping, styles, tags, ids, classes,
 1334                                     counter + 1, numElements, alreadyChecked);
 1335                       }
 1336                       childMapping = parentMapping.getChildSelectorMapping(
 1337                                            "." + className, false);
 1338                       if (childMapping != null) {
 1339                           getStyles(childMapping, styles, tags, ids, classes,
 1340                                     counter + 1, numElements, alreadyChecked);
 1341                       }
 1342                   }
 1343                   if (ids[counter] != null) {
 1344                       String idName = ids[counter];
 1345                       childMapping = parentMapping.getChildSelectorMapping(
 1346                                            tagString + "#" + idName, false);
 1347                       if (childMapping != null) {
 1348                           getStyles(childMapping, styles, tags, ids, classes,
 1349                                     counter + 1, numElements, alreadyChecked);
 1350                       }
 1351                       childMapping = parentMapping.getChildSelectorMapping(
 1352                                      "#" + idName, false);
 1353                       if (childMapping != null) {
 1354                           getStyles(childMapping, styles, tags, ids, classes,
 1355                                     counter + 1, numElements, alreadyChecked);
 1356                       }
 1357                   }
 1358               }
 1359           }
 1360       }
 1361   
 1362       /**
 1363        * Creates and returns a Style containing all the rules that match
 1364        *  <code>selector</code>.
 1365        */
 1366       private synchronized Style createResolvedStyle(String selector,
 1367                                         String[] tags,
 1368                                         String[] ids, String[] classes) {
 1369           SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
 1370           Vector tempVector = sb.getVector();
 1371           Hashtable tempHashtable = sb.getHashtable();
 1372           // Determine all the Styles that are appropriate, placing them
 1373           // in tempVector
 1374           try {
 1375               SelectorMapping mapping = getRootSelectorMapping();
 1376               int numElements = tags.length;
 1377               String tagString = tags[0];
 1378               SelectorMapping childMapping = mapping.getChildSelectorMapping(
 1379                                                      tagString, false);
 1380               if (childMapping != null) {
 1381                   getStyles(childMapping, tempVector, tags, ids, classes, 1,
 1382                             numElements, tempHashtable);
 1383               }
 1384               if (classes[0] != null) {
 1385                   String className = classes[0];
 1386                   childMapping = mapping.getChildSelectorMapping(
 1387                                          tagString + "." + className, false);
 1388                   if (childMapping != null) {
 1389                       getStyles(childMapping, tempVector, tags, ids, classes, 1,
 1390                                 numElements, tempHashtable);
 1391                   }
 1392                   childMapping = mapping.getChildSelectorMapping(
 1393                                          "." + className, false);
 1394                   if (childMapping != null) {
 1395                       getStyles(childMapping, tempVector, tags, ids, classes,
 1396                                 1, numElements, tempHashtable);
 1397                   }
 1398               }
 1399               if (ids[0] != null) {
 1400                   String idName = ids[0];
 1401                   childMapping = mapping.getChildSelectorMapping(
 1402                                          tagString + "#" + idName, false);
 1403                   if (childMapping != null) {
 1404                       getStyles(childMapping, tempVector, tags, ids, classes,
 1405                                 1, numElements, tempHashtable);
 1406                   }
 1407                   childMapping = mapping.getChildSelectorMapping(
 1408                                          "#" + idName, false);
 1409                   if (childMapping != null) {
 1410                       getStyles(childMapping, tempVector, tags, ids, classes,
 1411                                 1, numElements, tempHashtable);
 1412                   }
 1413               }
 1414               // Create a new Style that will delegate to all the matching
 1415               // Styles.
 1416               int numLinkedSS = (linkedStyleSheets != null) ?
 1417                                 linkedStyleSheets.size() : 0;
 1418               int numStyles = tempVector.size();
 1419               AttributeSet[] attrs = new AttributeSet[numStyles + numLinkedSS];
 1420               for (int counter = 0; counter < numStyles; counter++) {
 1421                   attrs[counter] = ((SelectorMapping)tempVector.
 1422                                     elementAt(counter)).getStyle();
 1423               }
 1424               // Get the AttributeSet from linked style sheets.
 1425               for (int counter = 0; counter < numLinkedSS; counter++) {
 1426                   AttributeSet attr = ((StyleSheet)linkedStyleSheets.
 1427                                    elementAt(counter)).getRule(selector);
 1428                   if (attr == null) {
 1429                       attrs[counter + numStyles] = SimpleAttributeSet.EMPTY;
 1430                   }
 1431                   else {
 1432                       attrs[counter + numStyles] = attr;
 1433                   }
 1434               }
 1435               ResolvedStyle retStyle = new ResolvedStyle(selector, attrs,
 1436                                                          numStyles);
 1437               resolvedStyles.put(selector, retStyle);
 1438               return retStyle;
 1439           }
 1440           finally {
 1441               SearchBuffer.releaseSearchBuffer(sb);
 1442           }
 1443       }
 1444   
 1445       /**
 1446        * Creates and returns a Style containing all the rules that
 1447        * matches <code>selector</code>.
 1448        *
 1449        * @param elements  a Vector of all the Elements
 1450        *                  the style is being asked for. The
 1451        *                  first Element is the deepest Element, with the last Element
 1452        *                  representing the root.
 1453        * @param t         the Tag to use for
 1454        *                  the first Element in <code>elements</code>
 1455        */
 1456       private Style createResolvedStyle(String selector, Vector elements,
 1457                                         HTML.Tag t) {
 1458           int numElements = elements.size();
 1459           // Build three arrays, one for tags, one for class's, and one for
 1460           // id's
 1461           String tags[] = new String[numElements];
 1462           String ids[] = new String[numElements];
 1463           String classes[] = new String[numElements];
 1464           for (int counter = 0; counter < numElements; counter++) {
 1465               Element e = (Element)elements.elementAt(counter);
 1466               AttributeSet attr = e.getAttributes();
 1467               if (counter == 0 && e.isLeaf()) {
 1468                   // For leafs, we use the second tier attributes.
 1469                   Object testAttr = attr.getAttribute(t);
 1470                   if (testAttr instanceof AttributeSet) {
 1471                       attr = (AttributeSet)testAttr;
 1472                   }
 1473                   else {
 1474                       attr = null;
 1475                   }
 1476               }
 1477               if (attr != null) {
 1478                   HTML.Tag tag = (HTML.Tag)attr.getAttribute(StyleConstants.
 1479                                                              NameAttribute);
 1480                   if (tag != null) {
 1481                       tags[counter] = tag.toString();
 1482                   }
 1483                   else {
 1484                       tags[counter] = null;
 1485                   }
 1486                   if (attr.isDefined(HTML.Attribute.CLASS)) {
 1487                       classes[counter] = attr.getAttribute
 1488                                         (HTML.Attribute.CLASS).toString();
 1489                   }
 1490                   else {
 1491                       classes[counter] = null;
 1492                   }
 1493                   if (attr.isDefined(HTML.Attribute.ID)) {
 1494                       ids[counter] = attr.getAttribute(HTML.Attribute.ID).
 1495                                           toString();
 1496                   }
 1497                   else {
 1498                       ids[counter] = null;
 1499                   }
 1500               }
 1501               else {
 1502                   tags[counter] = ids[counter] = classes[counter] = null;
 1503               }
 1504           }
 1505           tags[0] = t.toString();
 1506           return createResolvedStyle(selector, tags, ids, classes);
 1507       }
 1508   
 1509       /**
 1510        * Creates and returns a Style containing all the rules that match
 1511        *  <code>selector</code>. It is assumed that each simple selector
 1512        * in <code>selector</code> is separated by a space.
 1513        */
 1514       private Style createResolvedStyle(String selector) {
 1515           SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
 1516           // Will contain the tags, ids, and classes, in that order.
 1517           Vector elements = sb.getVector();
 1518           try {
 1519               boolean done;
 1520               int dotIndex = 0;
 1521               int spaceIndex = 0;
 1522               int poundIndex = 0;
 1523               int lastIndex = 0;
 1524               int length = selector.length();
 1525               while (lastIndex < length) {
 1526                   if (dotIndex == lastIndex) {
 1527                       dotIndex = selector.indexOf('.', lastIndex);
 1528                   }
 1529                   if (poundIndex == lastIndex) {
 1530                       poundIndex = selector.indexOf('#', lastIndex);
 1531                   }
 1532                   spaceIndex = selector.indexOf(' ', lastIndex);
 1533                   if (spaceIndex == -1) {
 1534                       spaceIndex = length;
 1535                   }
 1536                   if (dotIndex != -1 && poundIndex != -1 &&
 1537                       dotIndex < spaceIndex && poundIndex < spaceIndex) {
 1538                       if (poundIndex < dotIndex) {
 1539                           // #.
 1540                           if (lastIndex == poundIndex) {
 1541                               elements.addElement("");
 1542                           }
 1543                           else {
 1544                               elements.addElement(selector.substring(lastIndex,
 1545                                                                     poundIndex));
 1546                           }
 1547                           if ((dotIndex + 1) < spaceIndex) {
 1548                               elements.addElement(selector.substring
 1549                                                   (dotIndex + 1, spaceIndex));
 1550                           }
 1551                           else {
 1552                               elements.addElement(null);
 1553                           }
 1554                           if ((poundIndex + 1) == dotIndex) {
 1555                               elements.addElement(null);
 1556                           }
 1557                           else {
 1558                               elements.addElement(selector.substring
 1559                                                   (poundIndex + 1, dotIndex));
 1560                           }
 1561                       }
 1562                       else if(poundIndex < spaceIndex) {
 1563                           // .#
 1564                           if (lastIndex == dotIndex) {
 1565                               elements.addElement("");
 1566                           }
 1567                           else {
 1568                               elements.addElement(selector.substring(lastIndex,
 1569                                                                     dotIndex));
 1570                           }
 1571                           if ((dotIndex + 1) < poundIndex) {
 1572                               elements.addElement(selector.substring
 1573                                                   (dotIndex + 1, poundIndex));
 1574                           }
 1575                           else {
 1576                               elements.addElement(null);
 1577                           }
 1578                           if ((poundIndex + 1) == spaceIndex) {
 1579                               elements.addElement(null);
 1580                           }
 1581                           else {
 1582                               elements.addElement(selector.substring
 1583                                                   (poundIndex + 1, spaceIndex));
 1584                           }
 1585                       }
 1586                       dotIndex = poundIndex = spaceIndex + 1;
 1587                   }
 1588                   else if (dotIndex != -1 && dotIndex < spaceIndex) {
 1589                       // .
 1590                       if (dotIndex == lastIndex) {
 1591                           elements.addElement("");
 1592                       }
 1593                       else {
 1594                           elements.addElement(selector.substring(lastIndex,
 1595                                                                  dotIndex));
 1596                       }
 1597                       if ((dotIndex + 1) == spaceIndex) {
 1598                           elements.addElement(null);
 1599                       }
 1600                       else {
 1601                           elements.addElement(selector.substring(dotIndex + 1,
 1602                                                                  spaceIndex));
 1603                       }
 1604                       elements.addElement(null);
 1605                       dotIndex = spaceIndex + 1;
 1606                   }
 1607                   else if (poundIndex != -1 && poundIndex < spaceIndex) {
 1608                       // #
 1609                       if (poundIndex == lastIndex) {
 1610                           elements.addElement("");
 1611                       }
 1612                       else {
 1613                           elements.addElement(selector.substring(lastIndex,
 1614                                                                  poundIndex));
 1615                       }
 1616                       elements.addElement(null);
 1617                       if ((poundIndex + 1) == spaceIndex) {
 1618                           elements.addElement(null);
 1619                       }
 1620                       else {
 1621                           elements.addElement(selector.substring(poundIndex + 1,
 1622                                                                  spaceIndex));
 1623                       }
 1624                       poundIndex = spaceIndex + 1;
 1625                   }
 1626                   else {
 1627                       // id
 1628                       elements.addElement(selector.substring(lastIndex,
 1629                                                              spaceIndex));
 1630                       elements.addElement(null);
 1631                       elements.addElement(null);
 1632                   }
 1633                   lastIndex = spaceIndex + 1;
 1634               }
 1635               // Create the tag, id, and class arrays.
 1636               int total = elements.size();
 1637               int numTags = total / 3;
 1638               String[] tags = new String[numTags];
 1639               String[] ids = new String[numTags];
 1640               String[] classes = new String[numTags];
 1641               for (int index = 0, eIndex = total - 3; index < numTags;
 1642                    index++, eIndex -= 3) {
 1643                   tags[index] = (String)elements.elementAt(eIndex);
 1644                   classes[index] = (String)elements.elementAt(eIndex + 1);
 1645                   ids[index] = (String)elements.elementAt(eIndex + 2);
 1646               }
 1647               return createResolvedStyle(selector, tags, ids, classes);
 1648           }
 1649           finally {
 1650               SearchBuffer.releaseSearchBuffer(sb);
 1651           }
 1652       }
 1653   
 1654       /**
 1655        * Should be invoked when a new rule is added that did not previously
 1656        * exist. Goes through and refreshes the necessary resolved
 1657        * rules.
 1658        */
 1659       private synchronized void refreshResolvedRules(String selectorName,
 1660                                                      String[] selector,
 1661                                                      Style newStyle,
 1662                                                      int specificity) {
 1663           if (resolvedStyles.size() > 0) {
 1664               Enumeration values = resolvedStyles.elements();
 1665               while (values.hasMoreElements()) {
 1666                   ResolvedStyle style = (ResolvedStyle)values.nextElement();
 1667                   if (style.matches(selectorName)) {
 1668                       style.insertStyle(newStyle, specificity);
 1669                   }
 1670               }
 1671           }
 1672       }
 1673   
 1674   
 1675       /**
 1676        * A temporary class used to hold a Vector, a StringBuffer and a
 1677        * Hashtable. This is used to avoid allocing a lot of garbage when
 1678        * searching for rules. Use the static method obtainSearchBuffer and
 1679        * releaseSearchBuffer to get a SearchBuffer, and release it when
 1680        * done.
 1681        */
 1682       private static class SearchBuffer {
 1683           /** A stack containing instances of SearchBuffer. Used in getting
 1684            * rules. */
 1685           static Stack searchBuffers = new Stack();
 1686           // A set of temporary variables that can be used in whatever way.
 1687           Vector vector = null;
 1688           StringBuffer stringBuffer = null;
 1689           Hashtable hashtable = null;
 1690   
 1691           /**
 1692            * Returns an instance of SearchBuffer. Be sure and issue
 1693            * a releaseSearchBuffer when done with it.
 1694            */
 1695           static SearchBuffer obtainSearchBuffer() {
 1696               SearchBuffer sb;
 1697               try {
 1698                   if(!searchBuffers.empty()) {
 1699                      sb = (SearchBuffer)searchBuffers.pop();
 1700                   } else {
 1701                      sb = new SearchBuffer();
 1702                   }
 1703               } catch (EmptyStackException ese) {
 1704                   sb = new SearchBuffer();
 1705               }
 1706               return sb;
 1707           }
 1708   
 1709           /**
 1710            * Adds <code>sb</code> to the stack of SearchBuffers that can
 1711            * be used.
 1712            */
 1713           static void releaseSearchBuffer(SearchBuffer sb) {
 1714               sb.empty();
 1715               searchBuffers.push(sb);
 1716           }
 1717   
 1718           StringBuffer getStringBuffer() {
 1719               if (stringBuffer == null) {
 1720                   stringBuffer = new StringBuffer();
 1721               }
 1722               return stringBuffer;
 1723           }
 1724   
 1725           Vector getVector() {
 1726               if (vector == null) {
 1727                   vector = new Vector();
 1728               }
 1729               return vector;
 1730           }
 1731   
 1732           Hashtable getHashtable() {
 1733               if (hashtable == null) {
 1734                   hashtable = new Hashtable();
 1735               }
 1736               return hashtable;
 1737           }
 1738   
 1739           void empty() {
 1740               if (stringBuffer != null) {
 1741                   stringBuffer.setLength(0);
 1742               }
 1743               if (vector != null) {
 1744                   vector.removeAllElements();
 1745               }
 1746               if (hashtable != null) {
 1747                   hashtable.clear();
 1748               }
 1749           }
 1750       }
 1751   
 1752   
 1753       static final Border noBorder = new EmptyBorder(0,0,0,0);
 1754   
 1755       /**
 1756        * Class to carry out some of the duties of
 1757        * CSS formatting.  Implementations of this
 1758        * class enable views to present the CSS formatting
 1759        * while not knowing anything about how the CSS values
 1760        * are being cached.
 1761        * <p>
 1762        * As a delegate of Views, this object is responsible for
 1763        * the insets of a View and making sure the background
 1764        * is maintained according to the CSS attributes.
 1765        */
 1766       public static class BoxPainter implements Serializable {
 1767   
 1768           BoxPainter(AttributeSet a, CSS css, StyleSheet ss) {
 1769               this.ss = ss;
 1770               this.css = css;
 1771               border = getBorder(a);
 1772               binsets = border.getBorderInsets(null);
 1773               topMargin = getLength(CSS.Attribute.MARGIN_TOP, a);
 1774               bottomMargin = getLength(CSS.Attribute.MARGIN_BOTTOM, a);
 1775               leftMargin = getLength(CSS.Attribute.MARGIN_LEFT, a);
 1776               rightMargin = getLength(CSS.Attribute.MARGIN_RIGHT, a);
 1777               bg = ss.getBackground(a);
 1778               if (ss.getBackgroundImage(a) != null) {
 1779                   bgPainter = new BackgroundImagePainter(a, css, ss);
 1780               }
 1781           }
 1782   
 1783           /**
 1784            * Fetches a border to render for the given attributes.
 1785            * PENDING(prinz) This is pretty badly hacked at the
 1786            * moment.
 1787            */
 1788           Border getBorder(AttributeSet a) {
 1789               return new CSSBorder(a);
 1790           }
 1791   
 1792           /**
 1793            * Fetches the color to use for borders.  This will either be
 1794            * the value specified by the border-color attribute (which
 1795            * is not inherited), or it will default to the color attribute
 1796            * (which is inherited).
 1797            */
 1798           Color getBorderColor(AttributeSet a) {
 1799               Color color = css.getColor(a, CSS.Attribute.BORDER_COLOR);
 1800               if (color == null) {
 1801                   color = css.getColor(a, CSS.Attribute.COLOR);
 1802                   if (color == null) {
 1803                       return Color.black;
 1804                   }
 1805               }
 1806               return color;
 1807           }
 1808   
 1809           /**
 1810            * Fetches the inset needed on a given side to
 1811            * account for the margin, border, and padding.
 1812            *
 1813            * @param side The size of the box to fetch the
 1814            *  inset for.  This can be View.TOP,
 1815            *  View.LEFT, View.BOTTOM, or View.RIGHT.
 1816            * @param v the view making the request.  This is
 1817            *  used to get the AttributeSet, and may be used to
 1818            *  resolve percentage arguments.
 1819            * @exception IllegalArgumentException for an invalid direction
 1820            */
 1821           public float getInset(int side, View v) {
 1822               AttributeSet a = v.getAttributes();
 1823               float inset = 0;
 1824               switch(side) {
 1825               case View.LEFT:
 1826                   inset += getOrientationMargin(HorizontalMargin.LEFT,
 1827                                                 leftMargin, a, isLeftToRight(v));
 1828                   inset += binsets.left;
 1829                   inset += getLength(CSS.Attribute.PADDING_LEFT, a);
 1830                   break;
 1831               case View.RIGHT:
 1832                   inset += getOrientationMargin(HorizontalMargin.RIGHT,
 1833                                                 rightMargin, a, isLeftToRight(v));
 1834                   inset += binsets.right;
 1835                   inset += getLength(CSS.Attribute.PADDING_RIGHT, a);
 1836                   break;
 1837               case View.TOP:
 1838                   inset += topMargin;
 1839                   inset += binsets.top;
 1840                   inset += getLength(CSS.Attribute.PADDING_TOP, a);
 1841                   break;
 1842               case View.BOTTOM:
 1843                   inset += bottomMargin;
 1844                   inset += binsets.bottom;
 1845                   inset += getLength(CSS.Attribute.PADDING_BOTTOM, a);
 1846                   break;
 1847               default:
 1848                   throw new IllegalArgumentException("Invalid side: " + side);
 1849               }
 1850               return inset;
 1851           }
 1852   
 1853           /**
 1854            * Paints the CSS box according to the attributes
 1855            * given.  This should paint the border, padding,
 1856            * and background.
 1857            *
 1858            * @param g the rendering surface.
 1859            * @param x the x coordinate of the allocated area to
 1860            *  render into.
 1861            * @param y the y coordinate of the allocated area to
 1862            *  render into.
 1863            * @param w the width of the allocated area to render into.
 1864            * @param h the height of the allocated area to render into.
 1865            * @param v the view making the request.  This is
 1866            *  used to get the AttributeSet, and may be used to
 1867            *  resolve percentage arguments.
 1868            */
 1869           public void paint(Graphics g, float x, float y, float w, float h, View v) {
 1870               // PENDING(prinz) implement real rendering... which would
 1871               // do full set of border and background capabilities.
 1872               // remove margin
 1873   
 1874               float dx = 0;
 1875               float dy = 0;
 1876               float dw = 0;
 1877               float dh = 0;
 1878               AttributeSet a = v.getAttributes();
 1879               boolean isLeftToRight = isLeftToRight(v);
 1880               float localLeftMargin = getOrientationMargin(HorizontalMargin.LEFT,
 1881                                                            leftMargin,
 1882                                                            a, isLeftToRight);
 1883               float localRightMargin = getOrientationMargin(HorizontalMargin.RIGHT,
 1884                                                             rightMargin,
 1885                                                             a, isLeftToRight);
 1886               if (!(v instanceof HTMLEditorKit.HTMLFactory.BodyBlockView)) {
 1887                   dx = localLeftMargin;
 1888                   dy = topMargin;
 1889                   dw = -(localLeftMargin + localRightMargin);
 1890                   dh = -(topMargin + bottomMargin);
 1891               }
 1892               if (bg != null) {
 1893                   g.setColor(bg);
 1894                   g.fillRect((int) (x + dx),
 1895                              (int) (y + dy),
 1896                              (int) (w + dw),
 1897                              (int) (h + dh));
 1898               }
 1899               if (bgPainter != null) {
 1900                   bgPainter.paint(g, x + dx, y + dy, w + dw, h + dh, v);
 1901               }
 1902               x += localLeftMargin;
 1903               y += topMargin;
 1904               w -= localLeftMargin + localRightMargin;
 1905               h -= topMargin + bottomMargin;
 1906               if (border instanceof BevelBorder) {
 1907                   //BevelBorder does not support border width
 1908                   int bw = (int) getLength(CSS.Attribute.BORDER_TOP_WIDTH, a);
 1909                   for (int i = bw - 1; i >= 0; i--) {
 1910                       border.paintBorder(null, g, (int) x + i, (int) y + i,
 1911                                          (int) w - 2 * i, (int) h - 2 * i);
 1912                   }
 1913               } else {
 1914                   border.paintBorder(null, g, (int) x, (int) y, (int) w, (int) h);
 1915               }
 1916           }
 1917   
 1918           float getLength(CSS.Attribute key, AttributeSet a) {
 1919               return css.getLength(a, key, ss);
 1920           }
 1921   
 1922           static boolean isLeftToRight(View v) {
 1923               boolean ret = true;
 1924               if (isOrientationAware(v)) {
 1925                   Container container = null;
 1926                   if (v != null && (container = v.getContainer()) != null) {
 1927                       ret = container.getComponentOrientation().isLeftToRight();
 1928                   }
 1929               }
 1930               return ret;
 1931           }
 1932   
 1933           /*
 1934            * only certain tags are concerned about orientation
 1935            * <dir>, <menu>, <ul>, <ol>
 1936            * for all others we return true. It is implemented this way
 1937            * for performance purposes
 1938            */
 1939           static boolean isOrientationAware(View v) {
 1940               boolean ret = false;
 1941               AttributeSet attr = null;
 1942               Object obj = null;
 1943               if (v != null
 1944                   && (attr = v.getElement().getAttributes()) != null
 1945                   && (obj = attr.getAttribute(StyleConstants.NameAttribute)) instanceof HTML.Tag
 1946                   && (obj == HTML.Tag.DIR
 1947                       || obj == HTML.Tag.MENU
 1948                       || obj == HTML.Tag.UL
 1949                       || obj == HTML.Tag.OL)) {
 1950                   ret = true;
 1951               }
 1952   
 1953               return ret;
 1954           }
 1955   
 1956           static enum HorizontalMargin { LEFT, RIGHT };
 1957   
 1958           /**
 1959            * for <dir>, <menu>, <ul> etc.
 1960            * margins are Left-To-Right/Right-To-Left depended.
 1961            * see 5088268 for more details
 1962            * margin-(left|right)-(ltr|rtl) were introduced to describe it
 1963            * if margin-(left|right) is present we are to use it.
 1964            *
 1965            * @param side The horizontal side to fetch margin for
 1966            *  This can be HorizontalMargin.LEFT or HorizontalMargin.RIGHT
 1967            * @param cssMargin margin from css
 1968            * @param a AttributeSet for the View we getting margin for
 1969            * @param isLeftToRight
 1970            * @return orientation depended margin
 1971            */
 1972           float getOrientationMargin(HorizontalMargin side, float cssMargin,
 1973                                      AttributeSet a, boolean isLeftToRight) {
 1974               float margin = cssMargin;
 1975               float orientationMargin = cssMargin;
 1976               Object cssMarginValue = null;
 1977               switch (side) {
 1978               case RIGHT:
 1979                   {
 1980                       orientationMargin = (isLeftToRight) ?
 1981                           getLength(CSS.Attribute.MARGIN_RIGHT_LTR, a) :
 1982                           getLength(CSS.Attribute.MARGIN_RIGHT_RTL, a);
 1983                       cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_RIGHT);
 1984                   }
 1985                   break;
 1986               case LEFT :
 1987                   {
 1988                       orientationMargin = (isLeftToRight) ?
 1989                           getLength(CSS.Attribute.MARGIN_LEFT_LTR, a) :
 1990                           getLength(CSS.Attribute.MARGIN_LEFT_RTL, a);
 1991                       cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_LEFT);
 1992                   }
 1993                   break;
 1994               }
 1995   
 1996               if (cssMarginValue == null
 1997                   && orientationMargin != Integer.MIN_VALUE) {
 1998                   margin = orientationMargin;
 1999               }
 2000               return margin;
 2001           }
 2002   
 2003           float topMargin;
 2004           float bottomMargin;
 2005           float leftMargin;
 2006           float rightMargin;
 2007           // Bitmask, used to indicate what margins are relative:
 2008           // bit 0 for top, 1 for bottom, 2 for left and 3 for right.
 2009           short marginFlags;
 2010           Border border;
 2011           Insets binsets;
 2012           CSS css;
 2013           StyleSheet ss;
 2014           Color bg;
 2015           BackgroundImagePainter bgPainter;
 2016       }
 2017   
 2018       /**
 2019        * Class to carry out some of the duties of CSS list
 2020        * formatting.  Implementations of this
 2021        * class enable views to present the CSS formatting
 2022        * while not knowing anything about how the CSS values
 2023        * are being cached.
 2024        */
 2025       public static class ListPainter implements Serializable {
 2026   
 2027           ListPainter(AttributeSet attr, StyleSheet ss) {
 2028               this.ss = ss;
 2029               /* Get the image to use as a list bullet */
 2030               String imgstr = (String)attr.getAttribute(CSS.Attribute.
 2031                                                         LIST_STYLE_IMAGE);
 2032               type = null;
 2033               if (imgstr != null && !imgstr.equals("none")) {
 2034                   String tmpstr = null;
 2035                   try {
 2036                       StringTokenizer st = new StringTokenizer(imgstr, "()");
 2037                       if (st.hasMoreTokens())
 2038                           tmpstr = st.nextToken();
 2039                       if (st.hasMoreTokens())
 2040                           tmpstr = st.nextToken();
 2041                       URL u = new URL(tmpstr);
 2042                       img = new ImageIcon(u);
 2043                   } catch (MalformedURLException e) {
 2044                       if (tmpstr != null && ss != null && ss.getBase() != null) {
 2045                           try {
 2046                               URL u = new URL(ss.getBase(), tmpstr);
 2047                               img = new ImageIcon(u);
 2048                           } catch (MalformedURLException murle) {
 2049                               img = null;
 2050                           }
 2051                       }
 2052                       else {
 2053                           img = null;
 2054                       }
 2055                   }
 2056               }
 2057   
 2058               /* Get the type of bullet to use in the list */
 2059               if (img == null) {
 2060                   type = (CSS.Value)attr.getAttribute(CSS.Attribute.
 2061                                                       LIST_STYLE_TYPE);
 2062               }
 2063               start = 1;
 2064   
 2065               paintRect = new Rectangle();
 2066           }
 2067   
 2068           /**
 2069            * Returns a string that represents the value
 2070            * of the HTML.Attribute.TYPE attribute.
 2071            * If this attributes is not defined, then
 2072            * then the type defaults to "disc" unless
 2073            * the tag is on Ordered list.  In the case
 2074            * of the latter, the default type is "decimal".
 2075            */
 2076           private CSS.Value getChildType(View childView) {
 2077               CSS.Value childtype = (CSS.Value)childView.getAttributes().
 2078                                     getAttribute(CSS.Attribute.LIST_STYLE_TYPE);
 2079   
 2080               if (childtype == null) {
 2081                   if (type == null) {
 2082                       // Parent view.
 2083                       View v = childView.getParent();
 2084                       HTMLDocument doc = (HTMLDocument)v.getDocument();
 2085                       if (doc.matchNameAttribute(v.getElement().getAttributes(),
 2086                                                  HTML.Tag.OL)) {
 2087                           childtype = CSS.Value.DECIMAL;
 2088                       } else {
 2089                           childtype = CSS.Value.DISC;
 2090                       }
 2091                   } else {
 2092                       childtype = type;
 2093                   }
 2094               }
 2095               return childtype;
 2096           }
 2097   
 2098           /**
 2099            * Obtains the starting index from <code>parent</code>.
 2100            */
 2101           private void getStart(View parent) {
 2102               checkedForStart = true;
 2103               Element element = parent.getElement();
 2104               if (element != null) {
 2105                   AttributeSet attr = element.getAttributes();
 2106                   Object startValue;
 2107                   if (attr != null && attr.isDefined(HTML.Attribute.START) &&
 2108                       (startValue = attr.getAttribute
 2109                        (HTML.Attribute.START)) != null &&
 2110                       (startValue instanceof String)) {
 2111   
 2112                       try {
 2113                           start = Integer.parseInt((String)startValue);
 2114                       }
 2115                       catch (NumberFormatException nfe) {}
 2116                   }
 2117               }
 2118           }
 2119   
 2120           /**
 2121            * Returns an integer that should be used to render the child at
 2122            * <code>childIndex</code> with. The retValue will usually be
 2123            * <code>childIndex</code> + 1, unless <code>parentView</code>
 2124            * has some Views that do not represent LI's, or one of the views
 2125            * has a HTML.Attribute.START specified.
 2126            */
 2127           private int getRenderIndex(View parentView, int childIndex) {
 2128               if (!checkedForStart) {
 2129                   getStart(parentView);
 2130               }
 2131               int retIndex = childIndex;
 2132               for (int counter = childIndex; counter >= 0; counter--) {
 2133                   AttributeSet as = parentView.getElement().getElement(counter).
 2134                                     getAttributes();
 2135                   if (as.getAttribute(StyleConstants.NameAttribute) !=
 2136                       HTML.Tag.LI) {
 2137                       retIndex--;
 2138                   } else if (as.isDefined(HTML.Attribute.VALUE)) {
 2139                       Object value = as.getAttribute(HTML.Attribute.VALUE);
 2140                       if (value != null &&
 2141                           (value instanceof String)) {
 2142                           try {
 2143                               int iValue = Integer.parseInt((String)value);
 2144                               return retIndex - counter + iValue;
 2145                           }
 2146                           catch (NumberFormatException nfe) {}
 2147                       }
 2148                   }
 2149               }
 2150               return retIndex + start;
 2151           }
 2152   
 2153           /**
 2154            * Paints the CSS list decoration according to the
 2155            * attributes given.
 2156            *
 2157            * @param g the rendering surface.
 2158            * @param x the x coordinate of the list item allocation
 2159            * @param y the y coordinate of the list item allocation
 2160            * @param w the width of the list item allocation
 2161            * @param h the height of the list item allocation
 2162            * @param v the allocated area to paint into.
 2163            * @param item which list item is being painted.  This
 2164            *  is a number greater than or equal to 0.
 2165            */
 2166           public void paint(Graphics g, float x, float y, float w, float h, View v, int item) {
 2167               View cv = v.getView(item);
 2168               Object name = cv.getElement().getAttributes().getAttribute
 2169                            (StyleConstants.NameAttribute);
 2170               // Only draw something if the View is a list item. This won't
 2171               // be the case for comments.
 2172               if (!(name instanceof HTML.Tag) ||
 2173                   name != HTML.Tag.LI) {
 2174                   return;
 2175               }
 2176               // deside on what side draw bullets, etc.
 2177               isLeftToRight =
 2178                   cv.getContainer().getComponentOrientation().isLeftToRight();
 2179   
 2180               // How the list indicator is aligned is not specified, it is
 2181               // left up to the UA. IE and NS differ on this behavior.
 2182               // This is closer to NS where we align to the first line of text.
 2183               // If the child is not text we draw the indicator at the
 2184               // origin (0).
 2185               float align = 0;
 2186               if (cv.getViewCount() > 0) {
 2187                   View pView = cv.getView(0);
 2188                   Object cName = pView.getElement().getAttributes().
 2189                                  getAttribute(StyleConstants.NameAttribute);
 2190                   if ((cName == HTML.Tag.P || cName == HTML.Tag.IMPLIED) &&
 2191                                 pView.getViewCount() > 0) {
 2192                       paintRect.setBounds((int)x, (int)y, (int)w, (int)h);
 2193                       Shape shape = cv.getChildAllocation(0, paintRect);
 2194                       if (shape != null && (shape = pView.getView(0).
 2195                                    getChildAllocation(0, shape)) != null) {
 2196                           Rectangle rect = (shape instanceof Rectangle) ?
 2197                                            (Rectangle)shape : shape.getBounds();
 2198   
 2199                           align = pView.getView(0).getAlignment(View.Y_AXIS);
 2200                           y = rect.y;
 2201                           h = rect.height;
 2202                       }
 2203                   }
 2204               }
 2205   
 2206               // set the color of a decoration
 2207               if (ss != null) {
 2208                   g.setColor(ss.getForeground(cv.getAttributes()));
 2209               } else {
 2210                   g.setColor(Color.black);
 2211               }
 2212   
 2213               if (img != null) {
 2214                   drawIcon(g, (int) x, (int) y, (int) w, (int) h, align,
 2215                            v.getContainer());
 2216                   return;
 2217               }
 2218               CSS.Value childtype = getChildType(cv);
 2219               Font font = ((StyledDocument)cv.getDocument()).
 2220                                            getFont(cv.getAttributes());
 2221               if (font != null) {
 2222                   g.setFont(font);
 2223               }
 2224               if (childtype == CSS.Value.SQUARE || childtype == CSS.Value.CIRCLE
 2225                   || childtype == CSS.Value.DISC) {
 2226                   drawShape(g, childtype, (int) x, (int) y,
 2227                             (int) w, (int) h, align);
 2228               } else if (childtype == CSS.Value.DECIMAL) {
 2229                   drawLetter(g, '1', (int) x, (int) y, (int) w, (int) h, align,
 2230                              getRenderIndex(v, item));
 2231               } else if (childtype == CSS.Value.LOWER_ALPHA) {
 2232                   drawLetter(g, 'a', (int) x, (int) y, (int) w, (int) h, align,
 2233                              getRenderIndex(v, item));
 2234               } else if (childtype == CSS.Value.UPPER_ALPHA) {
 2235                   drawLetter(g, 'A', (int) x, (int) y, (int) w, (int) h, align,
 2236                              getRenderIndex(v, item));
 2237               } else if (childtype == CSS.Value.LOWER_ROMAN) {
 2238                   drawLetter(g, 'i', (int) x, (int) y, (int) w, (int) h, align,
 2239                              getRenderIndex(v, item));
 2240               } else if (childtype == CSS.Value.UPPER_ROMAN) {
 2241                   drawLetter(g, 'I', (int) x, (int) y, (int) w, (int) h, align,
 2242                              getRenderIndex(v, item));
 2243               }
 2244           }
 2245   
 2246           /**
 2247            * Draws the bullet icon specified by the list-style-image argument.
 2248            *
 2249            * @param g     the graphics context
 2250            * @param ax    x coordinate to place the bullet
 2251            * @param ay    y coordinate to place the bullet
 2252            * @param aw    width of the container the bullet is placed in
 2253            * @param ah    height of the container the bullet is placed in
 2254            * @param align preferred alignment factor for the child view
 2255            */
 2256           void drawIcon(Graphics g, int ax, int ay, int aw, int ah,
 2257                         float align, Component c) {
 2258               // Align to bottom of icon.
 2259               int gap = isLeftToRight ? - (img.getIconWidth() + bulletgap) :
 2260                                           (aw + bulletgap);
 2261               int x = ax + gap;
 2262               int y = Math.max(ay, ay + (int)(align * ah) -img.getIconHeight());
 2263   
 2264               img.paintIcon(c, g, x, y);
 2265           }
 2266   
 2267           /**
 2268            * Draws the graphical bullet item specified by the type argument.
 2269            *
 2270            * @param g     the graphics context
 2271            * @param type  type of bullet to draw (circle, square, disc)
 2272            * @param ax    x coordinate to place the bullet
 2273            * @param ay    y coordinate to place the bullet
 2274            * @param aw    width of the container the bullet is placed in
 2275            * @param ah    height of the container the bullet is placed in
 2276            * @param align preferred alignment factor for the child view
 2277            */
 2278           void drawShape(Graphics g, CSS.Value type, int ax, int ay, int aw,
 2279                          int ah, float align) {
 2280               // Align to bottom of shape.
 2281               int gap = isLeftToRight ? - (bulletgap + 8) : (aw + bulletgap);
 2282               int x = ax + gap;
 2283               int y = Math.max(ay, ay + (int)(align * ah) - 8);
 2284   
 2285               if (type == CSS.Value.SQUARE) {
 2286                   g.drawRect(x, y, 8, 8);
 2287               } else if (type == CSS.Value.CIRCLE) {
 2288                   g.drawOval(x, y, 8, 8);
 2289               } else {
 2290                   g.fillOval(x, y, 8, 8);
 2291               }
 2292           }
 2293   
 2294           /**
 2295            * Draws the letter or number for an ordered list.
 2296            *
 2297            * @param g     the graphics context
 2298            * @param letter type of ordered list to draw
 2299            * @param ax    x coordinate to place the bullet
 2300            * @param ay    y coordinate to place the bullet
 2301            * @param aw    width of the container the bullet is placed in
 2302            * @param ah    height of the container the bullet is placed in
 2303            * @param index position of the list item in the list
 2304            */
 2305           void drawLetter(Graphics g, char letter, int ax, int ay, int aw,
 2306                           int ah, float align, int index) {
 2307               String str = formatItemNum(index, letter);
 2308               str = isLeftToRight ? str + "." : "." + str;
 2309               FontMetrics fm = SwingUtilities2.getFontMetrics(null, g);
 2310               int stringwidth = SwingUtilities2.stringWidth(null, fm, str);
 2311               int gap = isLeftToRight ? - (stringwidth + bulletgap) :
 2312                                           (aw + bulletgap);
 2313               int x = ax + gap;
 2314               int y = Math.max(ay + fm.getAscent(), ay + (int)(ah * align));
 2315               SwingUtilities2.drawString(null, g, str, x, y);
 2316           }
 2317   
 2318           /**
 2319            * Converts the item number into the ordered list number
 2320            * (i.e.  1 2 3, i ii iii, a b c, etc.
 2321            *
 2322            * @param itemNum number to format
 2323            * @param type    type of ordered list
 2324            */
 2325           String formatItemNum(int itemNum, char type) {
 2326               String numStyle = "1";
 2327   
 2328               boolean uppercase = false;
 2329   
 2330               String formattedNum;
 2331   
 2332               switch (type) {
 2333               case '1':
 2334               default:
 2335                   formattedNum = String.valueOf(itemNum);
 2336                   break;
 2337   
 2338               case 'A':
 2339                   uppercase = true;
 2340                   // fall through
 2341               case 'a':
 2342                   formattedNum = formatAlphaNumerals(itemNum);
 2343                   break;
 2344   
 2345               case 'I':
 2346                   uppercase = true;
 2347                   // fall through
 2348               case 'i':
 2349                   formattedNum = formatRomanNumerals(itemNum);
 2350               }
 2351   
 2352