001    /* StyleSheet.java --
002       Copyright (C) 2005 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.text.html;
040    
041    import gnu.javax.swing.text.html.css.BorderWidth;
042    import gnu.javax.swing.text.html.css.CSSColor;
043    import gnu.javax.swing.text.html.css.CSSParser;
044    import gnu.javax.swing.text.html.css.CSSParserCallback;
045    import gnu.javax.swing.text.html.css.FontSize;
046    import gnu.javax.swing.text.html.css.FontStyle;
047    import gnu.javax.swing.text.html.css.FontWeight;
048    import gnu.javax.swing.text.html.css.Length;
049    import gnu.javax.swing.text.html.css.Selector;
050    
051    import java.awt.Color;
052    import java.awt.Font;
053    import java.awt.Graphics;
054    import java.awt.Rectangle;
055    import java.awt.Shape;
056    import java.awt.font.FontRenderContext;
057    import java.awt.geom.Rectangle2D;
058    import java.io.BufferedReader;
059    import java.io.IOException;
060    import java.io.InputStream;
061    import java.io.InputStreamReader;
062    import java.io.Reader;
063    import java.io.Serializable;
064    import java.io.StringReader;
065    import java.net.URL;
066    import java.util.ArrayList;
067    import java.util.Collections;
068    import java.util.Enumeration;
069    import java.util.HashMap;
070    import java.util.Iterator;
071    import java.util.List;
072    import java.util.Map;
073    
074    import javax.swing.border.Border;
075    import javax.swing.event.ChangeListener;
076    import javax.swing.text.AttributeSet;
077    import javax.swing.text.Element;
078    import javax.swing.text.MutableAttributeSet;
079    import javax.swing.text.SimpleAttributeSet;
080    import javax.swing.text.Style;
081    import javax.swing.text.StyleConstants;
082    import javax.swing.text.StyleContext;
083    import 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     */
104    public 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    }