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