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