OSDN Git Service

libjava/ChangeLog:
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / text / html / StyleSheet.java
1 /* StyleSheet.java -- 
2    Copyright (C) 2005 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package javax.swing.text.html;
40
41 import gnu.javax.swing.text.html.css.BorderWidth;
42 import gnu.javax.swing.text.html.css.CSSColor;
43 import gnu.javax.swing.text.html.css.CSSParser;
44 import gnu.javax.swing.text.html.css.CSSParserCallback;
45 import gnu.javax.swing.text.html.css.FontSize;
46 import gnu.javax.swing.text.html.css.FontStyle;
47 import gnu.javax.swing.text.html.css.FontWeight;
48 import gnu.javax.swing.text.html.css.Length;
49 import gnu.javax.swing.text.html.css.Selector;
50
51 import java.awt.Color;
52 import java.awt.Font;
53 import java.awt.Graphics;
54 import java.awt.Rectangle;
55 import java.awt.Shape;
56 import java.awt.font.FontRenderContext;
57 import java.awt.geom.Rectangle2D;
58 import java.io.BufferedReader;
59 import java.io.IOException;
60 import java.io.InputStream;
61 import java.io.InputStreamReader;
62 import java.io.Reader;
63 import java.io.Serializable;
64 import java.io.StringReader;
65 import java.net.URL;
66 import java.util.ArrayList;
67 import java.util.Collections;
68 import java.util.Enumeration;
69 import java.util.HashMap;
70 import java.util.Iterator;
71 import java.util.List;
72 import java.util.Map;
73
74 import javax.swing.border.Border;
75 import javax.swing.event.ChangeListener;
76 import javax.swing.text.AttributeSet;
77 import javax.swing.text.Element;
78 import javax.swing.text.MutableAttributeSet;
79 import javax.swing.text.SimpleAttributeSet;
80 import javax.swing.text.Style;
81 import javax.swing.text.StyleConstants;
82 import javax.swing.text.StyleContext;
83 import javax.swing.text.View;
84
85
86 /**
87  * This class adds support for defining the visual characteristics of HTML views
88  * being rendered. This enables views to be customized by a look-and-feel, mulitple
89  * views over the same model can be rendered differently. Each EditorPane has its 
90  * own StyleSheet, but by default one sheet will be shared by all of the HTMLEditorKit
91  * instances. An HTMLDocument can also have a StyleSheet, which holds specific CSS
92  * specs. 
93  * 
94  *  In order for Views to store less state and therefore be more lightweight, 
95  *  the StyleSheet can act as a factory for painters that handle some of the 
96  *  rendering tasks. Since the StyleSheet may be used by views over multiple
97  *  documents the HTML attributes don't effect the selector being used.
98  *  
99  *  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 }