| /* StyleSheet.java -- | |
| Copyright (C) 2005 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| 02110-1301 USA. | |
| Linking this library statically or dynamically with other modules is | |
| making a combined work based on this library. Thus, the terms and | |
| conditions of the GNU General Public License cover the whole | |
| combination. | |
| As a special exception, the copyright holders of this library give you | |
| permission to link this library with independent modules to produce an | |
| executable, regardless of the license terms of these independent | |
| modules, and to copy and distribute the resulting executable under | |
| terms of your choice, provided that you also meet, for each linked | |
| independent module, the terms and conditions of the license of that | |
| module. An independent module is a module which is not derived from | |
| or based on this library. If you modify this library, you may extend | |
| this exception to your version of the library, but you are not | |
| obligated to do so. If you do not wish to do so, delete this | |
| exception statement from your version. */ | |
| package javax.swing.text.html; | |
| import gnu.javax.swing.text.html.css.BorderWidth; | |
| import gnu.javax.swing.text.html.css.CSSColor; | |
| import gnu.javax.swing.text.html.css.CSSParser; | |
| import gnu.javax.swing.text.html.css.CSSParserCallback; | |
| import gnu.javax.swing.text.html.css.FontSize; | |
| import gnu.javax.swing.text.html.css.FontStyle; | |
| import gnu.javax.swing.text.html.css.FontWeight; | |
| import gnu.javax.swing.text.html.css.Length; | |
| import gnu.javax.swing.text.html.css.Selector; | |
| import java.awt.Color; | |
| import java.awt.Font; | |
| import java.awt.Graphics; | |
| import java.awt.Rectangle; | |
| import java.awt.Shape; | |
| import java.awt.font.FontRenderContext; | |
| import java.awt.geom.Rectangle2D; | |
| import java.io.BufferedReader; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.io.InputStreamReader; | |
| import java.io.Reader; | |
| import java.io.Serializable; | |
| import java.io.StringReader; | |
| import java.net.URL; | |
| import java.util.ArrayList; | |
| import java.util.Collections; | |
| import java.util.Enumeration; | |
| import java.util.HashMap; | |
| import java.util.Iterator; | |
| import java.util.List; | |
| import java.util.Map; | |
| import javax.swing.border.Border; | |
| import javax.swing.event.ChangeListener; | |
| import javax.swing.text.AttributeSet; | |
| import javax.swing.text.Element; | |
| import javax.swing.text.MutableAttributeSet; | |
| import javax.swing.text.SimpleAttributeSet; | |
| import javax.swing.text.Style; | |
| import javax.swing.text.StyleConstants; | |
| import javax.swing.text.StyleContext; | |
| import javax.swing.text.View; | |
| /** | |
| * This class adds support for defining the visual characteristics of HTML views | |
| * being rendered. This enables views to be customized by a look-and-feel, mulitple | |
| * views over the same model can be rendered differently. Each EditorPane has its | |
| * own StyleSheet, but by default one sheet will be shared by all of the HTMLEditorKit | |
| * instances. An HTMLDocument can also have a StyleSheet, which holds specific CSS | |
| * specs. | |
| * | |
| * In order for Views to store less state and therefore be more lightweight, | |
| * the StyleSheet can act as a factory for painters that handle some of the | |
| * rendering tasks. Since the StyleSheet may be used by views over multiple | |
| * documents the HTML attributes don't effect the selector being used. | |
| * | |
| * The rules are stored as named styles, and other information is stored to | |
| * translate the context of an element to a rule. | |
| * | |
| * @author Lillian Angel (langel@redhat.com) | |
| */ | |
| public class StyleSheet extends StyleContext | |
| { | |
| /** | |
| * Parses CSS stylesheets using the parser in gnu/javax/swing/html/css. | |
| * | |
| * This is package private to avoid accessor methods. | |
| */ | |
| class CSSStyleSheetParserCallback | |
| implements CSSParserCallback | |
| { | |
| /** | |
| * The current styles. | |
| */ | |
| private CSSStyle[] styles; | |
| /** | |
| * The precedence of the stylesheet to be parsed. | |
| */ | |
| private int precedence; | |
| /** | |
| * Creates a new CSS parser. This parser parses a CSS stylesheet with | |
| * the specified precedence. | |
| * | |
| * @param prec the precedence, according to the constants defined in | |
| * CSSStyle | |
| */ | |
| CSSStyleSheetParserCallback(int prec) | |
| { | |
| precedence = prec; | |
| } | |
| /** | |
| * Called at the beginning of a statement. | |
| * | |
| * @param sel the selector | |
| */ | |
| public void startStatement(Selector[] sel) | |
| { | |
| styles = new CSSStyle[sel.length]; | |
| for (int i = 0; i < sel.length; i++) | |
| styles[i] = new CSSStyle(precedence, sel[i]); | |
| } | |
| /** | |
| * Called at the end of a statement. | |
| */ | |
| public void endStatement() | |
| { | |
| for (int i = 0; i < styles.length; i++) | |
| css.add(styles[i]); | |
| styles = null; | |
| } | |
| /** | |
| * Called when a declaration is parsed. | |
| * | |
| * @param property the property | |
| * @param value the value | |
| */ | |
| public void declaration(String property, String value) | |
| { | |
| CSS.Attribute cssAtt = CSS.getAttribute(property); | |
| Object val = CSS.getValue(cssAtt, value); | |
| for (int i = 0; i < styles.length; i++) | |
| { | |
| CSSStyle style = styles[i]; | |
| CSS.addInternal(style, cssAtt, value); | |
| if (cssAtt != null) | |
| style.addAttribute(cssAtt, val); | |
| } | |
| } | |
| } | |
| /** | |
| * Represents a style that is defined by a CSS rule. | |
| */ | |
| private class CSSStyle | |
| extends SimpleAttributeSet | |
| implements Style, Comparable<CSSStyle> | |
| { | |
| static final int PREC_UA = 0; | |
| static final int PREC_NORM = 100000; | |
| static final int PREC_AUTHOR_NORMAL = 200000; | |
| static final int PREC_AUTHOR_IMPORTANT = 300000; | |
| static final int PREC_USER_IMPORTANT = 400000; | |
| /** | |
| * The priority of this style when matching CSS selectors. | |
| */ | |
| private int precedence; | |
| /** | |
| * The selector for this rule. | |
| * | |
| * This is package private to avoid accessor methods. | |
| */ | |
| Selector selector; | |
| CSSStyle(int prec, Selector sel) | |
| { | |
| precedence = prec; | |
| selector = sel; | |
| } | |
| public String getName() | |
| { | |
| // TODO: Implement this for correctness. | |
| return null; | |
| } | |
| public void addChangeListener(ChangeListener listener) | |
| { | |
| // TODO: Implement this for correctness. | |
| } | |
| public void removeChangeListener(ChangeListener listener) | |
| { | |
| // TODO: Implement this for correctness. | |
| } | |
| /** | |
| * Sorts the rule according to the style's precedence and the | |
| * selectors specificity. | |
| */ | |
| public int compareTo(CSSStyle other) | |
| { | |
| return other.precedence + other.selector.getSpecificity() | |
| - precedence - selector.getSpecificity(); | |
| } | |
| } | |
| /** The base URL */ | |
| URL base; | |
| /** Base font size (int) */ | |
| int baseFontSize; | |
| /** | |
| * The linked style sheets stored. | |
| */ | |
| private ArrayList<StyleSheet> linked; | |
| /** | |
| * Maps element names (selectors) to AttributSet (the corresponding style | |
| * information). | |
| */ | |
| ArrayList<CSSStyle> css = new ArrayList<CSSStyle>(); | |
| /** | |
| * Maps selectors to their resolved styles. | |
| */ | |
| private HashMap<String,Style> resolvedStyles; | |
| /** | |
| * Constructs a StyleSheet. | |
| */ | |
| public StyleSheet() | |
| { | |
| super(); | |
| baseFontSize = 4; // Default font size from CSS | |
| resolvedStyles = new HashMap<String,Style>(); | |
| } | |
| /** | |
| * Gets the style used to render the given tag. The element represents the tag | |
| * and can be used to determine the nesting, where the attributes will differ | |
| * if there is nesting inside of elements. | |
| * | |
| * @param t - the tag to translate to visual attributes | |
| * @param e - the element representing the tag | |
| * @return the set of CSS attributes to use to render the tag. | |
| */ | |
| public Style getRule(HTML.Tag t, Element e) | |
| { | |
| // Create list of the element and all of its parents, starting | |
| // with the bottommost element. | |
| ArrayList<Element> path = new ArrayList<Element>(); | |
| Element el; | |
| AttributeSet atts; | |
| for (el = e; el != null; el = el.getParentElement()) | |
| path.add(el); | |
| // Create fully qualified selector. | |
| StringBuilder selector = new StringBuilder(); | |
| int count = path.size(); | |
| // We append the actual element after this loop. | |
| for (int i = count - 1; i > 0; i--) | |
| { | |
| el = path.get(i); | |
| atts = el.getAttributes(); | |
| Object name = atts.getAttribute(StyleConstants.NameAttribute); | |
| selector.append(name.toString()); | |
| if (atts.isDefined(HTML.Attribute.ID)) | |
| { | |
| selector.append('#'); | |
| selector.append(atts.getAttribute(HTML.Attribute.ID)); | |
| } | |
| if (atts.isDefined(HTML.Attribute.CLASS)) | |
| { | |
| selector.append('.'); | |
| selector.append(atts.getAttribute(HTML.Attribute.CLASS)); | |
| } | |
| if (atts.isDefined(HTML.Attribute.DYNAMIC_CLASS)) | |
| { | |
| selector.append(':'); | |
| selector.append(atts.getAttribute(HTML.Attribute.DYNAMIC_CLASS)); | |
| } | |
| if (atts.isDefined(HTML.Attribute.PSEUDO_CLASS)) | |
| { | |
| selector.append(':'); | |
| selector.append(atts.getAttribute(HTML.Attribute.PSEUDO_CLASS)); | |
| } | |
| selector.append(' '); | |
| } | |
| selector.append(t.toString()); | |
| el = path.get(0); | |
| atts = el.getAttributes(); | |
| // For leaf elements, we have to fetch the tag specific attributes. | |
| if (el.isLeaf()) | |
| { | |
| Object o = atts.getAttribute(t); | |
| if (o instanceof AttributeSet) | |
| atts = (AttributeSet) o; | |
| else | |
| atts = null; | |
| } | |
| if (atts != null) | |
| { | |
| if (atts.isDefined(HTML.Attribute.ID)) | |
| { | |
| selector.append('#'); | |
| selector.append(atts.getAttribute(HTML.Attribute.ID)); | |
| } | |
| if (atts.isDefined(HTML.Attribute.CLASS)) | |
| { | |
| selector.append('.'); | |
| selector.append(atts.getAttribute(HTML.Attribute.CLASS)); | |
| } | |
| if (atts.isDefined(HTML.Attribute.DYNAMIC_CLASS)) | |
| { | |
| selector.append(':'); | |
| selector.append(atts.getAttribute(HTML.Attribute.DYNAMIC_CLASS)); | |
| } | |
| if (atts.isDefined(HTML.Attribute.PSEUDO_CLASS)) | |
| { | |
| selector.append(':'); | |
| selector.append(atts.getAttribute(HTML.Attribute.PSEUDO_CLASS)); | |
| } | |
| } | |
| return getResolvedStyle(selector.toString(), path, t); | |
| } | |
| /** | |
| * Fetches a resolved style. If there is no resolved style for the | |
| * specified selector, the resolve the style using | |
| * {@link #resolveStyle(String, List, HTML.Tag)}. | |
| * | |
| * @param selector the selector for which to resolve the style | |
| * @param path the Element path, used in the resolving algorithm | |
| * @param tag the tag for which to resolve | |
| * | |
| * @return the resolved style | |
| */ | |
| private Style getResolvedStyle(String selector, List<Element> path, HTML.Tag tag) | |
| { | |
| Style style = resolvedStyles.get(selector); | |
| if (style == null) | |
| style = resolveStyle(selector, path, tag); | |
| return style; | |
| } | |
| /** | |
| * Resolves a style. This creates arrays that hold the tag names, | |
| * class and id attributes and delegates the work to | |
| * {@link #resolveStyle(String, String[], List<Map<String,String>>)}. | |
| * | |
| * @param selector the selector | |
| * @param path the Element path | |
| * @param tag the tag | |
| * | |
| * @return the resolved style | |
| */ | |
| private Style resolveStyle(String selector, List<Element> path, HTML.Tag tag) | |
| { | |
| int count = path.size(); | |
| String[] tags = new String[count]; | |
| List<Map<String,String>> attributes = | |
| new ArrayList<Map<String,String>>(count); | |
| for (int i = 0; i < count; i++) | |
| { | |
| Element el = path.get(i); | |
| AttributeSet atts = el.getAttributes(); | |
| if (i == 0 && el.isLeaf()) | |
| { | |
| Object o = atts.getAttribute(tag); | |
| if (o instanceof AttributeSet) | |
| atts = (AttributeSet) o; | |
| else | |
| atts = null; | |
| } | |
| if (atts != null) | |
| { | |
| HTML.Tag t = | |
| (HTML.Tag) atts.getAttribute(StyleConstants.NameAttribute); | |
| if (t != null) | |
| tags[i] = t.toString(); | |
| else | |
| tags[i] = null; | |
| attributes.add(attributeSetToMap(atts)); | |
| } | |
| else | |
| { | |
| tags[i] = null; | |
| attributes.add(null); | |
| } | |
| } | |
| tags[0] = tag.toString(); | |
| return resolveStyle(selector, tags, attributes); | |
| } | |
| /** | |
| * Performs style resolving. | |
| * | |
| * @param selector the selector | |
| * @param tags the tags | |
| * @param attributes the attributes of the tags | |
| * | |
| * @return the resolved style | |
| */ | |
| private Style resolveStyle(String selector, String[] tags, | |
| List<Map<String,String>> attributes) | |
| { | |
| // FIXME: This style resolver is not correct. But it works good enough for | |
| // the default.css. | |
| ArrayList<CSSStyle> styles = new ArrayList<CSSStyle>(); | |
| for (CSSStyle style : css) | |
| { | |
| if (style.selector.matches(tags, attributes)) | |
| styles.add(style); | |
| } | |
| // Add styles from linked stylesheets. | |
| if (linked != null) | |
| { | |
| for (int i = linked.size() - 1; i >= 0; i--) | |
| { | |
| StyleSheet ss = linked.get(i); | |
| for (int j = ss.css.size() - 1; j >= 0; j--) | |
| { | |
| CSSStyle style = ss.css.get(j); | |
| if (style.selector.matches(tags, attributes)) | |
| styles.add(style); | |
| } | |
| } | |
| } | |
| // Sort selectors. | |
| Collections.sort(styles); | |
| Style[] styleArray = styles.toArray(new Style[styles.size()]); | |
| Style resolved = new MultiStyle(selector, styleArray); | |
| resolvedStyles.put(selector, resolved); | |
| return resolved; | |
| } | |
| /** | |
| * Gets the rule that best matches the selector. selector is a space | |
| * separated String of element names. The attributes of the returned | |
| * Style will change as rules are added and removed. | |
| * | |
| * @param selector - the element names separated by spaces | |
| * @return the set of CSS attributes to use to render | |
| */ | |
| public Style getRule(String selector) | |
| { | |
| CSSStyle best = null; | |
| for (Iterator<CSSStyle> i = css.iterator(); i.hasNext();) | |
| { | |
| CSSStyle style = i.next(); | |
| if (style.compareTo(best) < 0) | |
| best = style; | |
| } | |
| return best; | |
| } | |
| /** | |
| * Adds a set of rules to the sheet. The rules are expected to be in valid | |
| * CSS format. This is called as a result of parsing a <style> tag | |
| * | |
| * @param rule - the rule to add to the sheet | |
| */ | |
| public void addRule(String rule) | |
| { | |
| CSSStyleSheetParserCallback cb = | |
| new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL); | |
| // FIXME: Handle ref. | |
| StringReader in = new StringReader(rule); | |
| CSSParser parser = new CSSParser(in, cb); | |
| try | |
| { | |
| parser.parse(); | |
| } | |
| catch (IOException ex) | |
| { | |
| // Shouldn't happen. And if, then don't let it bork the outside code. | |
| } | |
| // Clean up resolved styles cache so that the new styles are recognized | |
| // on next stylesheet request. | |
| resolvedStyles.clear(); | |
| } | |
| /** | |
| * Translates a CSS declaration into an AttributeSet. This is called | |
| * as a result of encountering an HTML style attribute. | |
| * | |
| * @param decl - the declaration to get | |
| * @return the AttributeSet representing the declaration | |
| */ | |
| public AttributeSet getDeclaration(String decl) | |
| { | |
| if (decl == null) | |
| return SimpleAttributeSet.EMPTY; | |
| // FIXME: Not implemented. | |
| return null; | |
| } | |
| /** | |
| * Loads a set of rules that have been specified in terms of CSS grammar. | |
| * If there are any conflicts with existing rules, the new rule is added. | |
| * | |
| * @param in - the stream to read the CSS grammar from. | |
| * @param ref - the reference URL. It is the location of the stream, it may | |
| * be null. All relative URLs specified in the stream will be based upon this | |
| * parameter. | |
| * @throws IOException - For any IO error while reading | |
| */ | |
| public void loadRules(Reader in, URL ref) | |
| throws IOException | |
| { | |
| CSSStyleSheetParserCallback cb = | |
| new CSSStyleSheetParserCallback(CSSStyle.PREC_UA); | |
| // FIXME: Handle ref. | |
| CSSParser parser = new CSSParser(in, cb); | |
| parser.parse(); | |
| } | |
| /** | |
| * Gets a set of attributes to use in the view. This is a set of | |
| * attributes that can be used for View.getAttributes | |
| * | |
| * @param v - the view to get the set for | |
| * @return the AttributeSet to use in the view. | |
| */ | |
| public AttributeSet getViewAttributes(View v) | |
| { | |
| return new ViewAttributeSet(v, this); | |
| } | |
| /** | |
| * Removes a style previously added. | |
| * | |
| * @param nm - the name of the style to remove | |
| */ | |
| public void removeStyle(String nm) | |
| { | |
| // FIXME: Not implemented. | |
| super.removeStyle(nm); | |
| } | |
| /** | |
| * Adds the rules from ss to those of the receiver. ss's rules will | |
| * override the old rules. An added StyleSheet will never override the rules | |
| * of the receiving style sheet. | |
| * | |
| * @param ss - the new StyleSheet. | |
| */ | |
| public void addStyleSheet(StyleSheet ss) | |
| { | |
| if (linked == null) | |
| linked = new ArrayList<StyleSheet>(); | |
| linked.add(ss); | |
| } | |
| /** | |
| * Removes ss from those of the receiver | |
| * | |
| * @param ss - the StyleSheet to remove. | |
| */ | |
| public void removeStyleSheet(StyleSheet ss) | |
| { | |
| if (linked != null) | |
| { | |
| linked.remove(ss); | |
| } | |
| } | |
| /** | |
| * Returns an array of the linked StyleSheets. May return null. | |
| * | |
| * @return - An array of the linked StyleSheets. | |
| */ | |
| public StyleSheet[] getStyleSheets() | |
| { | |
| StyleSheet[] linkedSS; | |
| if (linked != null) | |
| { | |
| linkedSS = new StyleSheet[linked.size()]; | |
| linkedSS = linked.toArray(linkedSS); | |
| } | |
| else | |
| { | |
| linkedSS = null; | |
| } | |
| return linkedSS; | |
| } | |
| /** | |
| * Imports a style sheet from the url. The rules are directly added to the | |
| * receiver. This is usually called when a <link> tag is resolved in an | |
| * HTML document. | |
| * | |
| * @param url the URL to import the StyleSheet from | |
| */ | |
| public void importStyleSheet(URL url) | |
| { | |
| try | |
| { | |
| InputStream in = url.openStream(); | |
| Reader r = new BufferedReader(new InputStreamReader(in)); | |
| CSSStyleSheetParserCallback cb = | |
| new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL); | |
| CSSParser parser = new CSSParser(r, cb); | |
| parser.parse(); | |
| } | |
| catch (IOException ex) | |
| { | |
| // We can't do anything about it I guess. | |
| } | |
| } | |
| /** | |
| * Sets the base url. All import statements that are relative, will be | |
| * relative to base. | |
| * | |
| * @param base - | |
| * the base URL. | |
| */ | |
| public void setBase(URL base) | |
| { | |
| this.base = base; | |
| } | |
| /** | |
| * Gets the base url. | |
| * | |
| * @return - the base | |
| */ | |
| public URL getBase() | |
| { | |
| return base; | |
| } | |
| /** | |
| * Adds a CSS attribute to the given set. | |
| * | |
| * @param attr - the attribute set | |
| * @param key - the attribute to add | |
| * @param value - the value of the key | |
| */ | |
| public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key, | |
| String value) | |
| { | |
| Object val = CSS.getValue(key, value); | |
| CSS.addInternal(attr, key, value); | |
| attr.addAttribute(key, val); | |
| } | |
| /** | |
| * Adds a CSS attribute to the given set. | |
| * This method parses the value argument from HTML based on key. | |
| * Returns true if it finds a valid value for the given key, | |
| * and false otherwise. | |
| * | |
| * @param attr - the attribute set | |
| * @param key - the attribute to add | |
| * @param value - the value of the key | |
| * @return true if a valid value was found. | |
| */ | |
| public boolean addCSSAttributeFromHTML(MutableAttributeSet attr, CSS.Attribute key, | |
| String value) | |
| { | |
| // FIXME: Need to parse value from HTML based on key. | |
| attr.addAttribute(key, value); | |
| return attr.containsAttribute(key, value); | |
| } | |
| /** | |
| * Converts a set of HTML attributes to an equivalent set of CSS attributes. | |
| * | |
| * @param htmlAttrSet - the set containing the HTML attributes. | |
| * @return the set of CSS attributes | |
| */ | |
| public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) | |
| { | |
| AttributeSet cssAttr = htmlAttrSet.copyAttributes(); | |
| // The HTML align attribute maps directly to the CSS text-align attribute. | |
| Object o = htmlAttrSet.getAttribute(HTML.Attribute.ALIGN); | |
| if (o != null) | |
| cssAttr = addAttribute(cssAttr, CSS.Attribute.TEXT_ALIGN, o); | |
| // The HTML width attribute maps directly to CSS width. | |
| o = htmlAttrSet.getAttribute(HTML.Attribute.WIDTH); | |
| if (o != null) | |
| cssAttr = addAttribute(cssAttr, CSS.Attribute.WIDTH, | |
| new Length(o.toString())); | |
| // The HTML height attribute maps directly to CSS height. | |
| o = htmlAttrSet.getAttribute(HTML.Attribute.HEIGHT); | |
| if (o != null) | |
| cssAttr = addAttribute(cssAttr, CSS.Attribute.HEIGHT, | |
| new Length(o.toString())); | |
| o = htmlAttrSet.getAttribute(HTML.Attribute.NOWRAP); | |
| if (o != null) | |
| cssAttr = addAttribute(cssAttr, CSS.Attribute.WHITE_SPACE, "nowrap"); | |
| // Map cellspacing attr of tables to CSS border-spacing. | |
| o = htmlAttrSet.getAttribute(HTML.Attribute.CELLSPACING); | |
| if (o != null) | |
| cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_SPACING, | |
| new Length(o.toString())); | |
| // For table cells and headers, fetch the cellpadding value from the | |
| // parent table and set it as CSS padding attribute. | |
| HTML.Tag tag = (HTML.Tag) | |
| htmlAttrSet.getAttribute(StyleConstants.NameAttribute); | |
| if ((tag == HTML.Tag.TD || tag == HTML.Tag.TH) | |
| && htmlAttrSet instanceof Element) | |
| { | |
| Element el = (Element) htmlAttrSet; | |
| AttributeSet tableAttrs = el.getParentElement().getParentElement() | |
| .getAttributes(); | |
| o = tableAttrs.getAttribute(HTML.Attribute.CELLPADDING); | |
| if (o != null) | |
| { | |
| Length l = new Length(o.toString()); | |
| cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_BOTTOM, l); | |
| cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_LEFT, l); | |
| cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_RIGHT, l); | |
| cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_TOP, l); | |
| } | |
| o = tableAttrs.getAttribute(HTML.Attribute.BORDER); | |
| cssAttr = translateBorder(cssAttr, o); | |
| } | |
| // Translate border attribute. | |
| o = cssAttr.getAttribute(HTML.Attribute.BORDER); | |
| cssAttr = translateBorder(cssAttr, o); | |
| // TODO: Add more mappings. | |
| return cssAttr; | |
| } | |
| /** | |
| * Translates a HTML border attribute to a corresponding set of CSS | |
| * attributes. | |
| * | |
| * @param cssAttr the original set of CSS attributes to add to | |
| * @param o the value of the border attribute | |
| * | |
| * @return the new set of CSS attributes | |
| */ | |
| private AttributeSet translateBorder(AttributeSet cssAttr, Object o) | |
| { | |
| if (o != null) | |
| { | |
| BorderWidth l = new BorderWidth(o.toString()); | |
| if (l.getValue() > 0) | |
| { | |
| cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_WIDTH, l); | |
| cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_STYLE, | |
| "solid"); | |
| cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_COLOR, | |
| new CSSColor("black")); | |
| } | |
| } | |
| return cssAttr; | |
| } | |
| /** | |
| * Adds an attribute to the given set and returns a new set. This is implemented | |
| * to convert StyleConstants attributes to CSS before forwarding them to the superclass. | |
| * The StyleConstants attribute do not have corresponding CSS entry, the attribute | |
| * is stored (but will likely not be used). | |
| * | |
| * @param old - the old set | |
| * @param key - the non-null attribute key | |
| * @param value - the attribute value | |
| * @return the updated set | |
| */ | |
| public AttributeSet addAttribute(AttributeSet old, Object key, | |
| Object value) | |
| { | |
| // FIXME: Not implemented. | |
| return super.addAttribute(old, key, value); | |
| } | |
| /** | |
| * Adds a set of attributes to the element. If any of these attributes are | |
| * StyleConstants, they will be converted to CSS before forwarding to the | |
| * superclass. | |
| * | |
| * @param old - the old set | |
| * @param attr - the attributes to add | |
| * @return the updated attribute set | |
| */ | |
| public AttributeSet addAttributes(AttributeSet old, AttributeSet attr) | |
| { | |
| // FIXME: Not implemented. | |
| return super.addAttributes(old, attr); | |
| } | |
| /** | |
| * Removes an attribute from the set. If the attribute is a | |
| * StyleConstants, it will be converted to CSS before forwarding to the | |
| * superclass. | |
| * | |
| * @param old - the old set | |
| * @param key - the non-null attribute key | |
| * @return the updated set | |
| */ | |
| public AttributeSet removeAttribute(AttributeSet old, Object key) | |
| { | |
| // FIXME: Not implemented. | |
| return super.removeAttribute(old, key); | |
| } | |
| /** | |
| * Removes an attribute from the set. If any of the attributes are | |
| * StyleConstants, they will be converted to CSS before forwarding to the | |
| * superclass. | |
| * | |
| * @param old - the old set | |
| * @param attrs - the attributes to remove | |
| * @return the updated set | |
| */ | |
| public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs) | |
| { | |
| // FIXME: Not implemented. | |
| return super.removeAttributes(old, attrs); | |
| } | |
| /** | |
| * Removes a set of attributes for the element. If any of the attributes is a | |
| * StyleConstants, they will be converted to CSS before forwarding to the | |
| * superclass. | |
| * | |
| * @param old - the old attribute set | |
| * @param names - the attribute names | |
| * @return the update attribute set | |
| */ | |
| public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) | |
| { | |
| // FIXME: Not implemented. | |
| return super.removeAttributes(old, names); | |
| } | |
| /** | |
| * Creates a compact set of attributes that might be shared. This is a hook | |
| * for subclasses that want to change the behaviour of SmallAttributeSet. | |
| * | |
| * @param a - the set of attributes to be represented in the compact form. | |
| * @return the set of attributes created | |
| */ | |
| protected StyleContext.SmallAttributeSet createSmallAttributeSet(AttributeSet a) | |
| { | |
| return super.createSmallAttributeSet(a); | |
| } | |
| /** | |
| * Creates a large set of attributes. This set is not shared. This is a hook | |
| * for subclasses that want to change the behaviour of the larger attribute | |
| * storage format. | |
| * | |
| * @param a - the set of attributes to be represented in the larger form. | |
| * @return the large set of attributes. | |
| */ | |
| protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) | |
| { | |
| return super.createLargeAttributeSet(a); | |
| } | |
| /** | |
| * Gets the font to use for the given set. | |
| * | |
| * @param a - the set to get the font for. | |
| * @return the font for the set | |
| */ | |
| public Font getFont(AttributeSet a) | |
| { | |
| int realSize = getFontSize(a); | |
| // Decrement size for subscript and superscript. | |
| Object valign = a.getAttribute(CSS.Attribute.VERTICAL_ALIGN); | |
| if (valign != null) | |
| { | |
| String v = valign.toString(); | |
| if (v.contains("sup") || v.contains("sub")) | |
| realSize -= 2; | |
| } | |
| // TODO: Convert font family. | |
| String family = "SansSerif"; | |
| int style = Font.PLAIN; | |
| FontWeight weight = (FontWeight) a.getAttribute(CSS.Attribute.FONT_WEIGHT); | |
| if (weight != null) | |
| style |= weight.getValue(); | |
| FontStyle fStyle = (FontStyle) a.getAttribute(CSS.Attribute.FONT_STYLE); | |
| if (fStyle != null) | |
| style |= fStyle.getValue(); | |
| return new Font(family, style, realSize); | |
| } | |
| /** | |
| * Determines the EM base value based on the specified attributes. | |
| * | |
| * @param atts the attibutes | |
| * | |
| * @return the EM base value | |
| */ | |
| float getEMBase(AttributeSet atts) | |
| { | |
| Font font = getFont(atts); | |
| FontRenderContext ctx = new FontRenderContext(null, false, false); | |
| Rectangle2D bounds = font.getStringBounds("M", ctx); | |
| return (float) bounds.getWidth(); | |
| } | |
| /** | |
| * Determines the EX base value based on the specified attributes. | |
| * | |
| * @param atts the attibutes | |
| * | |
| * @return the EX base value | |
| */ | |
| float getEXBase(AttributeSet atts) | |
| { | |
| Font font = getFont(atts); | |
| FontRenderContext ctx = new FontRenderContext(null, false, false); | |
| Rectangle2D bounds = font.getStringBounds("x", ctx); | |
| return (float) bounds.getHeight(); | |
| } | |
| /** | |
| * Resolves the fontsize for a given set of attributes. | |
| * | |
| * @param atts the attributes | |
| * | |
| * @return the resolved font size | |
| */ | |
| private int getFontSize(AttributeSet atts) | |
| { | |
| int size = 12; | |
| if (atts.isDefined(CSS.Attribute.FONT_SIZE)) | |
| { | |
| FontSize fs = (FontSize) atts.getAttribute(CSS.Attribute.FONT_SIZE); | |
| if (fs.isRelative()) | |
| { | |
| int parSize = 12; | |
| AttributeSet resolver = atts.getResolveParent(); | |
| if (resolver != null) | |
| parSize = getFontSize(resolver); | |
| size = fs.getValue(parSize); | |
| } | |
| else | |
| { | |
| size = fs.getValue(); | |
| } | |
| } | |
| else | |
| { | |
| AttributeSet resolver = atts.getResolveParent(); | |
| if (resolver != null) | |
| size = getFontSize(resolver); | |
| } | |
| return size; | |
| } | |
| /** | |
| * Takes a set of attributes and turns it into a foreground | |
| * color specification. This is used to specify things like, brigher, more hue | |
| * etc. | |
| * | |
| * @param a - the set to get the foreground color for | |
| * @return the foreground color for the set | |
| */ | |
| public Color getForeground(AttributeSet a) | |
| { | |
| CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.COLOR); | |
| Color color = null; | |
| if (c != null) | |
| color = c.getValue(); | |
| return color; | |
| } | |
| /** | |
| * Takes a set of attributes and turns it into a background | |
| * color specification. This is used to specify things like, brigher, more hue | |
| * etc. | |
| * | |
| * @param a - the set to get the background color for | |
| * @return the background color for the set | |
| */ | |
| public Color getBackground(AttributeSet a) | |
| { | |
| CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.BACKGROUND_COLOR); | |
| Color color = null; | |
| if (c != null) | |
| color = c.getValue(); | |
| return color; | |
| } | |
| /** | |
| * Gets the box formatter to use for the given set of CSS attributes. | |
| * | |
| * @param a - the given set | |
| * @return the box formatter | |
| */ | |
| public BoxPainter getBoxPainter(AttributeSet a) | |
| { | |
| return new BoxPainter(a, this); | |
| } | |
| /** | |
| * Gets the list formatter to use for the given set of CSS attributes. | |
| * | |
| * @param a - the given set | |
| * @return the list formatter | |
| */ | |
| public ListPainter getListPainter(AttributeSet a) | |
| { | |
| return new ListPainter(a, this); | |
| } | |
| /** | |
| * Sets the base font size between 1 and 7. | |
| * | |
| * @param sz - the new font size for the base. | |
| */ | |
| public void setBaseFontSize(int sz) | |
| { | |
| if (sz <= 7 && sz >= 1) | |
| baseFontSize = sz; | |
| } | |
| /** | |
| * Sets the base font size from the String. It can either identify | |
| * a specific font size (between 1 and 7) or identify a relative | |
| * font size such as +1 or -2. | |
| * | |
| * @param size - the new font size as a String. | |
| */ | |
| public void setBaseFontSize(String size) | |
| { | |
| size = size.trim(); | |
| int temp = 0; | |
| try | |
| { | |
| if (size.length() == 2) | |
| { | |
| int i = new Integer(size.substring(1)).intValue(); | |
| if (size.startsWith("+")) | |
| temp = baseFontSize + i; | |
| else if (size.startsWith("-")) | |
| temp = baseFontSize - i; | |
| } | |
| else if (size.length() == 1) | |
| temp = new Integer(size.substring(0)).intValue(); | |
| if (temp <= 7 && temp >= 1) | |
| baseFontSize = temp; | |
| } | |
| catch (NumberFormatException nfe) | |
| { | |
| // Do nothing here | |
| } | |
| } | |
| /** | |
| * TODO | |
| * | |
| * @param pt - TODO | |
| * @return TODO | |
| */ | |
| public static int getIndexOfSize(float pt) | |
| { | |
| // FIXME: Not implemented. | |
| return 0; | |
| } | |
| /** | |
| * Gets the point size, given a size index. | |
| * | |
| * @param index - the size index | |
| * @return the point size. | |
| */ | |
| public float getPointSize(int index) | |
| { | |
| // FIXME: Not implemented. | |
| return 0; | |
| } | |
| /** | |
| * Given the string of the size, returns the point size value. | |
| * | |
| * @param size - the string representation of the size. | |
| * @return - the point size value. | |
| */ | |
| public float getPointSize(String size) | |
| { | |
| // FIXME: Not implemented. | |
| return 0; | |
| } | |
| /** | |
| * Convert the color string represenation into java.awt.Color. The valid | |
| * values are like "aqua" , "#00FFFF" or "rgb(1,6,44)". | |
| * | |
| * @param colorName the color to convert. | |
| * @return the matching java.awt.color | |
| */ | |
| public Color stringToColor(String colorName) | |
| { | |
| return CSSColor.convertValue(colorName); | |
| } | |
| /** | |
| * This class carries out some of the duties of CSS formatting. This enables views | |
| * to present the CSS formatting while not knowing how the CSS values are cached. | |
| * | |
| * This object is reponsible for the insets of a View and making sure | |
| * the background is maintained according to the CSS attributes. | |
| * | |
| * @author Lillian Angel (langel@redhat.com) | |
| */ | |
| public static class BoxPainter extends Object implements Serializable | |
| { | |
| /** | |
| * The left inset. | |
| */ | |
| private float leftInset; | |
| /** | |
| * The right inset. | |
| */ | |
| private float rightInset; | |
| /** | |
| * The top inset. | |
| */ | |
| private float topInset; | |
| /** | |
| * The bottom inset. | |
| */ | |
| private float bottomInset; | |
| /** | |
| * The border of the box. | |
| */ | |
| private Border border; | |
| private float leftPadding; | |
| private float rightPadding; | |
| private float topPadding; | |
| private float bottomPadding; | |
| /** | |
| * The background color. | |
| */ | |
| private Color background; | |
| /** | |
| * Package-private constructor. | |
| * | |
| * @param as - AttributeSet for painter | |
| */ | |
| BoxPainter(AttributeSet as, StyleSheet ss) | |
| { | |
| float emBase = ss.getEMBase(as); | |
| float exBase = ss.getEXBase(as); | |
| // Fetch margins. | |
| Length l = (Length) as.getAttribute(CSS.Attribute.MARGIN_LEFT); | |
| if (l != null) | |
| { | |
| l.setFontBases(emBase, exBase); | |
| leftInset = l.getValue(); | |
| } | |
| l = (Length) as.getAttribute(CSS.Attribute.MARGIN_RIGHT); | |
| if (l != null) | |
| { | |
| l.setFontBases(emBase, exBase); | |
| rightInset = l.getValue(); | |
| } | |
| l = (Length) as.getAttribute(CSS.Attribute.MARGIN_TOP); | |
| if (l != null) | |
| { | |
| l.setFontBases(emBase, exBase); | |
| topInset = l.getValue(); | |
| } | |
| l = (Length) as.getAttribute(CSS.Attribute.MARGIN_BOTTOM); | |
| if (l != null) | |
| { | |
| l.setFontBases(emBase, exBase); | |
| bottomInset = l.getValue(); | |
| } | |
| // Fetch padding. | |
| l = (Length) as.getAttribute(CSS.Attribute.PADDING_LEFT); | |
| if (l != null) | |
| { | |
| l.setFontBases(emBase, exBase); | |
| leftPadding = l.getValue(); | |
| } | |
| l = (Length) as.getAttribute(CSS.Attribute.PADDING_RIGHT); | |
| if (l != null) | |
| { | |
| l.setFontBases(emBase, exBase); | |
| rightPadding = l.getValue(); | |
| } | |
| l = (Length) as.getAttribute(CSS.Attribute.PADDING_TOP); | |
| if (l != null) | |
| { | |
| l.setFontBases(emBase, exBase); | |
| topPadding = l.getValue(); | |
| } | |
| l = (Length) as.getAttribute(CSS.Attribute.PADDING_BOTTOM); | |
| if (l != null) | |
| { | |
| l.setFontBases(emBase, exBase); | |
| bottomPadding = l.getValue(); | |
| } | |
| // Determine border. | |
| border = new CSSBorder(as, ss); | |
| // Determine background. | |
| background = ss.getBackground(as); | |
| } | |
| /** | |
| * Gets the inset needed on a given side to account for the margin, border | |
| * and padding. | |
| * | |
| * @param size - the size of the box to get the inset for. View.TOP, View.LEFT, | |
| * View.BOTTOM or View.RIGHT. | |
| * @param v - the view making the request. This is used to get the AttributeSet, | |
| * amd may be used to resolve percentage arguments. | |
| * @return the inset | |
| * @throws IllegalArgumentException - for an invalid direction. | |
| */ | |
| public float getInset(int size, View v) | |
| { | |
| float inset; | |
| switch (size) | |
| { | |
| case View.TOP: | |
| inset = topInset; | |
| if (border != null) | |
| inset += border.getBorderInsets(null).top; | |
| inset += topPadding; | |
| break; | |
| case View.BOTTOM: | |
| inset = bottomInset; | |
| if (border != null) | |
| inset += border.getBorderInsets(null).bottom; | |
| inset += bottomPadding; | |
| break; | |
| case View.LEFT: | |
| inset = leftInset; | |
| if (border != null) | |
| inset += border.getBorderInsets(null).left; | |
| inset += leftPadding; | |
| break; | |
| case View.RIGHT: | |
| inset = rightInset; | |
| if (border != null) | |
| inset += border.getBorderInsets(null).right; | |
| inset += rightPadding; | |
| break; | |
| default: | |
| inset = 0.0F; | |
| } | |
| return inset; | |
| } | |
| /** | |
| * Paints the CSS box according to the attributes given. This should | |
| * paint the border, padding and background. | |
| * | |
| * @param g - the graphics configuration | |
| * @param x - the x coordinate | |
| * @param y - the y coordinate | |
| * @param w - the width of the allocated area | |
| * @param h - the height of the allocated area | |
| * @param v - the view making the request | |
| */ | |
| public void paint(Graphics g, float x, float y, float w, float h, View v) | |
| { | |
| int inX = (int) (x + leftInset); | |
| int inY = (int) (y + topInset); | |
| int inW = (int) (w - leftInset - rightInset); | |
| int inH = (int) (h - topInset - bottomInset); | |
| if (background != null) | |
| { | |
| g.setColor(background); | |
| g.fillRect(inX, inY, inW, inH); | |
| } | |
| if (border != null) | |
| { | |
| border.paintBorder(null, g, inX, inY, inW, inH); | |
| } | |
| } | |
| } | |
| /** | |
| * This class carries out some of the CSS list formatting duties. Implementations | |
| * of this class enable views to present the CSS formatting while not knowing anything | |
| * about how the CSS values are being cached. | |
| * | |
| * @author Lillian Angel (langel@redhat.com) | |
| */ | |
| public static class ListPainter implements Serializable | |
| { | |
| /** | |
| * Attribute set for painter | |
| */ | |
| private AttributeSet attributes; | |
| /** | |
| * The associated style sheet. | |
| */ | |
| private StyleSheet styleSheet; | |
| /** | |
| * The bullet type. | |
| */ | |
| private String type; | |
| /** | |
| * Package-private constructor. | |
| * | |
| * @param as - AttributeSet for painter | |
| */ | |
| ListPainter(AttributeSet as, StyleSheet ss) | |
| { | |
| attributes = as; | |
| styleSheet = ss; | |
| type = (String) as.getAttribute(CSS.Attribute.LIST_STYLE_TYPE); | |
| } | |
| /** | |
| * Cached rectangle re-used in the paint method below. | |
| */ | |
| private final Rectangle tmpRect = new Rectangle(); | |
| /** | |
| * Paints the CSS list decoration according to the attributes given. | |
| * | |
| * @param g - the graphics configuration | |
| * @param x - the x coordinate | |
| * @param y - the y coordinate | |
| * @param w - the width of the allocated area | |
| * @param h - the height of the allocated area | |
| * @param v - the view making the request | |
| * @param item - the list item to be painted >=0. | |
| */ | |
| public void paint(Graphics g, float x, float y, float w, float h, View v, | |
| int item) | |
| { | |
| // FIXME: This is a very simplistic list rendering. We still need | |
| // to implement different bullet types (see type field) and custom | |
| // bullets via images. | |
| View itemView = v.getView(item); | |
| AttributeSet viewAtts = itemView.getAttributes(); | |
| Object tag = viewAtts.getAttribute(StyleConstants.NameAttribute); | |
| // Only paint something here when the child view is an LI tag | |
| // and the calling view is some of the list tags then). | |
| if (tag != null && tag == HTML.Tag.LI) | |
| { | |
| g.setColor(Color.BLACK); | |
| int centerX = (int) (x - 12); | |
| int centerY = -1; | |
| // For paragraphs (almost all cases) center bullet vertically | |
| // in the middle of the first line. | |
| tmpRect.setBounds((int) x, (int) y, (int) w, (int) h); | |
| if (itemView.getViewCount() > 0) | |
| { | |
| View v1 = itemView.getView(0); | |
| if (v1 instanceof ParagraphView && v1.getViewCount() > 0) | |
| { | |
| Shape a1 = itemView.getChildAllocation(0, tmpRect); | |
| Rectangle r1 = a1 instanceof Rectangle ? (Rectangle) a1 | |
| : a1.getBounds(); | |
| ParagraphView par = (ParagraphView) v1; | |
| Shape a = par.getChildAllocation(0, r1); | |
| if (a != null) | |
| { | |
| Rectangle r = a instanceof Rectangle ? (Rectangle) a | |
| : a.getBounds(); | |
| centerY = (int) (r.height / 2 + r.y); | |
| } | |
| } | |
| } | |
| if (centerY == -1) | |
| { | |
| centerY =(int) (h / 2 + y); | |
| } | |
| g.fillOval(centerX - 3, centerY - 3, 6, 6); | |
| } | |
| } | |
| } | |
| /** | |
| * Converts an AttributeSet to a Map. This is used for CSS resolving. | |
| * | |
| * @param atts the attributes to convert | |
| * | |
| * @return the converted map | |
| */ | |
| private Map<String,String> attributeSetToMap(AttributeSet atts) | |
| { | |
| HashMap<String,String> map = new HashMap<String,String>(); | |
| Enumeration<?> keys = atts.getAttributeNames(); | |
| while (keys.hasMoreElements()) | |
| { | |
| Object key = keys.nextElement(); | |
| Object value = atts.getAttribute(key); | |
| map.put(key.toString(), value.toString()); | |
| } | |
| return map; | |
| } | |
| } |