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<Element> 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[], List<Map<String,String>>)}.
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<Element> path, HTML.Tag tag)
392  {
393    int count = path.size();
394    String[] tags = new String[count];
395    List<Map<String,String>> attributes =
396      new ArrayList<Map<String,String>>(count);
397    for (int i = 0; i < count; i++)
398      {
399        Element el = path.get(i);
400        AttributeSet atts = el.getAttributes();
401        if (i == 0 && el.isLeaf())
402          {
403            Object o = atts.getAttribute(tag);
404            if (o instanceof AttributeSet)
405              atts = (AttributeSet) o;
406            else
407              atts = null;
408          }
409        if (atts != null)
410          {
411            HTML.Tag t =
412              (HTML.Tag) atts.getAttribute(StyleConstants.NameAttribute);
413            if (t != null)
414              tags[i] = t.toString();
415            else
416              tags[i] = null;
417            attributes.set(i, attributeSetToMap(atts));
418          }
419        else
420          {
421            tags[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,
438                             List<Map<String,String>> attributes)
439  {
440    // FIXME: This style resolver is not correct. But it works good enough for
441    // the default.css.
442    ArrayList<CSSStyle> styles = new ArrayList<CSSStyle>();
443    for (CSSStyle style : css)
444      {
445        if (style.selector.matches(tags, attributes))
446          styles.add(style);
447      }
448
449    // Add styles from linked stylesheets.
450    if (linked != null)
451      {
452        for (int i = linked.size() - 1; i >= 0; i--)
453          {
454            StyleSheet ss = linked.get(i);
455            for (int j = ss.css.size() - 1; j >= 0; j--)
456              {
457                CSSStyle style = ss.css.get(j);
458                if (style.selector.matches(tags, attributes))
459                  styles.add(style);
460              }
461          }
462      }
463
464    // Sort selectors.
465    Collections.sort(styles);
466    Style[] styleArray = styles.toArray(new Style[styles.size()]);
467    Style resolved = new MultiStyle(selector, styleArray);
468    resolvedStyles.put(selector, resolved);
469    return resolved;
470  }
471
472  /**
473   * Gets the rule that best matches the selector. selector is a space
474   * separated String of element names. The attributes of the returned
475   * Style will change as rules are added and removed.
476   *
477   * @param selector - the element names separated by spaces
478   * @return the set of CSS attributes to use to render
479   */
480  public Style getRule(String selector)
481  {
482    CSSStyle best = null;
483    for (Iterator<CSSStyle> i = css.iterator(); i.hasNext();)
484      {
485        CSSStyle style = i.next();
486        if (style.compareTo(best) < 0)
487          best = style;
488      }
489    return best;
490  }
491
492  /**
493   * Adds a set of rules to the sheet. The rules are expected to be in valid
494   * CSS format. This is called as a result of parsing a <style> tag
495   *
496   * @param rule - the rule to add to the sheet
497   */
498  public void addRule(String rule)
499  {
500    CSSStyleSheetParserCallback cb =
501      new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL);
502    // FIXME: Handle ref.
503    StringReader in = new StringReader(rule);
504    CSSParser parser = new CSSParser(in, cb);
505    try
506      {
507        parser.parse();
508      }
509    catch (IOException ex)
510      {
511        // Shouldn't happen. And if, then don't let it bork the outside code.
512      }
513    // Clean up resolved styles cache so that the new styles are recognized
514    // on next stylesheet request.
515    resolvedStyles.clear();
516  }
517
518  /**
519   * Translates a CSS declaration into an AttributeSet. This is called
520   * as a result of encountering an HTML style attribute.
521   *
522   * @param decl - the declaration to get
523   * @return the AttributeSet representing the declaration
524   */
525  public AttributeSet getDeclaration(String decl)
526  {
527    if (decl == null)
528      return SimpleAttributeSet.EMPTY;
529    // FIXME: Not implemented.
530    return null;
531  }
532
533  /**
534   * Loads a set of rules that have been specified in terms of CSS grammar.
535   * If there are any conflicts with existing rules, the new rule is added.
536   *
537   * @param in - the stream to read the CSS grammar from.
538   * @param ref - the reference URL. It is the location of the stream, it may
539   * be null. All relative URLs specified in the stream will be based upon this
540   * parameter.
541   * @throws IOException - For any IO error while reading
542   */
543  public void loadRules(Reader in, URL ref)
544    throws IOException
545  {
546    CSSStyleSheetParserCallback cb =
547      new CSSStyleSheetParserCallback(CSSStyle.PREC_UA);
548    // FIXME: Handle ref.
549    CSSParser parser = new CSSParser(in, cb);
550    parser.parse();
551  }
552
553  /**
554   * Gets a set of attributes to use in the view. This is a set of
555   * attributes that can be used for View.getAttributes
556   *
557   * @param v - the view to get the set for
558   * @return the AttributeSet to use in the view.
559   */
560  public AttributeSet getViewAttributes(View v)
561  {
562    return new ViewAttributeSet(v, this);
563  }
564
565  /**
566   * Removes a style previously added.
567   *
568   * @param nm - the name of the style to remove
569   */
570  public void removeStyle(String nm)
571  {
572    // FIXME: Not implemented.
573    super.removeStyle(nm);
574  }
575
576  /**
577   * Adds the rules from ss to those of the receiver. ss's rules will
578   * override the old rules. An added StyleSheet will never override the rules
579   * of the receiving style sheet.
580   *
581   * @param ss - the new StyleSheet.
582   */
583  public void addStyleSheet(StyleSheet ss)
584  {
585    if (linked == null)
586      linked = new ArrayList<StyleSheet>();
587    linked.add(ss);
588  }
589
590  /**
591   * Removes ss from those of the receiver
592   *
593   * @param ss - the StyleSheet to remove.
594   */
595  public void removeStyleSheet(StyleSheet ss)
596  {
597    if (linked != null)
598      {
599        linked.remove(ss);
600      }
601  }
602
603  /**
604   * Returns an array of the linked StyleSheets. May return null.
605   *
606   * @return - An array of the linked StyleSheets.
607   */
608  public StyleSheet[] getStyleSheets()
609  {
610    StyleSheet[] linkedSS;
611    if (linked != null)
612      {
613        linkedSS = new StyleSheet[linked.size()];
614        linkedSS = linked.toArray(linkedSS);
615      }
616    else
617      {
618        linkedSS = null;
619      }
620    return linkedSS;
621  }
622
623  /**
624   * Imports a style sheet from the url. The rules are directly added to the
625   * receiver. This is usually called when a <link> tag is resolved in an
626   * HTML document.
627   *
628   * @param url the URL to import the StyleSheet from
629   */
630  public void importStyleSheet(URL url)
631  {
632    try
633      {
634        InputStream in = url.openStream();
635        Reader r = new BufferedReader(new InputStreamReader(in));
636        CSSStyleSheetParserCallback cb =
637          new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL);
638        CSSParser parser = new CSSParser(r, cb);
639        parser.parse();
640      }
641    catch (IOException ex)
642      {
643        // We can't do anything about it I guess.
644      }
645  }
646
647  /**
648   * Sets the base url. All import statements that are relative, will be
649   * relative to base.
650   *
651   * @param base -
652   *          the base URL.
653   */
654  public void setBase(URL base)
655  {
656    this.base = base;
657  }
658
659  /**
660   * Gets the base url.
661   *
662   * @return - the base
663   */
664  public URL getBase()
665  {
666    return base;
667  }
668
669  /**
670   * Adds a CSS attribute to the given set.
671   *
672   * @param attr - the attribute set
673   * @param key - the attribute to add
674   * @param value - the value of the key
675   */
676  public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key,
677                              String value)
678  {
679    Object val = CSS.getValue(key, value);
680    CSS.addInternal(attr, key, value);
681    attr.addAttribute(key, val);
682  }
683
684  /**
685   * Adds a CSS attribute to the given set.
686   * This method parses the value argument from HTML based on key.
687   * Returns true if it finds a valid value for the given key,
688   * and false otherwise.
689   *
690   * @param attr - the attribute set
691   * @param key - the attribute to add
692   * @param value - the value of the key
693   * @return true if a valid value was found.
694   */
695  public boolean addCSSAttributeFromHTML(MutableAttributeSet attr, CSS.Attribute key,
696                                         String value)
697  {
698    // FIXME: Need to parse value from HTML based on key.
699    attr.addAttribute(key, value);
700    return attr.containsAttribute(key, value);
701  }
702
703  /**
704   * Converts a set of HTML attributes to an equivalent set of CSS attributes.
705   *
706   * @param htmlAttrSet - the set containing the HTML attributes.
707   * @return the set of CSS attributes
708   */
709  public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet)
710  {
711    AttributeSet cssAttr = htmlAttrSet.copyAttributes();
712
713    // The HTML align attribute maps directly to the CSS text-align attribute.
714    Object o = htmlAttrSet.getAttribute(HTML.Attribute.ALIGN);
715    if (o != null)
716      cssAttr = addAttribute(cssAttr, CSS.Attribute.TEXT_ALIGN, o);
717
718    // The HTML width attribute maps directly to CSS width.
719    o = htmlAttrSet.getAttribute(HTML.Attribute.WIDTH);
720    if (o != null)
721      cssAttr = addAttribute(cssAttr, CSS.Attribute.WIDTH,
722                             new Length(o.toString()));
723
724    // The HTML height attribute maps directly to CSS height.
725    o = htmlAttrSet.getAttribute(HTML.Attribute.HEIGHT);
726    if (o != null)
727      cssAttr = addAttribute(cssAttr, CSS.Attribute.HEIGHT,
728                             new Length(o.toString()));
729
730    o = htmlAttrSet.getAttribute(HTML.Attribute.NOWRAP);
731    if (o != null)
732      cssAttr = addAttribute(cssAttr, CSS.Attribute.WHITE_SPACE, "nowrap");
733
734    // Map cellspacing attr of tables to CSS border-spacing.
735    o = htmlAttrSet.getAttribute(HTML.Attribute.CELLSPACING);
736    if (o != null)
737      cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_SPACING,
738                             new Length(o.toString()));
739
740    // For table cells and headers, fetch the cellpadding value from the
741    // parent table and set it as CSS padding attribute.
742    HTML.Tag tag = (HTML.Tag)
743                   htmlAttrSet.getAttribute(StyleConstants.NameAttribute);
744    if ((tag == HTML.Tag.TD || tag == HTML.Tag.TH)
745        && htmlAttrSet instanceof Element)
746      {
747        Element el = (Element) htmlAttrSet;
748        AttributeSet tableAttrs = el.getParentElement().getParentElement()
749                                  .getAttributes();
750        o = tableAttrs.getAttribute(HTML.Attribute.CELLPADDING);
751        if (o != null)
752          {
753            Length l = new Length(o.toString());
754            cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_BOTTOM, l);
755            cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_LEFT, l);
756            cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_RIGHT, l);
757            cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_TOP, l);
758          }
759        o = tableAttrs.getAttribute(HTML.Attribute.BORDER);
760        cssAttr = translateBorder(cssAttr, o);
761      }
762
763    // Translate border attribute.
764    o = cssAttr.getAttribute(HTML.Attribute.BORDER);
765    cssAttr = translateBorder(cssAttr, o);
766
767    // TODO: Add more mappings.
768    return cssAttr;
769  }
770
771  /**
772   * Translates a HTML border attribute to a corresponding set of CSS
773   * attributes.
774   *
775   * @param cssAttr the original set of CSS attributes to add to
776   * @param o the value of the border attribute
777   *
778   * @return the new set of CSS attributes
779   */
780  private AttributeSet translateBorder(AttributeSet cssAttr, Object o)
781  {
782    if (o != null)
783      {
784        BorderWidth l = new BorderWidth(o.toString());
785        if (l.getValue() > 0)
786          {
787            cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_WIDTH, l);
788            cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_STYLE,
789                                   "solid");
790            cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_COLOR,
791                                   new CSSColor("black"));
792          }
793      }
794    return cssAttr;
795  }
796
797  /**
798   * Adds an attribute to the given set and returns a new set. This is implemented
799   * to convert StyleConstants attributes to CSS before forwarding them to the superclass.
800   * The StyleConstants attribute do not have corresponding CSS entry, the attribute
801   * is stored (but will likely not be used).
802   *
803   * @param old - the old set
804   * @param key - the non-null attribute key
805   * @param value - the attribute value
806   * @return the updated set
807   */
808  public AttributeSet addAttribute(AttributeSet old, Object key,
809                                   Object value)
810  {
811    // FIXME: Not implemented.
812    return super.addAttribute(old, key, value);
813  }
814
815  /**
816   * Adds a set of attributes to the element. If any of these attributes are
817   * StyleConstants, they will be converted to CSS before forwarding to the
818   * superclass.
819   *
820   * @param old - the old set
821   * @param attr - the attributes to add
822   * @return the updated attribute set
823   */
824  public AttributeSet addAttributes(AttributeSet old, AttributeSet attr)
825  {
826    // FIXME: Not implemented.
827    return super.addAttributes(old, attr);
828  }
829
830  /**
831   * Removes an attribute from the set. If the attribute is a
832   * StyleConstants, it will be converted to CSS before forwarding to the
833   * superclass.
834   *
835   * @param old - the old set
836   * @param key - the non-null attribute key
837   * @return the updated set
838   */
839  public AttributeSet removeAttribute(AttributeSet old, Object key)
840  {
841    // FIXME: Not implemented.
842    return super.removeAttribute(old, key);
843  }
844
845  /**
846   * Removes an attribute from the set. If any of the attributes are
847   * StyleConstants, they will be converted to CSS before forwarding to the
848   * superclass.
849   *
850   * @param old - the old set
851   * @param attrs - the attributes to remove
852   * @return the updated set
853   */
854  public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs)
855  {
856    // FIXME: Not implemented.
857    return super.removeAttributes(old, attrs);
858  }
859
860  /**
861   * Removes a set of attributes for the element. If any of the attributes is a
862   * StyleConstants, they will be converted to CSS before forwarding to the
863   * superclass.
864   *
865   * @param old - the old attribute set
866   * @param names - the attribute names
867   * @return the update attribute set
868   */
869  public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names)
870  {
871    // FIXME: Not implemented.
872    return super.removeAttributes(old, names);
873  }
874
875  /**
876   * Creates a compact set of attributes that might be shared. This is a hook
877   * for subclasses that want to change the behaviour of SmallAttributeSet.
878   *
879   * @param a - the set of attributes to be represented in the compact form.
880   * @return the set of attributes created
881   */
882  protected StyleContext.SmallAttributeSet createSmallAttributeSet(AttributeSet a)
883  {
884    return super.createSmallAttributeSet(a);
885  }
886
887  /**
888   * Creates a large set of attributes. This set is not shared. This is a hook
889   * for subclasses that want to change the behaviour of the larger attribute
890   * storage format.
891   *
892   * @param a - the set of attributes to be represented in the larger form.
893   * @return the large set of attributes.
894   */
895  protected MutableAttributeSet createLargeAttributeSet(AttributeSet a)
896  {
897    return super.createLargeAttributeSet(a);
898  }
899
900  /**
901   * Gets the font to use for the given set.
902   *
903   * @param a - the set to get the font for.
904   * @return the font for the set
905   */
906  public Font getFont(AttributeSet a)
907  {
908    int realSize = getFontSize(a);
909
910    // Decrement size for subscript and superscript.
911    Object valign = a.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
912    if (valign != null)
913      {
914        String v = valign.toString();
915        if (v.contains("sup") || v.contains("sub"))
916          realSize -= 2;
917      }
918
919    // TODO: Convert font family.
920    String family = "SansSerif";
921
922    int style = Font.PLAIN;
923    FontWeight weight = (FontWeight) a.getAttribute(CSS.Attribute.FONT_WEIGHT);
924    if (weight != null)
925      style |= weight.getValue();
926    FontStyle fStyle = (FontStyle) a.getAttribute(CSS.Attribute.FONT_STYLE);
927    if (fStyle != null)
928      style |= fStyle.getValue();
929    return new Font(family, style, realSize);
930  }
931
932  /**
933   * Determines the EM base value based on the specified attributes.
934   *
935   * @param atts the attibutes
936   *
937   * @return the EM base value
938   */
939  float getEMBase(AttributeSet atts)
940  {
941    Font font = getFont(atts);
942    FontRenderContext ctx = new FontRenderContext(null, false, false);
943    Rectangle2D bounds = font.getStringBounds("M", ctx);
944    return (float) bounds.getWidth();
945  }
946
947  /**
948   * Determines the EX base value based on the specified attributes.
949   *
950   * @param atts the attibutes
951   *
952   * @return the EX base value
953   */
954  float getEXBase(AttributeSet atts)
955  {
956    Font font = getFont(atts);
957    FontRenderContext ctx = new FontRenderContext(null, false, false);
958    Rectangle2D bounds = font.getStringBounds("x", ctx);
959    return (float) bounds.getHeight();
960  }
961
962  /**
963   * Resolves the fontsize for a given set of attributes.
964   *
965   * @param atts the attributes
966   *
967   * @return the resolved font size
968   */
969  private int getFontSize(AttributeSet atts)
970  {
971    int size = 12;
972    if (atts.isDefined(CSS.Attribute.FONT_SIZE))
973      {
974        FontSize fs = (FontSize) atts.getAttribute(CSS.Attribute.FONT_SIZE);
975        if (fs.isRelative())
976          {
977            int parSize = 12;
978            AttributeSet resolver = atts.getResolveParent();
979            if (resolver != null)
980              parSize = getFontSize(resolver);
981            size = fs.getValue(parSize);
982          }
983        else
984          {
985            size = fs.getValue();
986          }
987      }
988    else
989      {
990        AttributeSet resolver = atts.getResolveParent();
991        if (resolver != null)
992          size = getFontSize(resolver);
993      }
994    return size;
995  }
996
997  /**
998   * Takes a set of attributes and turns it into a foreground
999   * color specification. This is used to specify things like, brigher, more hue
1000   * etc.
1001   *
1002   * @param a - the set to get the foreground color for
1003   * @return the foreground color for the set
1004   */
1005  public Color getForeground(AttributeSet a)
1006  {
1007    CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.COLOR);
1008    Color color = null;
1009    if (c != null)
1010      color = c.getValue();
1011    return color;
1012  }
1013
1014  /**
1015   * Takes a set of attributes and turns it into a background
1016   * color specification. This is used to specify things like, brigher, more hue
1017   * etc.
1018   *
1019   * @param a - the set to get the background color for
1020   * @return the background color for the set
1021   */
1022  public Color getBackground(AttributeSet a)
1023  {
1024    CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.BACKGROUND_COLOR);
1025    Color color = null;
1026    if (c != null)
1027      color = c.getValue();
1028    return color;
1029  }
1030
1031  /**
1032   * Gets the box formatter to use for the given set of CSS attributes.
1033   *
1034   * @param a - the given set
1035   * @return the box formatter
1036   */
1037  public BoxPainter getBoxPainter(AttributeSet a)
1038  {
1039    return new BoxPainter(a, this);
1040  }
1041
1042  /**
1043   * Gets the list formatter to use for the given set of CSS attributes.
1044   *
1045   * @param a - the given set
1046   * @return the list formatter
1047   */
1048  public ListPainter getListPainter(AttributeSet a)
1049  {
1050    return new ListPainter(a, this);
1051  }
1052
1053  /**
1054   * Sets the base font size between 1 and 7.
1055   *
1056   * @param sz - the new font size for the base.
1057   */
1058  public void setBaseFontSize(int sz)
1059  {
1060    if (sz <= 7 && sz >= 1)
1061      baseFontSize = sz;
1062  }
1063
1064  /**
1065   * Sets the base font size from the String. It can either identify
1066   * a specific font size (between 1 and 7) or identify a relative
1067   * font size such as +1 or -2.
1068   *
1069   * @param size - the new font size as a String.
1070   */
1071  public void setBaseFontSize(String size)
1072  {
1073    size = size.trim();
1074    int temp = 0;
1075    try
1076      {
1077        if (size.length() == 2)
1078          {
1079            int i = new Integer(size.substring(1)).intValue();
1080            if (size.startsWith("+"))
1081              temp = baseFontSize + i;
1082            else if (size.startsWith("-"))
1083              temp = baseFontSize - i;
1084          }
1085        else if (size.length() == 1)
1086          temp = new Integer(size.substring(0)).intValue();
1087
1088        if (temp <= 7 && temp >= 1)
1089          baseFontSize = temp;
1090      }
1091    catch (NumberFormatException nfe)
1092      {
1093        // Do nothing here
1094      }
1095  }
1096
1097  /**
1098   * TODO
1099   *
1100   * @param pt - TODO
1101   * @return TODO
1102   */
1103  public static int getIndexOfSize(float pt)
1104  {
1105    // FIXME: Not implemented.
1106    return 0;
1107  }
1108
1109  /**
1110   * Gets the point size, given a size index.
1111   *
1112   * @param index - the size index
1113   * @return the point size.
1114   */
1115  public float getPointSize(int index)
1116  {
1117    // FIXME: Not implemented.
1118    return 0;
1119  }
1120
1121  /**
1122   * Given the string of the size, returns the point size value.
1123   *
1124   * @param size - the string representation of the size.
1125   * @return - the point size value.
1126   */
1127  public float getPointSize(String size)
1128  {
1129    // FIXME: Not implemented.
1130    return 0;
1131  }
1132
1133  /**
1134   * Convert the color string represenation into java.awt.Color. The valid
1135   * values are like "aqua" , "#00FFFF" or "rgb(1,6,44)".
1136   *
1137   * @param colorName the color to convert.
1138   * @return the matching java.awt.color
1139   */
1140  public Color stringToColor(String colorName)
1141  {
1142    return CSSColor.convertValue(colorName);
1143  }
1144
1145  /**
1146   * This class carries out some of the duties of CSS formatting. This enables views
1147   * to present the CSS formatting while not knowing how the CSS values are cached.
1148   *
1149   * This object is reponsible for the insets of a View and making sure
1150   * the background is maintained according to the CSS attributes.
1151   *
1152   * @author Lillian Angel (langel@redhat.com)
1153   */
1154  public static class BoxPainter extends Object implements Serializable
1155  {
1156
1157    /**
1158     * The left inset.
1159     */
1160    private float leftInset;
1161
1162    /**
1163     * The right inset.
1164     */
1165    private float rightInset;
1166
1167    /**
1168     * The top inset.
1169     */
1170    private float topInset;
1171
1172    /**
1173     * The bottom inset.
1174     */
1175    private float bottomInset;
1176
1177    /**
1178     * The border of the box.
1179     */
1180    private Border border;
1181
1182    private float leftPadding;
1183    private float rightPadding;
1184    private float topPadding;
1185    private float bottomPadding;
1186
1187    /**
1188     * The background color.
1189     */
1190    private Color background;
1191
1192    /**
1193     * Package-private constructor.
1194     *
1195     * @param as - AttributeSet for painter
1196     */
1197    BoxPainter(AttributeSet as, StyleSheet ss)
1198    {
1199      float emBase = ss.getEMBase(as);
1200      float exBase = ss.getEXBase(as);
1201      // Fetch margins.
1202      Length l = (Length) as.getAttribute(CSS.Attribute.MARGIN_LEFT);
1203      if (l != null)
1204        {
1205          l.setFontBases(emBase, exBase);
1206          leftInset = l.getValue();
1207        }
1208      l = (Length) as.getAttribute(CSS.Attribute.MARGIN_RIGHT);
1209      if (l != null)
1210        {
1211          l.setFontBases(emBase, exBase);
1212          rightInset = l.getValue();
1213        }
1214      l = (Length) as.getAttribute(CSS.Attribute.MARGIN_TOP);
1215      if (l != null)
1216        {
1217          l.setFontBases(emBase, exBase);
1218          topInset = l.getValue();
1219        }
1220      l = (Length) as.getAttribute(CSS.Attribute.MARGIN_BOTTOM);
1221      if (l != null)
1222        {
1223          l.setFontBases(emBase, exBase);
1224          bottomInset = l.getValue();
1225        }
1226
1227      // Fetch padding.
1228      l = (Length) as.getAttribute(CSS.Attribute.PADDING_LEFT);
1229      if (l != null)
1230        {
1231          l.setFontBases(emBase, exBase);
1232          leftPadding = l.getValue();
1233        }
1234      l = (Length) as.getAttribute(CSS.Attribute.PADDING_RIGHT);
1235      if (l != null)
1236        {
1237          l.setFontBases(emBase, exBase);
1238          rightPadding = l.getValue();
1239        }
1240      l = (Length) as.getAttribute(CSS.Attribute.PADDING_TOP);
1241      if (l != null)
1242        {
1243          l.setFontBases(emBase, exBase);
1244          topPadding = l.getValue();
1245        }
1246      l = (Length) as.getAttribute(CSS.Attribute.PADDING_BOTTOM);
1247      if (l != null)
1248        {
1249          l.setFontBases(emBase, exBase);
1250          bottomPadding = l.getValue();
1251        }
1252
1253      // Determine border.
1254      border = new CSSBorder(as, ss);
1255
1256      // Determine background.
1257      background = ss.getBackground(as);
1258
1259    }
1260
1261
1262    /**
1263     * Gets the inset needed on a given side to account for the margin, border
1264     * and padding.
1265     *
1266     * @param size - the size of the box to get the inset for. View.TOP, View.LEFT,
1267     * View.BOTTOM or View.RIGHT.
1268     * @param v - the view making the request. This is used to get the AttributeSet,
1269     * amd may be used to resolve percentage arguments.
1270     * @return the inset
1271     * @throws IllegalArgumentException - for an invalid direction.
1272     */
1273    public float getInset(int size, View v)
1274    {
1275      float inset;
1276      switch (size)
1277        {
1278        case View.TOP:
1279          inset = topInset;
1280          if (border != null)
1281            inset += border.getBorderInsets(null).top;
1282          inset += topPadding;
1283          break;
1284        case View.BOTTOM:
1285          inset = bottomInset;
1286          if (border != null)
1287            inset += border.getBorderInsets(null).bottom;
1288          inset += bottomPadding;
1289          break;
1290        case View.LEFT:
1291          inset = leftInset;
1292          if (border != null)
1293            inset += border.getBorderInsets(null).left;
1294          inset += leftPadding;
1295          break;
1296        case View.RIGHT:
1297          inset = rightInset;
1298          if (border != null)
1299            inset += border.getBorderInsets(null).right;
1300          inset += rightPadding;
1301          break;
1302        default:
1303          inset = 0.0F;
1304      }
1305      return inset;
1306    }
1307
1308    /**
1309     * Paints the CSS box according to the attributes given. This should
1310     * paint the border, padding and background.
1311     *
1312     * @param g - the graphics configuration
1313     * @param x - the x coordinate
1314     * @param y - the y coordinate
1315     * @param w - the width of the allocated area
1316     * @param h - the height of the allocated area
1317     * @param v - the view making the request
1318     */
1319    public void paint(Graphics g, float x, float y, float w, float h, View v)
1320    {
1321      int inX = (int) (x + leftInset);
1322      int inY = (int) (y + topInset);
1323      int inW = (int) (w - leftInset - rightInset);
1324      int inH = (int) (h - topInset - bottomInset);
1325      if (background != null)
1326        {
1327          g.setColor(background);
1328          g.fillRect(inX, inY, inW, inH);
1329        }
1330      if (border != null)
1331        {
1332          border.paintBorder(null, g, inX, inY, inW, inH);
1333        }
1334    }
1335  }
1336
1337  /**
1338   * This class carries out some of the CSS list formatting duties. Implementations
1339   * of this class enable views to present the CSS formatting while not knowing anything
1340   * about how the CSS values are being cached.
1341   *
1342   * @author Lillian Angel (langel@redhat.com)
1343   */
1344  public static class ListPainter implements Serializable
1345  {
1346
1347    /**
1348     * Attribute set for painter
1349     */
1350    private AttributeSet attributes;
1351
1352    /**
1353     * The associated style sheet.
1354     */
1355    private StyleSheet styleSheet;
1356
1357    /**
1358     * The bullet type.
1359     */
1360    private String type;
1361
1362    /**
1363     * Package-private constructor.
1364     *
1365     * @param as - AttributeSet for painter
1366     */
1367    ListPainter(AttributeSet as, StyleSheet ss)
1368    {
1369      attributes = as;
1370      styleSheet = ss;
1371      type = (String) as.getAttribute(CSS.Attribute.LIST_STYLE_TYPE);
1372    }
1373
1374    /**
1375     * Cached rectangle re-used in the paint method below.
1376     */
1377    private final Rectangle tmpRect = new Rectangle();
1378
1379    /**
1380     * Paints the CSS list decoration according to the attributes given.
1381     *
1382     * @param g - the graphics configuration
1383     * @param x - the x coordinate
1384     * @param y - the y coordinate
1385     * @param w - the width of the allocated area
1386     * @param h - the height of the allocated area
1387     * @param v - the view making the request
1388     * @param item - the list item to be painted >=0.
1389     */
1390    public void paint(Graphics g, float x, float y, float w, float h, View v,
1391                      int item)
1392    {
1393      // FIXME: This is a very simplistic list rendering. We still need
1394      // to implement different bullet types (see type field) and custom
1395      // bullets via images.
1396      View itemView = v.getView(item);
1397      AttributeSet viewAtts = itemView.getAttributes();
1398      Object tag = viewAtts.getAttribute(StyleConstants.NameAttribute);
1399      // Only paint something here when the child view is an LI tag
1400      // and the calling view is some of the list tags then).
1401      if (tag != null && tag == HTML.Tag.LI)
1402        {
1403          g.setColor(Color.BLACK);
1404          int centerX = (int) (x - 12);
1405          int centerY = -1;
1406          // For paragraphs (almost all cases) center bullet vertically
1407          // in the middle of the first line.
1408          tmpRect.setBounds((int) x, (int) y, (int) w, (int) h);
1409          if (itemView.getViewCount() > 0)
1410            {
1411              View v1 = itemView.getView(0);
1412              if (v1 instanceof ParagraphView && v1.getViewCount() > 0)
1413                {
1414                  Shape a1 = itemView.getChildAllocation(0, tmpRect);
1415                  Rectangle r1 = a1 instanceof Rectangle ? (Rectangle) a1
1416                                                         : a1.getBounds();
1417                  ParagraphView par = (ParagraphView) v1;
1418                  Shape a = par.getChildAllocation(0, r1);
1419                  if (a != null)
1420                    {
1421                      Rectangle r = a instanceof Rectangle ? (Rectangle) a
1422                                                           : a.getBounds();
1423                      centerY = (int) (r.height / 2 + r.y);
1424                    }
1425                }
1426            }
1427          if (centerY == -1)
1428            {
1429              centerY =(int) (h / 2 + y);
1430            }
1431          g.fillOval(centerX - 3, centerY - 3, 6, 6);
1432        }
1433    }
1434  }
1435
1436  /**
1437   * Converts an AttributeSet to a Map. This is used for CSS resolving.
1438   *
1439   * @param atts the attributes to convert
1440   *
1441   * @return the converted map
1442   */
1443  private Map<String,String> attributeSetToMap(AttributeSet atts)
1444  {
1445    HashMap<String,String> map = new HashMap<String,String>();
1446    Enumeration<?> keys = atts.getAttributeNames();
1447    while (keys.hasMoreElements())
1448      {
1449        Object key = keys.nextElement();
1450        Object value = atts.getAttribute(key);
1451        map.put(key.toString(), value.toString());
1452      }
1453    return map;
1454  }
1455}