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