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 }