001/* StyleSheet.java -- 
002   Copyright (C) 2005 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.text.html;
040
041import gnu.javax.swing.text.html.css.BorderWidth;
042import gnu.javax.swing.text.html.css.CSSColor;
043import gnu.javax.swing.text.html.css.CSSParser;
044import gnu.javax.swing.text.html.css.CSSParserCallback;
045import gnu.javax.swing.text.html.css.FontSize;
046import gnu.javax.swing.text.html.css.FontStyle;
047import gnu.javax.swing.text.html.css.FontWeight;
048import gnu.javax.swing.text.html.css.Length;
049import gnu.javax.swing.text.html.css.Selector;
050
051import java.awt.Color;
052import java.awt.Font;
053import java.awt.Graphics;
054import java.awt.Rectangle;
055import java.awt.Shape;
056import java.awt.font.FontRenderContext;
057import java.awt.geom.Rectangle2D;
058import java.io.BufferedReader;
059import java.io.IOException;
060import java.io.InputStream;
061import java.io.InputStreamReader;
062import java.io.Reader;
063import java.io.Serializable;
064import java.io.StringReader;
065import java.net.URL;
066import java.util.ArrayList;
067import java.util.Collections;
068import java.util.Enumeration;
069import java.util.HashMap;
070import java.util.Iterator;
071import java.util.List;
072import java.util.Map;
073
074import javax.swing.border.Border;
075import javax.swing.event.ChangeListener;
076import javax.swing.text.AttributeSet;
077import javax.swing.text.Element;
078import javax.swing.text.MutableAttributeSet;
079import javax.swing.text.SimpleAttributeSet;
080import javax.swing.text.Style;
081import javax.swing.text.StyleConstants;
082import javax.swing.text.StyleContext;
083import javax.swing.text.View;
084
085
086/**
087 * This class adds support for defining the visual characteristics of HTML views
088 * being rendered. This enables views to be customized by a look-and-feel, mulitple
089 * views over the same model can be rendered differently. Each EditorPane has its 
090 * own StyleSheet, but by default one sheet will be shared by all of the HTMLEditorKit
091 * instances. An HTMLDocument can also have a StyleSheet, which holds specific CSS
092 * specs. 
093 * 
094 *  In order for Views to store less state and therefore be more lightweight, 
095 *  the StyleSheet can act as a factory for painters that handle some of the 
096 *  rendering tasks. Since the StyleSheet may be used by views over multiple
097 *  documents the HTML attributes don't effect the selector being used.
098 *  
099 *  The rules are stored as named styles, and other information is stored to 
100 *  translate the context of an element to a rule.
101 *
102 * @author Lillian Angel (langel@redhat.com)
103 */
104public class StyleSheet extends StyleContext
105{
106
107  /**
108   * Parses CSS stylesheets using the parser in gnu/javax/swing/html/css.
109   *
110   * This is package private to avoid accessor methods.
111   */
112  class CSSStyleSheetParserCallback
113    implements CSSParserCallback
114  {
115    /**
116     * The current styles.
117     */
118    private CSSStyle[] styles;
119
120    /**
121     * The precedence of the stylesheet to be parsed.
122     */
123    private int precedence;
124
125    /**
126     * Creates a new CSS parser. This parser parses a CSS stylesheet with
127     * the specified precedence.
128     *
129     * @param prec the precedence, according to the constants defined in
130     *        CSSStyle
131     */
132    CSSStyleSheetParserCallback(int prec)
133    {
134      precedence = prec;
135    }
136
137    /**
138     * Called at the beginning of a statement.
139     *
140     * @param sel the selector
141     */
142    public void startStatement(Selector[] sel)
143    {
144      styles = new CSSStyle[sel.length];
145      for (int i = 0; i < sel.length; i++)
146        styles[i] = new CSSStyle(precedence, sel[i]);
147    }
148
149    /**
150     * Called at the end of a statement.
151     */
152    public void endStatement()
153    {
154      for (int i = 0; i < styles.length; i++)
155        css.add(styles[i]);
156      styles = null;
157    }
158
159    /**
160     * Called when a declaration is parsed.
161     *
162     * @param property the property
163     * @param value the value
164     */
165    public void declaration(String property, String value)
166    {
167      CSS.Attribute cssAtt = CSS.getAttribute(property);
168      Object val = CSS.getValue(cssAtt, value);
169      for (int i = 0; i < styles.length; i++)
170        {
171          CSSStyle style = styles[i];
172          CSS.addInternal(style, cssAtt, value);
173          if (cssAtt != null)
174            style.addAttribute(cssAtt, val);
175        }
176    }
177
178  }
179
180  /**
181   * Represents a style that is defined by a CSS rule.
182   */
183  private class CSSStyle
184    extends SimpleAttributeSet
185    implements Style, Comparable<CSSStyle>
186  {
187
188    static final int PREC_UA = 0;
189    static final int PREC_NORM = 100000;
190    static final int PREC_AUTHOR_NORMAL = 200000;
191    static final int PREC_AUTHOR_IMPORTANT = 300000;
192    static final int PREC_USER_IMPORTANT = 400000;
193
194    /**
195     * The priority of this style when matching CSS selectors.
196     */
197    private int precedence;
198
199    /**
200     * The selector for this rule.
201     *
202     * This is package private to avoid accessor methods.
203     */
204    Selector selector;
205
206    CSSStyle(int prec, Selector sel)
207    {
208      precedence = prec;
209      selector = sel;
210    }
211
212    public String getName()
213    {
214      // TODO: Implement this for correctness.
215      return null;
216    }
217
218    public void addChangeListener(ChangeListener listener)
219    {
220      // TODO: Implement this for correctness.
221    }
222
223    public void removeChangeListener(ChangeListener listener)
224    {
225      // TODO: Implement this for correctness.
226    }
227
228    /**
229     * Sorts the rule according to the style's precedence and the
230     * selectors specificity.
231     */
232    public int compareTo(CSSStyle other)
233    {
234      return other.precedence + other.selector.getSpecificity()
235             - precedence - selector.getSpecificity();
236    }
237    
238  }
239
240  /** The base URL */
241  URL base;
242  
243  /** Base font size (int) */
244  int baseFontSize;
245  
246  /**
247   * The linked style sheets stored.
248   */
249  private ArrayList<StyleSheet> linked;
250
251  /**
252   * Maps element names (selectors) to AttributSet (the corresponding style
253   * information).
254   */
255  ArrayList<CSSStyle> css = new ArrayList<CSSStyle>();
256
257  /**
258   * Maps selectors to their resolved styles.
259   */
260  private HashMap<String,Style> resolvedStyles;
261
262  /**
263   * Constructs a StyleSheet.
264   */
265  public StyleSheet()
266  {
267    super();
268    baseFontSize = 4; // Default font size from CSS
269    resolvedStyles = new HashMap<String,Style>();
270  }
271
272  /**
273   * Gets the style used to render the given tag. The element represents the tag
274   * and can be used to determine the nesting, where the attributes will differ
275   * if there is nesting inside of elements.
276   * 
277   * @param t - the tag to translate to visual attributes
278   * @param e - the element representing the tag
279   * @return the set of CSS attributes to use to render the tag.
280   */
281  public Style getRule(HTML.Tag t, Element e)
282  {
283    // Create list of the element and all of its parents, starting
284    // with the bottommost element.
285    ArrayList<Element> path = new ArrayList<Element>();
286    Element el;
287    AttributeSet atts;
288    for (el = e; el != null; el = el.getParentElement())
289      path.add(el);
290
291    // Create fully qualified selector.
292    StringBuilder selector = new StringBuilder();
293    int count = path.size();
294    // We append the actual element after this loop.
295    for (int i = count - 1; i > 0; i--)
296      {
297        el = path.get(i);
298        atts = el.getAttributes();
299        Object name = atts.getAttribute(StyleConstants.NameAttribute);
300        selector.append(name.toString());
301        if (atts.isDefined(HTML.Attribute.ID))
302          {
303            selector.append('#');
304            selector.append(atts.getAttribute(HTML.Attribute.ID));
305          }
306        if (atts.isDefined(HTML.Attribute.CLASS))
307          {
308            selector.append('.');
309            selector.append(atts.getAttribute(HTML.Attribute.CLASS));
310          }
311        if (atts.isDefined(HTML.Attribute.DYNAMIC_CLASS))
312          {
313            selector.append(':');
314            selector.append(atts.getAttribute(HTML.Attribute.DYNAMIC_CLASS));
315          }
316        if (atts.isDefined(HTML.Attribute.PSEUDO_CLASS))
317          {
318            selector.append(':');
319            selector.append(atts.getAttribute(HTML.Attribute.PSEUDO_CLASS));
320          }
321        selector.append(' ');
322      }
323    selector.append(t.toString());
324    el = path.get(0);
325    atts = el.getAttributes();
326    // For leaf elements, we have to fetch the tag specific attributes.
327    if (el.isLeaf())
328      {
329        Object o = atts.getAttribute(t);
330        if (o instanceof AttributeSet)
331          atts = (AttributeSet) o;
332        else
333          atts = null;
334      }
335    if (atts != null)
336      {
337        if (atts.isDefined(HTML.Attribute.ID))
338          {
339            selector.append('#');
340            selector.append(atts.getAttribute(HTML.Attribute.ID));
341          }
342        if (atts.isDefined(HTML.Attribute.CLASS))
343          {
344            selector.append('.');
345            selector.append(atts.getAttribute(HTML.Attribute.CLASS));
346          }
347        if (atts.isDefined(HTML.Attribute.DYNAMIC_CLASS))
348          {
349            selector.append(':');
350            selector.append(atts.getAttribute(HTML.Attribute.DYNAMIC_CLASS));
351          }
352        if (atts.isDefined(HTML.Attribute.PSEUDO_CLASS))
353          {
354            selector.append(':');
355            selector.append(atts.getAttribute(HTML.Attribute.PSEUDO_CLASS));
356          }
357      }
358    return getResolvedStyle(selector.toString(), path, t);
359  }
360
361  /**
362   * Fetches a resolved style. If there is no resolved style for the
363   * specified selector, the resolve the style using
364   * {@link #resolveStyle(String, List, HTML.Tag)}.
365   * 
366   * @param selector the selector for which to resolve the style
367   * @param path the Element path, used in the resolving algorithm
368   * @param tag the tag for which to resolve
369   *
370   * @return the resolved style
371   */
372  private Style getResolvedStyle(String selector, List path, HTML.Tag tag)
373  {
374    Style style = resolvedStyles.get(selector);
375    if (style == null)
376      style = resolveStyle(selector, path, tag);
377    return style;
378  }
379
380  /**
381   * Resolves a style. This creates arrays that hold the tag names,
382   * class and id attributes and delegates the work to
383   * {@link #resolveStyle(String, String[], Map[])}.
384   *
385   * @param selector the selector
386   * @param path the Element path
387   * @param tag the tag
388   *
389   * @return the resolved style
390   */
391  private Style resolveStyle(String selector, List path, HTML.Tag tag)
392  {
393    int count = path.size();
394    String[] tags = new String[count];
395    Map[] attributes = new Map[count];
396    for (int i = 0; i < count; i++)
397      {
398        Element el = (Element) path.get(i);
399        AttributeSet atts = el.getAttributes();
400        if (i == 0 && el.isLeaf())
401          {
402            Object o = atts.getAttribute(tag);
403            if (o instanceof AttributeSet)
404              atts = (AttributeSet) o;
405            else
406              atts = null;
407          }
408        if (atts != null)
409          {
410            HTML.Tag t =
411              (HTML.Tag) atts.getAttribute(StyleConstants.NameAttribute);
412            if (t != null)
413              tags[i] = t.toString();
414            else
415              tags[i] = null;
416            attributes[i] = attributeSetToMap(atts);
417          }
418        else
419          {
420            tags[i] = null;
421            attributes[i] = null;
422          }
423      }
424    tags[0] = tag.toString();
425    return resolveStyle(selector, tags, attributes);
426  }
427
428  /**
429   * Performs style resolving.
430   *
431   * @param selector the selector
432   * @param tags the tags
433   * @param attributes the attributes of the tags
434   *
435   * @return the resolved style
436   */
437  private Style resolveStyle(String selector, String[] tags, Map[] attributes)
438  {
439    // FIXME: This style resolver is not correct. But it works good enough for
440    // the default.css.
441    ArrayList<CSSStyle> styles = new ArrayList<CSSStyle>();
442    for (CSSStyle style : css)
443      {
444        if (style.selector.matches(tags, attributes))
445          styles.add(style);
446      }
447
448    // Add styles from linked stylesheets.
449    if (linked != null)
450      {
451        for (int i = linked.size() - 1; i >= 0; i--)
452          {
453            StyleSheet ss = linked.get(i);
454            for (int j = ss.css.size() - 1; j >= 0; j--)
455              {
456                CSSStyle style = ss.css.get(j);
457                if (style.selector.matches(tags, attributes))
458                  styles.add(style);
459              }
460          }
461      }
462
463    // Sort selectors.
464    Collections.sort(styles);
465    Style[] styleArray = new Style[styles.size()];
466    styleArray = (Style[]) styles.toArray(styleArray);
467    Style resolved = new MultiStyle(selector,
468                                    (Style[]) styles.toArray(styleArray));
469    resolvedStyles.put(selector, resolved);
470    return resolved;
471  }
472
473  /**
474   * Gets the rule that best matches the selector. selector is a space
475   * separated String of element names. The attributes of the returned 
476   * Style will change as rules are added and removed.
477   * 
478   * @param selector - the element names separated by spaces
479   * @return the set of CSS attributes to use to render
480   */
481  public Style getRule(String selector)
482  {
483    CSSStyle best = null;
484    for (Iterator i = css.iterator(); i.hasNext();)
485      {
486        CSSStyle style = (CSSStyle) i.next();
487        if (style.compareTo(best) < 0)
488          best = style;
489      }
490    return best;
491  }
492  
493  /**
494   * Adds a set of rules to the sheet. The rules are expected to be in valid
495   * CSS format. This is called as a result of parsing a <style> tag
496   * 
497   * @param rule - the rule to add to the sheet
498   */
499  public void addRule(String rule)
500  {
501    CSSStyleSheetParserCallback cb =
502      new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL);
503    // FIXME: Handle ref.
504    StringReader in = new StringReader(rule);
505    CSSParser parser = new CSSParser(in, cb);
506    try
507      {
508        parser.parse();
509      }
510    catch (IOException ex)
511      {
512        // Shouldn't happen. And if, then don't let it bork the outside code.
513      }
514    // Clean up resolved styles cache so that the new styles are recognized
515    // on next stylesheet request.
516    resolvedStyles.clear();
517  }
518  
519  /**
520   * Translates a CSS declaration into an AttributeSet. This is called
521   * as a result of encountering an HTML style attribute.
522   * 
523   * @param decl - the declaration to get
524   * @return the AttributeSet representing the declaration
525   */
526  public AttributeSet getDeclaration(String decl)
527  {
528    if (decl == null)
529      return SimpleAttributeSet.EMPTY;
530    // FIXME: Not implemented.
531    return null;     
532  }
533  
534  /**
535   * Loads a set of rules that have been specified in terms of CSS grammar.
536   * If there are any conflicts with existing rules, the new rule is added.
537   * 
538   * @param in - the stream to read the CSS grammar from.
539   * @param ref - the reference URL. It is the location of the stream, it may
540   * be null. All relative URLs specified in the stream will be based upon this
541   * parameter.
542   * @throws IOException - For any IO error while reading
543   */
544  public void loadRules(Reader in, URL ref)
545    throws IOException
546  {
547    CSSStyleSheetParserCallback cb =
548      new CSSStyleSheetParserCallback(CSSStyle.PREC_UA);
549    // FIXME: Handle ref.
550    CSSParser parser = new CSSParser(in, cb);
551    parser.parse();
552  }
553  
554  /**
555   * Gets a set of attributes to use in the view. This is a set of
556   * attributes that can be used for View.getAttributes
557   * 
558   * @param v - the view to get the set for
559   * @return the AttributeSet to use in the view.
560   */
561  public AttributeSet getViewAttributes(View v)
562  {
563    return new ViewAttributeSet(v, this);
564  }
565  
566  /**
567   * Removes a style previously added.
568   * 
569   * @param nm - the name of the style to remove
570   */
571  public void removeStyle(String nm)
572  {
573    // FIXME: Not implemented.
574    super.removeStyle(nm);
575  }
576  
577  /**
578   * Adds the rules from ss to those of the receiver. ss's rules will
579   * override the old rules. An added StyleSheet will never override the rules
580   * of the receiving style sheet.
581   * 
582   * @param ss - the new StyleSheet.
583   */
584  public void addStyleSheet(StyleSheet ss)
585  {
586    if (linked == null)
587      linked = new ArrayList();
588    linked.add(ss);
589  }
590  
591  /**
592   * Removes ss from those of the receiver
593   * 
594   * @param ss - the StyleSheet to remove.
595   */
596  public void removeStyleSheet(StyleSheet ss)
597  {
598    if (linked != null)
599      {
600        linked.remove(ss);
601      }
602  }
603  
604  /**
605   * Returns an array of the linked StyleSheets. May return null.
606   * 
607   * @return - An array of the linked StyleSheets.
608   */
609  public StyleSheet[] getStyleSheets()
610  {
611    StyleSheet[] linkedSS;
612    if (linked != null)
613      {
614        linkedSS = new StyleSheet[linked.size()];
615        linkedSS = linked.toArray(linkedSS);
616      }
617    else
618      {
619        linkedSS = null;
620      }
621    return linkedSS;
622  }
623  
624  /**
625   * Imports a style sheet from the url. The rules are directly added to the
626   * receiver. This is usually called when a <link> tag is resolved in an
627   * HTML document.
628   * 
629   * @param url the URL to import the StyleSheet from
630   */
631  public void importStyleSheet(URL url)
632  {
633    try
634      {
635        InputStream in = url.openStream();
636        Reader r = new BufferedReader(new InputStreamReader(in));
637        CSSStyleSheetParserCallback cb =
638          new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL);
639        CSSParser parser = new CSSParser(r, cb);
640        parser.parse();
641      }
642    catch (IOException ex)
643      {
644        // We can't do anything about it I guess.
645      }
646  }
647  
648  /**
649   * Sets the base url. All import statements that are relative, will be
650   * relative to base.
651   * 
652   * @param base -
653   *          the base URL.
654   */
655  public void setBase(URL base)
656  {
657    this.base = base;
658  }
659  
660  /**
661   * Gets the base url.
662   * 
663   * @return - the base
664   */
665  public URL getBase()
666  {
667    return base;
668  }
669  
670  /**
671   * Adds a CSS attribute to the given set.
672   * 
673   * @param attr - the attribute set
674   * @param key - the attribute to add
675   * @param value - the value of the key
676   */
677  public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key,
678                              String value)
679  {
680    Object val = CSS.getValue(key, value);
681    CSS.addInternal(attr, key, value);
682    attr.addAttribute(key, val);
683  }
684  
685  /**
686   * Adds a CSS attribute to the given set.
687   * This method parses the value argument from HTML based on key. 
688   * Returns true if it finds a valid value for the given key, 
689   * and false otherwise.
690   * 
691   * @param attr - the attribute set
692   * @param key - the attribute to add
693   * @param value - the value of the key
694   * @return true if a valid value was found.
695   */
696  public boolean addCSSAttributeFromHTML(MutableAttributeSet attr, CSS.Attribute key,
697                                         String value)
698  {
699    // FIXME: Need to parse value from HTML based on key.
700    attr.addAttribute(key, value);
701    return attr.containsAttribute(key, value);
702  }
703  
704  /**
705   * Converts a set of HTML attributes to an equivalent set of CSS attributes.
706   * 
707   * @param htmlAttrSet - the set containing the HTML attributes.
708   * @return the set of CSS attributes
709   */
710  public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet)
711  {
712    AttributeSet cssAttr = htmlAttrSet.copyAttributes();
713
714    // The HTML align attribute maps directly to the CSS text-align attribute.
715    Object o = htmlAttrSet.getAttribute(HTML.Attribute.ALIGN);
716    if (o != null)
717      cssAttr = addAttribute(cssAttr, CSS.Attribute.TEXT_ALIGN, o);
718
719    // The HTML width attribute maps directly to CSS width.
720    o = htmlAttrSet.getAttribute(HTML.Attribute.WIDTH);
721    if (o != null)
722      cssAttr = addAttribute(cssAttr, CSS.Attribute.WIDTH,
723                             new Length(o.toString()));
724
725    // The HTML height attribute maps directly to CSS height.
726    o = htmlAttrSet.getAttribute(HTML.Attribute.HEIGHT);
727    if (o != null)
728      cssAttr = addAttribute(cssAttr, CSS.Attribute.HEIGHT,
729                             new Length(o.toString()));
730
731    o = htmlAttrSet.getAttribute(HTML.Attribute.NOWRAP);
732    if (o != null)
733      cssAttr = addAttribute(cssAttr, CSS.Attribute.WHITE_SPACE, "nowrap");
734
735    // Map cellspacing attr of tables to CSS border-spacing.
736    o = htmlAttrSet.getAttribute(HTML.Attribute.CELLSPACING);
737    if (o != null)
738      cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_SPACING,
739                             new Length(o.toString()));
740
741    // For table cells and headers, fetch the cellpadding value from the
742    // parent table and set it as CSS padding attribute.
743    HTML.Tag tag = (HTML.Tag)
744                   htmlAttrSet.getAttribute(StyleConstants.NameAttribute);
745    if ((tag == HTML.Tag.TD || tag == HTML.Tag.TH)
746        && htmlAttrSet instanceof Element)
747      {
748        Element el = (Element) htmlAttrSet;
749        AttributeSet tableAttrs = el.getParentElement().getParentElement()
750                                  .getAttributes();
751        o = tableAttrs.getAttribute(HTML.Attribute.CELLPADDING);
752        if (o != null)
753          {
754            Length l = new Length(o.toString());
755            cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_BOTTOM, l);
756            cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_LEFT, l);
757            cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_RIGHT, l);
758            cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_TOP, l);
759          }
760        o = tableAttrs.getAttribute(HTML.Attribute.BORDER);
761        cssAttr = translateBorder(cssAttr, o);
762      }
763
764    // Translate border attribute.
765    o = cssAttr.getAttribute(HTML.Attribute.BORDER);
766    cssAttr = translateBorder(cssAttr, o);
767
768    // TODO: Add more mappings.
769    return cssAttr;
770  }
771
772  /**
773   * Translates a HTML border attribute to a corresponding set of CSS
774   * attributes.
775   *
776   * @param cssAttr the original set of CSS attributes to add to 
777   * @param o the value of the border attribute
778   *
779   * @return the new set of CSS attributes
780   */
781  private AttributeSet translateBorder(AttributeSet cssAttr, Object o)
782  {
783    if (o != null)
784      {
785        BorderWidth l = new BorderWidth(o.toString());
786        if (l.getValue() > 0)
787          {
788            cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_WIDTH, l);
789            cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_STYLE,
790                                   "solid");
791            cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_COLOR,
792                                   new CSSColor("black"));
793          }
794      }
795    return cssAttr;
796  }
797
798  /**
799   * Adds an attribute to the given set and returns a new set. This is implemented
800   * to convert StyleConstants attributes to CSS before forwarding them to the superclass.
801   * The StyleConstants attribute do not have corresponding CSS entry, the attribute
802   * is stored (but will likely not be used).
803   * 
804   * @param old - the old set
805   * @param key - the non-null attribute key
806   * @param value - the attribute value
807   * @return the updated set 
808   */
809  public AttributeSet addAttribute(AttributeSet old, Object key,
810                                   Object value)
811  {
812    // FIXME: Not implemented.
813    return super.addAttribute(old, key, value);       
814  }
815  
816  /**
817   * Adds a set of attributes to the element. If any of these attributes are
818   * StyleConstants, they will be converted to CSS before forwarding to the 
819   * superclass.
820   * 
821   * @param old - the old set
822   * @param attr - the attributes to add
823   * @return the updated attribute set
824   */
825  public AttributeSet addAttributes(AttributeSet old, AttributeSet attr)
826  {
827    // FIXME: Not implemented.
828    return super.addAttributes(old, attr);           
829  }
830  
831  /**
832   * Removes an attribute from the set. If the attribute is a
833   * StyleConstants, it will be converted to CSS before forwarding to the 
834   * superclass.
835   * 
836   * @param old - the old set
837   * @param key - the non-null attribute key
838   * @return the updated set 
839   */
840  public AttributeSet removeAttribute(AttributeSet old, Object key)
841  {
842    // FIXME: Not implemented.
843    return super.removeAttribute(old, key);    
844  }
845  
846  /**
847   * Removes an attribute from the set. If any of the attributes are
848   * StyleConstants, they will be converted to CSS before forwarding to the 
849   * superclass.
850   * 
851   * @param old - the old set
852   * @param attrs - the attributes to remove
853   * @return the updated set 
854   */
855  public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs)
856  {
857    // FIXME: Not implemented.
858    return super.removeAttributes(old, attrs);    
859  }
860  
861  /**
862   * Removes a set of attributes for the element. If any of the attributes is a
863   * StyleConstants, they will be converted to CSS before forwarding to the 
864   * superclass.
865   * 
866   * @param old - the old attribute set
867   * @param names - the attribute names
868   * @return the update attribute set
869   */
870  public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names)
871  {
872    // FIXME: Not implemented.
873    return super.removeAttributes(old, names);
874  }
875  
876  /**
877   * Creates a compact set of attributes that might be shared. This is a hook
878   * for subclasses that want to change the behaviour of SmallAttributeSet.
879   * 
880   * @param a - the set of attributes to be represented in the compact form.
881   * @return the set of attributes created
882   */
883  protected StyleContext.SmallAttributeSet createSmallAttributeSet(AttributeSet a)
884  {
885    return super.createSmallAttributeSet(a);     
886  }
887  
888  /**
889   * Creates a large set of attributes. This set is not shared. This is a hook
890   * for subclasses that want to change the behaviour of the larger attribute
891   * storage format.
892   * 
893   * @param a - the set of attributes to be represented in the larger form.
894   * @return the large set of attributes.
895   */
896  protected MutableAttributeSet createLargeAttributeSet(AttributeSet a)
897  {
898    return super.createLargeAttributeSet(a);     
899  }
900  
901  /**
902   * Gets the font to use for the given set.
903   * 
904   * @param a - the set to get the font for.
905   * @return the font for the set
906   */
907  public Font getFont(AttributeSet a)
908  {
909    int realSize = getFontSize(a);
910
911    // Decrement size for subscript and superscript.
912    Object valign = a.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
913    if (valign != null)
914      {
915        String v = valign.toString();
916        if (v.contains("sup") || v.contains("sub"))
917          realSize -= 2;
918      }
919
920    // TODO: Convert font family.
921    String family = "SansSerif";
922
923    int style = Font.PLAIN;
924    FontWeight weight = (FontWeight) a.getAttribute(CSS.Attribute.FONT_WEIGHT);
925    if (weight != null)
926      style |= weight.getValue();
927    FontStyle fStyle = (FontStyle) a.getAttribute(CSS.Attribute.FONT_STYLE);
928    if (fStyle != null)
929      style |= fStyle.getValue();
930    return new Font(family, style, realSize);
931  }
932
933  /**
934   * Determines the EM base value based on the specified attributes.
935   *
936   * @param atts the attibutes
937   *
938   * @return the EM base value
939   */
940  float getEMBase(AttributeSet atts)
941  {
942    Font font = getFont(atts);
943    FontRenderContext ctx = new FontRenderContext(null, false, false);
944    Rectangle2D bounds = font.getStringBounds("M", ctx);
945    return (float) bounds.getWidth();
946  }
947
948  /**
949   * Determines the EX base value based on the specified attributes.
950   *
951   * @param atts the attibutes
952   *
953   * @return the EX base value
954   */
955  float getEXBase(AttributeSet atts)
956  {
957    Font font = getFont(atts);
958    FontRenderContext ctx = new FontRenderContext(null, false, false);
959    Rectangle2D bounds = font.getStringBounds("x", ctx);
960    return (float) bounds.getHeight();
961  }
962
963  /**
964   * Resolves the fontsize for a given set of attributes.
965   *
966   * @param atts the attributes
967   *
968   * @return the resolved font size
969   */
970  private int getFontSize(AttributeSet atts)
971  {
972    int size = 12;
973    if (atts.isDefined(CSS.Attribute.FONT_SIZE))
974      {
975        FontSize fs = (FontSize) atts.getAttribute(CSS.Attribute.FONT_SIZE);
976        if (fs.isRelative())
977          {
978            int parSize = 12;
979            AttributeSet resolver = atts.getResolveParent();
980            if (resolver != null)
981              parSize = getFontSize(resolver);
982            size = fs.getValue(parSize); 
983          }
984        else
985          {
986            size = fs.getValue();
987          }
988      }
989    else
990      {
991        AttributeSet resolver = atts.getResolveParent();
992        if (resolver != null)
993          size = getFontSize(resolver);
994      }
995    return size;
996  }
997
998  /**
999   * Takes a set of attributes and turns it into a foreground
1000   * color specification. This is used to specify things like, brigher, more hue
1001   * etc.
1002   * 
1003   * @param a - the set to get the foreground color for
1004   * @return the foreground color for the set
1005   */
1006  public Color getForeground(AttributeSet a)
1007  {
1008    CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.COLOR);
1009    Color color = null;
1010    if (c != null)
1011      color = c.getValue();
1012    return color;     
1013  }
1014  
1015  /**
1016   * Takes a set of attributes and turns it into a background
1017   * color specification. This is used to specify things like, brigher, more hue
1018   * etc.
1019   * 
1020   * @param a - the set to get the background color for
1021   * @return the background color for the set
1022   */
1023  public Color getBackground(AttributeSet a)
1024  {
1025    CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.BACKGROUND_COLOR);
1026    Color color = null;
1027    if (c != null)
1028      color = c.getValue();
1029    return color;     
1030  }
1031  
1032  /**
1033   * Gets the box formatter to use for the given set of CSS attributes.
1034   * 
1035   * @param a - the given set
1036   * @return the box formatter
1037   */
1038  public BoxPainter getBoxPainter(AttributeSet a)
1039  {
1040    return new BoxPainter(a, this);     
1041  }
1042  
1043  /**
1044   * Gets the list formatter to use for the given set of CSS attributes.
1045   * 
1046   * @param a - the given set
1047   * @return the list formatter
1048   */
1049  public ListPainter getListPainter(AttributeSet a)
1050  {
1051    return new ListPainter(a, this);         
1052  }
1053  
1054  /**
1055   * Sets the base font size between 1 and 7.
1056   * 
1057   * @param sz - the new font size for the base.
1058   */
1059  public void setBaseFontSize(int sz)
1060  {
1061    if (sz <= 7 && sz >= 1)
1062      baseFontSize = sz;
1063  }
1064  
1065  /**
1066   * Sets the base font size from the String. It can either identify
1067   * a specific font size (between 1 and 7) or identify a relative
1068   * font size such as +1 or -2.
1069   * 
1070   * @param size - the new font size as a String.
1071   */
1072  public void setBaseFontSize(String size)
1073  {
1074    size = size.trim();
1075    int temp = 0;
1076    try
1077      {
1078        if (size.length() == 2)
1079          {
1080            int i = new Integer(size.substring(1)).intValue();
1081            if (size.startsWith("+"))
1082              temp = baseFontSize + i;
1083            else if (size.startsWith("-"))
1084              temp = baseFontSize - i;
1085          }
1086        else if (size.length() == 1)
1087          temp = new Integer(size.substring(0)).intValue();
1088
1089        if (temp <= 7 && temp >= 1)
1090          baseFontSize = temp;
1091      }
1092    catch (NumberFormatException nfe)
1093      {
1094        // Do nothing here
1095      }
1096  }
1097  
1098  /**
1099   * TODO
1100   * 
1101   * @param pt - TODO
1102   * @return TODO
1103   */
1104  public static int getIndexOfSize(float pt)
1105  {
1106    // FIXME: Not implemented.
1107    return 0;
1108  }
1109  
1110  /**
1111   * Gets the point size, given a size index.
1112   * 
1113   * @param index - the size index
1114   * @return the point size.
1115   */
1116  public float getPointSize(int index)
1117  {
1118    // FIXME: Not implemented.
1119    return 0;    
1120  }
1121  
1122  /**
1123   * Given the string of the size, returns the point size value.
1124   * 
1125   * @param size - the string representation of the size.
1126   * @return - the point size value.
1127   */
1128  public float getPointSize(String size)
1129  {
1130    // FIXME: Not implemented.
1131    return 0;    
1132  }
1133  
1134  /**
1135   * Convert the color string represenation into java.awt.Color. The valid
1136   * values are like "aqua" , "#00FFFF" or "rgb(1,6,44)".
1137   * 
1138   * @param colorName the color to convert.
1139   * @return the matching java.awt.color
1140   */
1141  public Color stringToColor(String colorName)
1142  {
1143    return CSSColor.convertValue(colorName);
1144  }
1145  
1146  /**
1147   * This class carries out some of the duties of CSS formatting. This enables views
1148   * to present the CSS formatting while not knowing how the CSS values are cached.
1149   * 
1150   * This object is reponsible for the insets of a View and making sure
1151   * the background is maintained according to the CSS attributes.
1152   * 
1153   * @author Lillian Angel (langel@redhat.com)
1154   */
1155  public static class BoxPainter extends Object implements Serializable
1156  {
1157
1158    /**
1159     * The left inset.
1160     */
1161    private float leftInset;
1162
1163    /**
1164     * The right inset.
1165     */
1166    private float rightInset;
1167
1168    /**
1169     * The top inset.
1170     */
1171    private float topInset;
1172
1173    /**
1174     * The bottom inset.
1175     */
1176    private float bottomInset;
1177
1178    /**
1179     * The border of the box.
1180     */
1181    private Border border;
1182
1183    private float leftPadding;
1184    private float rightPadding;
1185    private float topPadding;
1186    private float bottomPadding;
1187
1188    /**
1189     * The background color.
1190     */
1191    private Color background;
1192
1193    /**
1194     * Package-private constructor.
1195     * 
1196     * @param as - AttributeSet for painter
1197     */
1198    BoxPainter(AttributeSet as, StyleSheet ss)
1199    {
1200      float emBase = ss.getEMBase(as);
1201      float exBase = ss.getEXBase(as);
1202      // Fetch margins.
1203      Length l = (Length) as.getAttribute(CSS.Attribute.MARGIN_LEFT);
1204      if (l != null)
1205        {
1206          l.setFontBases(emBase, exBase);
1207          leftInset = l.getValue();
1208        }
1209      l = (Length) as.getAttribute(CSS.Attribute.MARGIN_RIGHT);
1210      if (l != null)
1211        {
1212          l.setFontBases(emBase, exBase);
1213          rightInset = l.getValue();
1214        }
1215      l = (Length) as.getAttribute(CSS.Attribute.MARGIN_TOP);
1216      if (l != null)
1217        {
1218          l.setFontBases(emBase, exBase);
1219          topInset = l.getValue();
1220        }
1221      l = (Length) as.getAttribute(CSS.Attribute.MARGIN_BOTTOM);
1222      if (l != null)
1223        {
1224          l.setFontBases(emBase, exBase);
1225          bottomInset = l.getValue();
1226        }
1227
1228      // Fetch padding.
1229      l = (Length) as.getAttribute(CSS.Attribute.PADDING_LEFT);
1230      if (l != null)
1231        {
1232          l.setFontBases(emBase, exBase);
1233          leftPadding = l.getValue();
1234        }
1235      l = (Length) as.getAttribute(CSS.Attribute.PADDING_RIGHT);
1236      if (l != null)
1237        {
1238          l.setFontBases(emBase, exBase);
1239          rightPadding = l.getValue();
1240        }
1241      l = (Length) as.getAttribute(CSS.Attribute.PADDING_TOP);
1242      if (l != null)
1243        {
1244          l.setFontBases(emBase, exBase);
1245          topPadding = l.getValue();
1246        }
1247      l = (Length) as.getAttribute(CSS.Attribute.PADDING_BOTTOM);
1248      if (l != null)
1249        {
1250          l.setFontBases(emBase, exBase);
1251          bottomPadding = l.getValue();
1252        }
1253
1254      // Determine border.
1255      border = new CSSBorder(as, ss);
1256
1257      // Determine background.
1258      background = ss.getBackground(as);
1259
1260    }
1261    
1262    
1263    /**
1264     * Gets the inset needed on a given side to account for the margin, border
1265     * and padding.
1266     * 
1267     * @param size - the size of the box to get the inset for. View.TOP, View.LEFT,
1268     * View.BOTTOM or View.RIGHT.
1269     * @param v - the view making the request. This is used to get the AttributeSet,
1270     * amd may be used to resolve percentage arguments.
1271     * @return the inset
1272     * @throws IllegalArgumentException - for an invalid direction.
1273     */
1274    public float getInset(int size, View v)
1275    {
1276      float inset;
1277      switch (size)
1278        {
1279        case View.TOP:
1280          inset = topInset;
1281          if (border != null)
1282            inset += border.getBorderInsets(null).top;
1283          inset += topPadding;
1284          break;
1285        case View.BOTTOM:
1286          inset = bottomInset;
1287          if (border != null)
1288            inset += border.getBorderInsets(null).bottom;
1289          inset += bottomPadding;
1290          break;
1291        case View.LEFT:
1292          inset = leftInset;
1293          if (border != null)
1294            inset += border.getBorderInsets(null).left;
1295          inset += leftPadding;
1296          break;
1297        case View.RIGHT:
1298          inset = rightInset;
1299          if (border != null)
1300            inset += border.getBorderInsets(null).right;
1301          inset += rightPadding;
1302          break;
1303        default:
1304          inset = 0.0F;
1305      }
1306      return inset;
1307    }
1308    
1309    /**
1310     * Paints the CSS box according to the attributes given. This should
1311     * paint the border, padding and background.
1312     * 
1313     * @param g - the graphics configuration
1314     * @param x - the x coordinate
1315     * @param y - the y coordinate
1316     * @param w - the width of the allocated area
1317     * @param h - the height of the allocated area
1318     * @param v - the view making the request
1319     */
1320    public void paint(Graphics g, float x, float y, float w, float h, View v)
1321    {
1322      int inX = (int) (x + leftInset);
1323      int inY = (int) (y + topInset);
1324      int inW = (int) (w - leftInset - rightInset);
1325      int inH = (int) (h - topInset - bottomInset);
1326      if (background != null)
1327        {
1328          g.setColor(background);
1329          g.fillRect(inX, inY, inW, inH);
1330        }
1331      if (border != null)
1332        {
1333          border.paintBorder(null, g, inX, inY, inW, inH);
1334        }
1335    }
1336  }
1337  
1338  /**
1339   * This class carries out some of the CSS list formatting duties. Implementations
1340   * of this class enable views to present the CSS formatting while not knowing anything
1341   * about how the CSS values are being cached.
1342   * 
1343   * @author Lillian Angel (langel@redhat.com)
1344   */
1345  public static class ListPainter implements Serializable
1346  {
1347
1348    /**
1349     * Attribute set for painter
1350     */
1351    private AttributeSet attributes;
1352
1353    /**
1354     * The associated style sheet.
1355     */
1356    private StyleSheet styleSheet;
1357
1358    /**
1359     * The bullet type.
1360     */
1361    private String type;
1362
1363    /**
1364     * Package-private constructor.
1365     * 
1366     * @param as - AttributeSet for painter
1367     */
1368    ListPainter(AttributeSet as, StyleSheet ss)
1369    {
1370      attributes = as;
1371      styleSheet = ss;
1372      type = (String) as.getAttribute(CSS.Attribute.LIST_STYLE_TYPE);
1373    }
1374
1375    /**
1376     * Cached rectangle re-used in the paint method below.
1377     */
1378    private final Rectangle tmpRect = new Rectangle();
1379
1380    /**
1381     * Paints the CSS list decoration according to the attributes given.
1382     * 
1383     * @param g - the graphics configuration
1384     * @param x - the x coordinate
1385     * @param y - the y coordinate
1386     * @param w - the width of the allocated area
1387     * @param h - the height of the allocated area
1388     * @param v - the view making the request
1389     * @param item - the list item to be painted >=0.
1390     */
1391    public void paint(Graphics g, float x, float y, float w, float h, View v,
1392                      int item)
1393    {
1394      // FIXME: This is a very simplistic list rendering. We still need
1395      // to implement different bullet types (see type field) and custom
1396      // bullets via images.
1397      View itemView = v.getView(item);
1398      AttributeSet viewAtts = itemView.getAttributes();
1399      Object tag = viewAtts.getAttribute(StyleConstants.NameAttribute);
1400      // Only paint something here when the child view is an LI tag
1401      // and the calling view is some of the list tags then).
1402      if (tag != null && tag == HTML.Tag.LI)
1403        {
1404          g.setColor(Color.BLACK);
1405          int centerX = (int) (x - 12);
1406          int centerY = -1;
1407          // For paragraphs (almost all cases) center bullet vertically
1408          // in the middle of the first line.
1409          tmpRect.setBounds((int) x, (int) y, (int) w, (int) h);
1410          if (itemView.getViewCount() > 0)
1411            {
1412              View v1 = itemView.getView(0);
1413              if (v1 instanceof ParagraphView && v1.getViewCount() > 0)
1414                {             
1415                  Shape a1 = itemView.getChildAllocation(0, tmpRect);
1416                  Rectangle r1 = a1 instanceof Rectangle ? (Rectangle) a1
1417                                                         : a1.getBounds();
1418                  ParagraphView par = (ParagraphView) v1;
1419                  Shape a = par.getChildAllocation(0, r1);
1420                  if (a != null)
1421                    {
1422                      Rectangle r = a instanceof Rectangle ? (Rectangle) a
1423                                                           : a.getBounds();
1424                      centerY = (int) (r.height / 2 + r.y);
1425                    }
1426                }
1427            }
1428          if (centerY == -1)
1429            {
1430              centerY =(int) (h / 2 + y);
1431            }
1432          g.fillOval(centerX - 3, centerY - 3, 6, 6);
1433        }
1434    }
1435  }
1436
1437  /**
1438   * Converts an AttributeSet to a Map. This is used for CSS resolving.
1439   *
1440   * @param atts the attributes to convert
1441   *
1442   * @return the converted map
1443   */
1444  private Map attributeSetToMap(AttributeSet atts)
1445  {
1446    HashMap<String,String> map = new HashMap<String,String>();
1447    Enumeration<?> keys = atts.getAttributeNames();
1448    while (keys.hasMoreElements())
1449      {
1450        Object key = keys.nextElement();
1451        Object value = atts.getAttribute(key);
1452        map.put(key.toString(), value.toString());
1453      }
1454    return map;
1455  }
1456}