0001 /*
0002 * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025 package javax.swing.text.html;
0026
0027 import sun.swing.SwingUtilities2;
0028 import java.util.*;
0029 import java.awt.*;
0030 import java.io.*;
0031 import java.net.*;
0032 import javax.swing.Icon;
0033 import javax.swing.ImageIcon;
0034 import javax.swing.border.*;
0035 import javax.swing.event.ChangeListener;
0036 import javax.swing.text.*;
0037
0038 /**
0039 * Support for defining the visual characteristics of
0040 * HTML views being rendered. The StyleSheet is used to
0041 * translate the HTML model into visual characteristics.
0042 * This enables views to be customized by a look-and-feel,
0043 * multiple views over the same model can be rendered
0044 * differently, etc. This can be thought of as a CSS
0045 * rule repository. The key for CSS attributes is an
0046 * object of type CSS.Attribute. The type of the value
0047 * is up to the StyleSheet implementation, but the
0048 * <code>toString</code> method is required
0049 * to return a string representation of CSS value.
0050 * <p>
0051 * The primary entry point for HTML View implementations
0052 * to get their attributes is the
0053 * <a href="#getViewAttributes">getViewAttributes</a>
0054 * method. This should be implemented to establish the
0055 * desired policy used to associate attributes with the view.
0056 * Each HTMLEditorKit (i.e. and therefore each associated
0057 * JEditorPane) can have its own StyleSheet, but by default one
0058 * sheet will be shared by all of the HTMLEditorKit instances.
0059 * HTMLDocument instance can also have a StyleSheet, which
0060 * holds the document-specific CSS specifications.
0061 * <p>
0062 * In order for Views to store less state and therefore be
0063 * more lightweight, the StyleSheet can act as a factory for
0064 * painters that handle some of the rendering tasks. This allows
0065 * implementations to determine what they want to cache
0066 * and have the sharing potentially at the level that a
0067 * selector is common to multiple views. Since the StyleSheet
0068 * may be used by views over multiple documents and typically
0069 * the HTML attributes don't effect the selector being used,
0070 * the potential for sharing is significant.
0071 * <p>
0072 * The rules are stored as named styles, and other information
0073 * is stored to translate the context of an element to a
0074 * rule quickly. The following code fragment will display
0075 * the named styles, and therefore the CSS rules contained.
0076 * <code><pre>
0077 *
0078 * import java.util.*;
0079 * import javax.swing.text.*;
0080 * import javax.swing.text.html.*;
0081 *
0082 * public class ShowStyles {
0083 *
0084 * public static void main(String[] args) {
0085 * HTMLEditorKit kit = new HTMLEditorKit();
0086 * HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
0087 * StyleSheet styles = doc.getStyleSheet();
0088 *
0089 * Enumeration rules = styles.getStyleNames();
0090 * while (rules.hasMoreElements()) {
0091 * String name = (String) rules.nextElement();
0092 * Style rule = styles.getStyle(name);
0093 * System.out.println(rule.toString());
0094 * }
0095 * System.exit(0);
0096 * }
0097 * }
0098 *
0099 * </pre></code>
0100 * <p>
0101 * The semantics for when a CSS style should overide visual attributes
0102 * defined by an element are not well defined. For example, the html
0103 * <code><body bgcolor=red></code> makes the body have a red
0104 * background. But if the html file also contains the CSS rule
0105 * <code>body { background: blue }</code> it becomes less clear as to
0106 * what color the background of the body should be. The current
0107 * implemention gives visual attributes defined in the element the
0108 * highest precedence, that is they are always checked before any styles.
0109 * Therefore, in the previous example the background would have a
0110 * red color as the body element defines the background color to be red.
0111 * <p>
0112 * As already mentioned this supports CSS. We don't support the full CSS
0113 * spec. Refer to the javadoc of the CSS class to see what properties
0114 * we support. The two major CSS parsing related
0115 * concepts we do not currently
0116 * support are pseudo selectors, such as <code>A:link { color: red }</code>,
0117 * and the <code>important</code> modifier.
0118 * <p>
0119 * <font color="red">Note: This implementation is currently
0120 * incomplete. It can be replaced with alternative implementations
0121 * that are complete. Future versions of this class will provide
0122 * better CSS support.</font>
0123 *
0124 * @author Timothy Prinzing
0125 * @author Sunita Mani
0126 * @author Sara Swanson
0127 * @author Jill Nakata
0128 * @version 1.97 05/05/07
0129 */
0130 public class StyleSheet extends StyleContext {
0131 // As the javadoc states, this class maintains a mapping between
0132 // a CSS selector (such as p.bar) and a Style.
0133 // This consists of a number of parts:
0134 // . Each selector is broken down into its constituent simple selectors,
0135 // and stored in an inverted graph, for example:
0136 // p { color: red } ol p { font-size: 10pt } ul p { font-size: 12pt }
0137 // results in the graph:
0138 // root
0139 // |
0140 // p
0141 // / \
0142 // ol ul
0143 // each node (an instance of SelectorMapping) has an associated
0144 // specificity and potentially a Style.
0145 // . Every rule that is asked for (either by way of getRule(String) or
0146 // getRule(HTML.Tag, Element)) results in a unique instance of
0147 // ResolvedStyle. ResolvedStyles contain the AttributeSets from the
0148 // SelectorMapping.
0149 // . When a new rule is created it is inserted into the graph, and
0150 // the AttributeSets of each ResolvedStyles are updated appropriately.
0151 // . This class creates special AttributeSets, LargeConversionSet and
0152 // SmallConversionSet, that maintain a mapping between StyleConstants
0153 // and CSS so that developers that wish to use the StyleConstants
0154 // methods can do so.
0155 // . When one of the AttributeSets is mutated by way of a
0156 // StyleConstants key, all the associated CSS keys are removed. This is
0157 // done so that the two representations don't get out of sync. For
0158 // example, if the developer adds StyleConsants.BOLD, FALSE to an
0159 // AttributeSet that contains HTML.Tag.B, the HTML.Tag.B entry will
0160 // be removed.
0161
0162 /**
0163 * Construct a StyleSheet
0164 */
0165 public StyleSheet() {
0166 super ();
0167 selectorMapping = new SelectorMapping(0);
0168 resolvedStyles = new Hashtable();
0169 if (css == null) {
0170 css = new CSS();
0171 }
0172 }
0173
0174 /**
0175 * Fetches the style to use to render the given type
0176 * of HTML tag. The element given is representing
0177 * the tag and can be used to determine the nesting
0178 * for situations where the attributes will differ
0179 * if nesting inside of elements.
0180 *
0181 * @param t the type to translate to visual attributes
0182 * @param e the element representing the tag; the element
0183 * can be used to determine the nesting for situations where
0184 * the attributes will differ if nested inside of other
0185 * elements
0186 * @return the set of CSS attributes to use to render
0187 * the tag
0188 */
0189 public Style getRule(HTML.Tag t, Element e) {
0190 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
0191
0192 try {
0193 // Build an array of all the parent elements.
0194 Vector searchContext = sb.getVector();
0195
0196 for (Element p = e; p != null; p = p.getParentElement()) {
0197 searchContext.addElement(p);
0198 }
0199
0200 // Build a fully qualified selector.
0201 int n = searchContext.size();
0202 StringBuffer cacheLookup = sb.getStringBuffer();
0203 AttributeSet attr;
0204 String eName;
0205 Object name;
0206
0207 // >= 1 as the HTML.Tag for the 0th element is passed in.
0208 for (int counter = n - 1; counter >= 1; counter--) {
0209 e = (Element) searchContext.elementAt(counter);
0210 attr = e.getAttributes();
0211 name = attr.getAttribute(StyleConstants.NameAttribute);
0212 eName = name.toString();
0213 cacheLookup.append(eName);
0214 if (attr != null) {
0215 if (attr.isDefined(HTML.Attribute.ID)) {
0216 cacheLookup.append('#');
0217 cacheLookup.append(attr
0218 .getAttribute(HTML.Attribute.ID));
0219 } else if (attr.isDefined(HTML.Attribute.CLASS)) {
0220 cacheLookup.append('.');
0221 cacheLookup.append(attr
0222 .getAttribute(HTML.Attribute.CLASS));
0223 }
0224 }
0225 cacheLookup.append(' ');
0226 }
0227 cacheLookup.append(t.toString());
0228 e = (Element) searchContext.elementAt(0);
0229 attr = e.getAttributes();
0230 if (e.isLeaf()) {
0231 // For leafs, we use the second tier attributes.
0232 Object testAttr = attr.getAttribute(t);
0233 if (testAttr instanceof AttributeSet) {
0234 attr = (AttributeSet) testAttr;
0235 } else {
0236 attr = null;
0237 }
0238 }
0239 if (attr != null) {
0240 if (attr.isDefined(HTML.Attribute.ID)) {
0241 cacheLookup.append('#');
0242 cacheLookup.append(attr
0243 .getAttribute(HTML.Attribute.ID));
0244 } else if (attr.isDefined(HTML.Attribute.CLASS)) {
0245 cacheLookup.append('.');
0246 cacheLookup.append(attr
0247 .getAttribute(HTML.Attribute.CLASS));
0248 }
0249 }
0250
0251 Style style = getResolvedStyle(cacheLookup.toString(),
0252 searchContext, t);
0253 return style;
0254 } finally {
0255 SearchBuffer.releaseSearchBuffer(sb);
0256 }
0257 }
0258
0259 /**
0260 * Fetches the rule that best matches the selector given
0261 * in string form. Where <code>selector</code> is a space separated
0262 * String of the element names. For example, <code>selector</code>
0263 * might be 'html body tr td''<p>
0264 * The attributes of the returned Style will change
0265 * as rules are added and removed. That is if you to ask for a rule
0266 * with a selector "table p" and a new rule was added with a selector
0267 * of "p" the returned Style would include the new attributes from
0268 * the rule "p".
0269 */
0270 public Style getRule(String selector) {
0271 selector = cleanSelectorString(selector);
0272 if (selector != null) {
0273 Style style = getResolvedStyle(selector);
0274 return style;
0275 }
0276 return null;
0277 }
0278
0279 /**
0280 * Adds a set of rules to the sheet. The rules are expected to
0281 * be in valid CSS format. Typically this would be called as
0282 * a result of parsing a <style> tag.
0283 */
0284 public void addRule(String rule) {
0285 if (rule != null) {
0286 //tweaks to control display properties
0287 //see BasicEditorPaneUI
0288 final String baseUnitsDisable = "BASE_SIZE_DISABLE";
0289 final String baseUnits = "BASE_SIZE ";
0290 final String w3cLengthUnitsEnable = "W3C_LENGTH_UNITS_ENABLE";
0291 final String w3cLengthUnitsDisable = "W3C_LENGTH_UNITS_DISABLE";
0292 if (rule == baseUnitsDisable) {
0293 sizeMap = sizeMapDefault;
0294 } else if (rule.startsWith(baseUnits)) {
0295 rebaseSizeMap(Integer.parseInt(rule.substring(baseUnits
0296 .length())));
0297 } else if (rule == w3cLengthUnitsEnable) {
0298 w3cLengthUnits = true;
0299 } else if (rule == w3cLengthUnitsDisable) {
0300 w3cLengthUnits = false;
0301 } else {
0302 CssParser parser = new CssParser();
0303 try {
0304 parser.parse(getBase(), new StringReader(rule),
0305 false, false);
0306 } catch (IOException ioe) {
0307 }
0308 }
0309 }
0310 }
0311
0312 /**
0313 * Translates a CSS declaration to an AttributeSet that represents
0314 * the CSS declaration. Typically this would be called as a
0315 * result of encountering an HTML style attribute.
0316 */
0317 public AttributeSet getDeclaration(String decl) {
0318 if (decl == null) {
0319 return SimpleAttributeSet.EMPTY;
0320 }
0321 CssParser parser = new CssParser();
0322 return parser.parseDeclaration(decl);
0323 }
0324
0325 /**
0326 * Loads a set of rules that have been specified in terms of
0327 * CSS1 grammar. If there are collisions with existing rules,
0328 * the newly specified rule will win.
0329 *
0330 * @param in the stream to read the CSS grammar from
0331 * @param ref the reference URL. This value represents the
0332 * location of the stream and may be null. All relative
0333 * URLs specified in the stream will be based upon this
0334 * parameter.
0335 */
0336 public void loadRules(Reader in, URL ref) throws IOException {
0337 CssParser parser = new CssParser();
0338 parser.parse(ref, in, false, false);
0339 }
0340
0341 /**
0342 * Fetches a set of attributes to use in the view for
0343 * displaying. This is basically a set of attributes that
0344 * can be used for View.getAttributes.
0345 */
0346 public AttributeSet getViewAttributes(View v) {
0347 return new ViewAttributeSet(v);
0348 }
0349
0350 /**
0351 * Removes a named style previously added to the document.
0352 *
0353 * @param nm the name of the style to remove
0354 */
0355 public void removeStyle(String nm) {
0356 Style aStyle = getStyle(nm);
0357
0358 if (aStyle != null) {
0359 String selector = cleanSelectorString(nm);
0360 String[] selectors = getSimpleSelectors(selector);
0361 synchronized (this ) {
0362 SelectorMapping mapping = getRootSelectorMapping();
0363 for (int i = selectors.length - 1; i >= 0; i--) {
0364 mapping = mapping.getChildSelectorMapping(
0365 selectors[i], true);
0366 }
0367 Style rule = mapping.getStyle();
0368 if (rule != null) {
0369 mapping.setStyle(null);
0370 if (resolvedStyles.size() > 0) {
0371 Enumeration values = resolvedStyles.elements();
0372 while (values.hasMoreElements()) {
0373 ResolvedStyle style = (ResolvedStyle) values
0374 .nextElement();
0375 style.removeStyle(rule);
0376 }
0377 }
0378 }
0379 }
0380 }
0381 super .removeStyle(nm);
0382 }
0383
0384 /**
0385 * Adds the rules from the StyleSheet <code>ss</code> to those of
0386 * the receiver. <code>ss's</code> rules will override the rules of
0387 * any previously added style sheets. An added StyleSheet will never
0388 * override the rules of the receiving style sheet.
0389 *
0390 * @since 1.3
0391 */
0392 public void addStyleSheet(StyleSheet ss) {
0393 synchronized (this ) {
0394 if (linkedStyleSheets == null) {
0395 linkedStyleSheets = new Vector();
0396 }
0397 if (!linkedStyleSheets.contains(ss)) {
0398 int index = 0;
0399 if (ss instanceof javax.swing.plaf.UIResource
0400 && linkedStyleSheets.size() > 1) {
0401 index = linkedStyleSheets.size() - 1;
0402 }
0403 linkedStyleSheets.insertElementAt(ss, index);
0404 linkStyleSheetAt(ss, index);
0405 }
0406 }
0407 }
0408
0409 /**
0410 * Removes the StyleSheet <code>ss</code> from those of the receiver.
0411 *
0412 * @since 1.3
0413 */
0414 public void removeStyleSheet(StyleSheet ss) {
0415 synchronized (this ) {
0416 if (linkedStyleSheets != null) {
0417 int index = linkedStyleSheets.indexOf(ss);
0418 if (index != -1) {
0419 linkedStyleSheets.removeElementAt(index);
0420 unlinkStyleSheet(ss, index);
0421 if (index == 0 && linkedStyleSheets.size() == 0) {
0422 linkedStyleSheets = null;
0423 }
0424 }
0425 }
0426 }
0427 }
0428
0429 //
0430 // The following is used to import style sheets.
0431 //
0432
0433 /**
0434 * Returns an array of the linked StyleSheets. Will return null
0435 * if there are no linked StyleSheets.
0436 *
0437 * @since 1.3
0438 */
0439 public StyleSheet[] getStyleSheets() {
0440 StyleSheet[] retValue;
0441
0442 synchronized (this ) {
0443 if (linkedStyleSheets != null) {
0444 retValue = new StyleSheet[linkedStyleSheets.size()];
0445 linkedStyleSheets.copyInto(retValue);
0446 } else {
0447 retValue = null;
0448 }
0449 }
0450 return retValue;
0451 }
0452
0453 /**
0454 * Imports a style sheet from <code>url</code>. The resulting rules
0455 * are directly added to the receiver. If you do not want the rules
0456 * to become part of the receiver, create a new StyleSheet and use
0457 * addStyleSheet to link it in.
0458 *
0459 * @since 1.3
0460 */
0461 public void importStyleSheet(URL url) {
0462 try {
0463 InputStream is;
0464
0465 is = url.openStream();
0466 Reader r = new BufferedReader(new InputStreamReader(is));
0467 CssParser parser = new CssParser();
0468 parser.parse(url, r, false, true);
0469 r.close();
0470 is.close();
0471 } catch (Throwable e) {
0472 // on error we simply have no styles... the html
0473 // will look mighty wrong but still function.
0474 }
0475 }
0476
0477 /**
0478 * Sets the base. All import statements that are relative, will be
0479 * relative to <code>base</code>.
0480 *
0481 * @since 1.3
0482 */
0483 public void setBase(URL base) {
0484 this .base = base;
0485 }
0486
0487 /**
0488 * Returns the base.
0489 *
0490 * @since 1.3
0491 */
0492 public URL getBase() {
0493 return base;
0494 }
0495
0496 /**
0497 * Adds a CSS attribute to the given set.
0498 *
0499 * @since 1.3
0500 */
0501 public void addCSSAttribute(MutableAttributeSet attr,
0502 CSS.Attribute key, String value) {
0503 css.addInternalCSSValue(attr, key, value);
0504 }
0505
0506 /**
0507 * Adds a CSS attribute to the given set.
0508 *
0509 * @since 1.3
0510 */
0511 public boolean addCSSAttributeFromHTML(MutableAttributeSet attr,
0512 CSS.Attribute key, String value) {
0513 Object iValue = css.getCssValue(key, value);
0514 if (iValue != null) {
0515 attr.addAttribute(key, iValue);
0516 return true;
0517 }
0518 return false;
0519 }
0520
0521 // ---- Conversion functionality ---------------------------------
0522
0523 /**
0524 * Converts a set of HTML attributes to an equivalent
0525 * set of CSS attributes.
0526 *
0527 * @param htmlAttrSet AttributeSet containing the HTML attributes.
0528 */
0529 public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) {
0530 AttributeSet cssAttrSet = css.translateHTMLToCSS(htmlAttrSet);
0531
0532 MutableAttributeSet cssStyleSet = addStyle(null, null);
0533 cssStyleSet.addAttributes(cssAttrSet);
0534
0535 return cssStyleSet;
0536 }
0537
0538 /**
0539 * Adds an attribute to the given set, and returns
0540 * the new representative set. This is reimplemented to
0541 * convert StyleConstant attributes to CSS prior to forwarding
0542 * to the superclass behavior. The StyleConstants attribute
0543 * has no corresponding CSS entry, the StyleConstants attribute
0544 * is stored (but will likely be unused).
0545 *
0546 * @param old the old attribute set
0547 * @param key the non-null attribute key
0548 * @param value the attribute value
0549 * @return the updated attribute set
0550 * @see MutableAttributeSet#addAttribute
0551 */
0552 public AttributeSet addAttribute(AttributeSet old, Object key,
0553 Object value) {
0554 if (css == null) {
0555 // supers constructor will call this before returning,
0556 // and we need to make sure CSS is non null.
0557 css = new CSS();
0558 }
0559 if (key instanceof StyleConstants) {
0560 HTML.Tag tag = HTML
0561 .getTagForStyleConstantsKey((StyleConstants) key);
0562
0563 if (tag != null && old.isDefined(tag)) {
0564 old = removeAttribute(old, tag);
0565 }
0566
0567 Object cssValue = css.styleConstantsValueToCSSValue(
0568 (StyleConstants) key, value);
0569 if (cssValue != null) {
0570 Object cssKey = css
0571 .styleConstantsKeyToCSSKey((StyleConstants) key);
0572 if (cssKey != null) {
0573 return super .addAttribute(old, cssKey, cssValue);
0574 }
0575 }
0576 }
0577 return super .addAttribute(old, key, value);
0578 }
0579
0580 /**
0581 * Adds a set of attributes to the element. If any of these attributes
0582 * are StyleConstants attributes, they will be converted to CSS prior
0583 * to forwarding to the superclass behavior.
0584 *
0585 * @param old the old attribute set
0586 * @param attr the attributes to add
0587 * @return the updated attribute set
0588 * @see MutableAttributeSet#addAttribute
0589 */
0590 public AttributeSet addAttributes(AttributeSet old,
0591 AttributeSet attr) {
0592 if (!(attr instanceof HTMLDocument.TaggedAttributeSet)) {
0593 old = removeHTMLTags(old, attr);
0594 }
0595 return super .addAttributes(old, convertAttributeSet(attr));
0596 }
0597
0598 /**
0599 * Removes an attribute from the set. If the attribute is a StyleConstants
0600 * attribute, the request will be converted to a CSS attribute prior to
0601 * forwarding to the superclass behavior.
0602 *
0603 * @param old the old set of attributes
0604 * @param key the non-null attribute name
0605 * @return the updated attribute set
0606 * @see MutableAttributeSet#removeAttribute
0607 */
0608 public AttributeSet removeAttribute(AttributeSet old, Object key) {
0609 if (key instanceof StyleConstants) {
0610 HTML.Tag tag = HTML
0611 .getTagForStyleConstantsKey((StyleConstants) key);
0612 if (tag != null) {
0613 old = super .removeAttribute(old, tag);
0614 }
0615
0616 Object cssKey = css
0617 .styleConstantsKeyToCSSKey((StyleConstants) key);
0618 if (cssKey != null) {
0619 return super .removeAttribute(old, cssKey);
0620 }
0621 }
0622 return super .removeAttribute(old, key);
0623 }
0624
0625 /**
0626 * Removes a set of attributes for the element. If any of the attributes
0627 * is a StyleConstants attribute, the request will be converted to a CSS
0628 * attribute prior to forwarding to the superclass behavior.
0629 *
0630 * @param old the old attribute set
0631 * @param names the attribute names
0632 * @return the updated attribute set
0633 * @see MutableAttributeSet#removeAttributes
0634 */
0635 public AttributeSet removeAttributes(AttributeSet old,
0636 Enumeration<?> names) {
0637 // PENDING: Should really be doing something similar to
0638 // removeHTMLTags here, but it is rather expensive to have to
0639 // clone names
0640 return super .removeAttributes(old, names);
0641 }
0642
0643 /**
0644 * Removes a set of attributes. If any of the attributes
0645 * is a StyleConstants attribute, the request will be converted to a CSS
0646 * attribute prior to forwarding to the superclass behavior.
0647 *
0648 * @param old the old attribute set
0649 * @param attrs the attributes
0650 * @return the updated attribute set
0651 * @see MutableAttributeSet#removeAttributes
0652 */
0653 public AttributeSet removeAttributes(AttributeSet old,
0654 AttributeSet attrs) {
0655 if (old != attrs) {
0656 old = removeHTMLTags(old, attrs);
0657 }
0658 return super .removeAttributes(old, convertAttributeSet(attrs));
0659 }
0660
0661 /**
0662 * Creates a compact set of attributes that might be shared.
0663 * This is a hook for subclasses that want to alter the
0664 * behavior of SmallAttributeSet. This can be reimplemented
0665 * to return an AttributeSet that provides some sort of
0666 * attribute conversion.
0667 *
0668 * @param a The set of attributes to be represented in the
0669 * the compact form.
0670 */
0671 protected SmallAttributeSet createSmallAttributeSet(AttributeSet a) {
0672 return new SmallConversionSet(a);
0673 }
0674
0675 /**
0676 * Creates a large set of attributes that should trade off
0677 * space for time. This set will not be shared. This is
0678 * a hook for subclasses that want to alter the behavior
0679 * of the larger attribute storage format (which is
0680 * SimpleAttributeSet by default). This can be reimplemented
0681 * to return a MutableAttributeSet that provides some sort of
0682 * attribute conversion.
0683 *
0684 * @param a The set of attributes to be represented in the
0685 * the larger form.
0686 */
0687 protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) {
0688 return new LargeConversionSet(a);
0689 }
0690
0691 /**
0692 * For any StyleConstants key in attr that has an associated HTML.Tag,
0693 * it is removed from old. The resulting AttributeSet is then returned.
0694 */
0695 private AttributeSet removeHTMLTags(AttributeSet old,
0696 AttributeSet attr) {
0697 if (!(attr instanceof LargeConversionSet)
0698 && !(attr instanceof SmallConversionSet)) {
0699 Enumeration names = attr.getAttributeNames();
0700
0701 while (names.hasMoreElements()) {
0702 Object key = names.nextElement();
0703
0704 if (key instanceof StyleConstants) {
0705 HTML.Tag tag = HTML
0706 .getTagForStyleConstantsKey((StyleConstants) key);
0707
0708 if (tag != null && old.isDefined(tag)) {
0709 old = super .removeAttribute(old, tag);
0710 }
0711 }
0712 }
0713 }
0714 return old;
0715 }
0716
0717 /**
0718 * Converts a set of attributes (if necessary) so that
0719 * any attributes that were specified as StyleConstants
0720 * attributes and have a CSS mapping, will be converted
0721 * to CSS attributes.
0722 */
0723 AttributeSet convertAttributeSet(AttributeSet a) {
0724 if ((a instanceof LargeConversionSet)
0725 || (a instanceof SmallConversionSet)) {
0726 // known to be converted.
0727 return a;
0728 }
0729 // in most cases, there are no StyleConstants attributes
0730 // so we iterate the collection of keys to avoid creating
0731 // a new set.
0732 Enumeration names = a.getAttributeNames();
0733 while (names.hasMoreElements()) {
0734 Object name = names.nextElement();
0735 if (name instanceof StyleConstants) {
0736 // we really need to do a conversion, iterate again
0737 // building a new set.
0738 MutableAttributeSet converted = new LargeConversionSet();
0739 Enumeration keys = a.getAttributeNames();
0740 while (keys.hasMoreElements()) {
0741 Object key = keys.nextElement();
0742 Object cssValue = null;
0743 if (key instanceof StyleConstants) {
0744 // convert the StyleConstants attribute if possible
0745 Object cssKey = css
0746 .styleConstantsKeyToCSSKey((StyleConstants) key);
0747 if (cssKey != null) {
0748 Object value = a.getAttribute(key);
0749 cssValue = css
0750 .styleConstantsValueToCSSValue(
0751 (StyleConstants) key, value);
0752 if (cssValue != null) {
0753 converted
0754 .addAttribute(cssKey, cssValue);
0755 }
0756 }
0757 }
0758 if (cssValue == null) {
0759 converted
0760 .addAttribute(key, a.getAttribute(key));
0761 }
0762 }
0763 return converted;
0764 }
0765 }
0766 return a;
0767 }
0768
0769 /**
0770 * Large set of attributes that does conversion of requests
0771 * for attributes of type StyleConstants.
0772 */
0773 class LargeConversionSet extends SimpleAttributeSet {
0774
0775 /**
0776 * Creates a new attribute set based on a supplied set of attributes.
0777 *
0778 * @param source the set of attributes
0779 */
0780 public LargeConversionSet(AttributeSet source) {
0781 super (source);
0782 }
0783
0784 public LargeConversionSet() {
0785 super ();
0786 }
0787
0788 /**
0789 * Checks whether a given attribute is defined.
0790 *
0791 * @param key the attribute key
0792 * @return true if the attribute is defined
0793 * @see AttributeSet#isDefined
0794 */
0795 public boolean isDefined(Object key) {
0796 if (key instanceof StyleConstants) {
0797 Object cssKey = css
0798 .styleConstantsKeyToCSSKey((StyleConstants) key);
0799 if (cssKey != null) {
0800 return super .isDefined(cssKey);
0801 }
0802 }
0803 return super .isDefined(key);
0804 }
0805
0806 /**
0807 * Gets the value of an attribute.
0808 *
0809 * @param key the attribute name
0810 * @return the attribute value
0811 * @see AttributeSet#getAttribute
0812 */
0813 public Object getAttribute(Object key) {
0814 if (key instanceof StyleConstants) {
0815 Object cssKey = css
0816 .styleConstantsKeyToCSSKey((StyleConstants) key);
0817 if (cssKey != null) {
0818 Object value = super .getAttribute(cssKey);
0819 if (value != null) {
0820 return css.cssValueToStyleConstantsValue(
0821 (StyleConstants) key, value);
0822 }
0823 }
0824 }
0825 return super .getAttribute(key);
0826 }
0827 }
0828
0829 /**
0830 * Small set of attributes that does conversion of requests
0831 * for attributes of type StyleConstants.
0832 */
0833 class SmallConversionSet extends SmallAttributeSet {
0834
0835 /**
0836 * Creates a new attribute set based on a supplied set of attributes.
0837 *
0838 * @param source the set of attributes
0839 */
0840 public SmallConversionSet(AttributeSet attrs) {
0841 super (attrs);
0842 }
0843
0844 /**
0845 * Checks whether a given attribute is defined.
0846 *
0847 * @param key the attribute key
0848 * @return true if the attribute is defined
0849 * @see AttributeSet#isDefined
0850 */
0851 public boolean isDefined(Object key) {
0852 if (key instanceof StyleConstants) {
0853 Object cssKey = css
0854 .styleConstantsKeyToCSSKey((StyleConstants) key);
0855 if (cssKey != null) {
0856 return super .isDefined(cssKey);
0857 }
0858 }
0859 return super .isDefined(key);
0860 }
0861
0862 /**
0863 * Gets the value of an attribute.
0864 *
0865 * @param key the attribute name
0866 * @return the attribute value
0867 * @see AttributeSet#getAttribute
0868 */
0869 public Object getAttribute(Object key) {
0870 if (key instanceof StyleConstants) {
0871 Object cssKey = css
0872 .styleConstantsKeyToCSSKey((StyleConstants) key);
0873 if (cssKey != null) {
0874 Object value = super .getAttribute(cssKey);
0875 if (value != null) {
0876 return css.cssValueToStyleConstantsValue(
0877 (StyleConstants) key, value);
0878 }
0879 }
0880 }
0881 return super .getAttribute(key);
0882 }
0883 }
0884
0885 // ---- Resource handling ----------------------------------------
0886
0887 /**
0888 * Fetches the font to use for the given set of attributes.
0889 */
0890 public Font getFont(AttributeSet a) {
0891 return css.getFont(this , a, 12, this );
0892 }
0893
0894 /**
0895 * Takes a set of attributes and turn it into a foreground color
0896 * specification. This might be used to specify things
0897 * like brighter, more hue, etc.
0898 *
0899 * @param a the set of attributes
0900 * @return the color
0901 */
0902 public Color getForeground(AttributeSet a) {
0903 Color c = css.getColor(a, CSS.Attribute.COLOR);
0904 if (c == null) {
0905 return Color.black;
0906 }
0907 return c;
0908 }
0909
0910 /**
0911 * Takes a set of attributes and turn it into a background color
0912 * specification. This might be used to specify things
0913 * like brighter, more hue, etc.
0914 *
0915 * @param a the set of attributes
0916 * @return the color
0917 */
0918 public Color getBackground(AttributeSet a) {
0919 return css.getColor(a, CSS.Attribute.BACKGROUND_COLOR);
0920 }
0921
0922 /**
0923 * Fetches the box formatter to use for the given set
0924 * of CSS attributes.
0925 */
0926 public BoxPainter getBoxPainter(AttributeSet a) {
0927 return new BoxPainter(a, css, this );
0928 }
0929
0930 /**
0931 * Fetches the list formatter to use for the given set
0932 * of CSS attributes.
0933 */
0934 public ListPainter getListPainter(AttributeSet a) {
0935 return new ListPainter(a, this );
0936 }
0937
0938 /**
0939 * Sets the base font size, with valid values between 1 and 7.
0940 */
0941 public void setBaseFontSize(int sz) {
0942 css.setBaseFontSize(sz);
0943 }
0944
0945 /**
0946 * Sets the base font size from the passed in String. The string
0947 * can either identify a specific font size, with legal values between
0948 * 1 and 7, or identifiy a relative font size such as +1 or -2.
0949 */
0950 public void setBaseFontSize(String size) {
0951 css.setBaseFontSize(size);
0952 }
0953
0954 public static int getIndexOfSize(float pt) {
0955 return CSS.getIndexOfSize(pt, sizeMapDefault);
0956 }
0957
0958 /**
0959 * Returns the point size, given a size index.
0960 */
0961 public float getPointSize(int index) {
0962 return css.getPointSize(index, this );
0963 }
0964
0965 /**
0966 * Given a string such as "+2", "-2", or "2",
0967 * returns a point size value.
0968 */
0969 public float getPointSize(String size) {
0970 return css.getPointSize(size, this );
0971 }
0972
0973 /**
0974 * Converts a color string such as "RED" or "#NNNNNN" to a Color.
0975 * Note: This will only convert the HTML3.2 color strings
0976 * or a string of length 7;
0977 * otherwise, it will return null.
0978 */
0979 public Color stringToColor(String string) {
0980 return CSS.stringToColor(string);
0981 }
0982
0983 /**
0984 * Returns the ImageIcon to draw in the background for
0985 * <code>attr</code>.
0986 */
0987 ImageIcon getBackgroundImage(AttributeSet attr) {
0988 Object value = attr
0989 .getAttribute(CSS.Attribute.BACKGROUND_IMAGE);
0990
0991 if (value != null) {
0992 return ((CSS.BackgroundImage) value).getImage(getBase());
0993 }
0994 return null;
0995 }
0996
0997 /**
0998 * Adds a rule into the StyleSheet.
0999 *
1000 * @param selector the selector to use for the rule.
1001 * This will be a set of simple selectors, and must
1002 * be a length of 1 or greater.
1003 * @param declaration the set of CSS attributes that
1004 * make up the rule.
1005 */
1006 void addRule(String[] selector, AttributeSet declaration,
1007 boolean isLinked) {
1008 int n = selector.length;
1009 StringBuffer sb = new StringBuffer();
1010 sb.append(selector[0]);
1011 for (int counter = 1; counter < n; counter++) {
1012 sb.append(' ');
1013 sb.append(selector[counter]);
1014 }
1015 String selectorName = sb.toString();
1016 Style rule = getStyle(selectorName);
1017 if (rule == null) {
1018 // Notice how the rule is first created, and it not part of
1019 // the synchronized block. It is done like this as creating
1020 // a new rule will fire a ChangeEvent. We do not want to be
1021 // holding the lock when calling to other objects, it can
1022 // result in deadlock.
1023 Style altRule = addStyle(selectorName, null);
1024 synchronized (this ) {
1025 SelectorMapping mapping = getRootSelectorMapping();
1026 for (int i = n - 1; i >= 0; i--) {
1027 mapping = mapping.getChildSelectorMapping(
1028 selector[i], true);
1029 }
1030 rule = mapping.getStyle();
1031 if (rule == null) {
1032 rule = altRule;
1033 mapping.setStyle(rule);
1034 refreshResolvedRules(selectorName, selector, rule,
1035 mapping.getSpecificity());
1036 }
1037 }
1038 }
1039 if (isLinked) {
1040 rule = getLinkedStyle(rule);
1041 }
1042 rule.addAttributes(declaration);
1043 }
1044
1045 //
1046 // The following gaggle of methods is used in maintaing the rules from
1047 // the sheet.
1048 //
1049
1050 /**
1051 * Updates the attributes of the rules to reference any related
1052 * rules in <code>ss</code>.
1053 */
1054 private synchronized void linkStyleSheetAt(StyleSheet ss, int index) {
1055 if (resolvedStyles.size() > 0) {
1056 Enumeration values = resolvedStyles.elements();
1057 while (values.hasMoreElements()) {
1058 ResolvedStyle rule = (ResolvedStyle) values
1059 .nextElement();
1060 rule.insertExtendedStyleAt(ss.getRule(rule.getName()),
1061 index);
1062 }
1063 }
1064 }
1065
1066 /**
1067 * Removes references to the rules in <code>ss</code>.
1068 * <code>index</code> gives the index the StyleSheet was at, that is
1069 * how many StyleSheets had been added before it.
1070 */
1071 private synchronized void unlinkStyleSheet(StyleSheet ss, int index) {
1072 if (resolvedStyles.size() > 0) {
1073 Enumeration values = resolvedStyles.elements();
1074 while (values.hasMoreElements()) {
1075 ResolvedStyle rule = (ResolvedStyle) values
1076 .nextElement();
1077 rule.removeExtendedStyleAt(index);
1078 }
1079 }
1080 }
1081
1082 /**
1083 * Returns the simple selectors that comprise selector.
1084 */
1085 /* protected */
1086 String[] getSimpleSelectors(String selector) {
1087 selector = cleanSelectorString(selector);
1088 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1089 Vector selectors = sb.getVector();
1090 int lastIndex = 0;
1091 int length = selector.length();
1092 while (lastIndex != -1) {
1093 int newIndex = selector.indexOf(' ', lastIndex);
1094 if (newIndex != -1) {
1095 selectors.addElement(selector.substring(lastIndex,
1096 newIndex));
1097 if (++newIndex == length) {
1098 lastIndex = -1;
1099 } else {
1100 lastIndex = newIndex;
1101 }
1102 } else {
1103 selectors.addElement(selector.substring(lastIndex));
1104 lastIndex = -1;
1105 }
1106 }
1107 String[] retValue = new String[selectors.size()];
1108 selectors.copyInto(retValue);
1109 SearchBuffer.releaseSearchBuffer(sb);
1110 return retValue;
1111 }
1112
1113 /**
1114 * Returns a string that only has one space between simple selectors,
1115 * which may be the passed in String.
1116 */
1117 /*protected*/String cleanSelectorString(String selector) {
1118 boolean lastWasSpace = true;
1119 for (int counter = 0, maxCounter = selector.length(); counter < maxCounter; counter++) {
1120 switch (selector.charAt(counter)) {
1121 case ' ':
1122 if (lastWasSpace) {
1123 return _cleanSelectorString(selector);
1124 }
1125 lastWasSpace = true;
1126 break;
1127 case '\n':
1128 case '\r':
1129 case '\t':
1130 return _cleanSelectorString(selector);
1131 default:
1132 lastWasSpace = false;
1133 }
1134 }
1135 if (lastWasSpace) {
1136 return _cleanSelectorString(selector);
1137 }
1138 // It was fine.
1139 return selector;
1140 }
1141
1142 /**
1143 * Returns a new String that contains only one space between non
1144 * white space characters.
1145 */
1146 private String _cleanSelectorString(String selector) {
1147 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1148 StringBuffer buff = sb.getStringBuffer();
1149 boolean lastWasSpace = true;
1150 int lastIndex = 0;
1151 char[] chars = selector.toCharArray();
1152 int numChars = chars.length;
1153 String retValue = null;
1154 try {
1155 for (int counter = 0; counter < numChars; counter++) {
1156 switch (chars[counter]) {
1157 case ' ':
1158 if (!lastWasSpace) {
1159 lastWasSpace = true;
1160 if (lastIndex < counter) {
1161 buff.append(chars, lastIndex, 1 + counter
1162 - lastIndex);
1163 }
1164 }
1165 lastIndex = counter + 1;
1166 break;
1167 case '\n':
1168 case '\r':
1169 case '\t':
1170 if (!lastWasSpace) {
1171 lastWasSpace = true;
1172 if (lastIndex < counter) {
1173 buff.append(chars, lastIndex, counter
1174 - lastIndex);
1175 buff.append(' ');
1176 }
1177 }
1178 lastIndex = counter + 1;
1179 break;
1180 default:
1181 lastWasSpace = false;
1182 break;
1183 }
1184 }
1185 if (lastWasSpace && buff.length() > 0) {
1186 // Remove last space.
1187 buff.setLength(buff.length() - 1);
1188 } else if (lastIndex < numChars) {
1189 buff.append(chars, lastIndex, numChars - lastIndex);
1190 }
1191 retValue = buff.toString();
1192 } finally {
1193 SearchBuffer.releaseSearchBuffer(sb);
1194 }
1195 return retValue;
1196 }
1197
1198 /**
1199 * Returns the root selector mapping that all selectors are relative
1200 * to. This is an inverted graph of the selectors.
1201 */
1202 private SelectorMapping getRootSelectorMapping() {
1203 return selectorMapping;
1204 }
1205
1206 /**
1207 * Returns the specificity of the passed in String. It assumes the
1208 * passed in string doesn't contain junk, that is each selector is
1209 * separated by a space and each selector at most contains one . or one
1210 * #. A simple selector has a weight of 1, an id selector has a weight
1211 * of 100, and a class selector has a weight of 10000.
1212 */
1213 /*protected*/static int getSpecificity(String selector) {
1214 int specificity = 0;
1215 boolean lastWasSpace = true;
1216
1217 for (int counter = 0, maxCounter = selector.length(); counter < maxCounter; counter++) {
1218 switch (selector.charAt(counter)) {
1219 case '.':
1220 specificity += 100;
1221 break;
1222 case '#':
1223 specificity += 10000;
1224 break;
1225 case ' ':
1226 lastWasSpace = true;
1227 break;
1228 default:
1229 if (lastWasSpace) {
1230 lastWasSpace = false;
1231 specificity += 1;
1232 }
1233 }
1234 }
1235 return specificity;
1236 }
1237
1238 /**
1239 * Returns the style that linked attributes should be added to. This
1240 * will create the style if necessary.
1241 */
1242 private Style getLinkedStyle(Style localStyle) {
1243 // NOTE: This is not synchronized, and the caller of this does
1244 // not synchronize. There is the chance for one of the callers to
1245 // overwrite the existing resolved parent, but it is quite rare.
1246 // The reason this is left like this is because setResolveParent
1247 // will fire a ChangeEvent. It is really, REALLY bad for us to
1248 // hold a lock when calling outside of us, it may cause a deadlock.
1249 Style retStyle = (Style) localStyle.getResolveParent();
1250 if (retStyle == null) {
1251 retStyle = addStyle(null, null);
1252 localStyle.setResolveParent(retStyle);
1253 }
1254 return retStyle;
1255 }
1256
1257 /**
1258 * Returns the resolved style for <code>selector</code>. This will
1259 * create the resolved style, if necessary.
1260 */
1261 private synchronized Style getResolvedStyle(String selector,
1262 Vector elements, HTML.Tag t) {
1263 Style retStyle = (Style) resolvedStyles.get(selector);
1264 if (retStyle == null) {
1265 retStyle = createResolvedStyle(selector, elements, t);
1266 }
1267 return retStyle;
1268 }
1269
1270 /**
1271 * Returns the resolved style for <code>selector</code>. This will
1272 * create the resolved style, if necessary.
1273 */
1274 private synchronized Style getResolvedStyle(String selector) {
1275 Style retStyle = (Style) resolvedStyles.get(selector);
1276 if (retStyle == null) {
1277 retStyle = createResolvedStyle(selector);
1278 }
1279 return retStyle;
1280 }
1281
1282 /**
1283 * Adds <code>mapping</code> to <code>elements</code>. It is added
1284 * such that <code>elements</code> will remain ordered by
1285 * specificity.
1286 */
1287 private void addSortedStyle(SelectorMapping mapping, Vector elements) {
1288 int size = elements.size();
1289
1290 if (size > 0) {
1291 int specificity = mapping.getSpecificity();
1292
1293 for (int counter = 0; counter < size; counter++) {
1294 if (specificity >= ((SelectorMapping) elements
1295 .elementAt(counter)).getSpecificity()) {
1296 elements.insertElementAt(mapping, counter);
1297 return;
1298 }
1299 }
1300 }
1301 elements.addElement(mapping);
1302 }
1303
1304 /**
1305 * Adds <code>parentMapping</code> to <code>styles</code>, and
1306 * recursively calls this method if <code>parentMapping</code> has
1307 * any child mappings for any of the Elements in <code>elements</code>.
1308 */
1309 private synchronized void getStyles(SelectorMapping parentMapping,
1310 Vector styles, String[] tags, String[] ids,
1311 String[] classes, int index, int numElements,
1312 Hashtable alreadyChecked) {
1313 // Avoid desending the same mapping twice.
1314 if (alreadyChecked.contains(parentMapping)) {
1315 return;
1316 }
1317 alreadyChecked.put(parentMapping, parentMapping);
1318 Style style = parentMapping.getStyle();
1319 if (style != null) {
1320 addSortedStyle(parentMapping, styles);
1321 }
1322 for (int counter = index; counter < numElements; counter++) {
1323 String tagString = tags[counter];
1324 if (tagString != null) {
1325 SelectorMapping childMapping = parentMapping
1326 .getChildSelectorMapping(tagString, false);
1327 if (childMapping != null) {
1328 getStyles(childMapping, styles, tags, ids, classes,
1329 counter + 1, numElements, alreadyChecked);
1330 }
1331 if (classes[counter] != null) {
1332 String className = classes[counter];
1333 childMapping = parentMapping
1334 .getChildSelectorMapping(tagString + "."
1335 + className, false);
1336 if (childMapping != null) {
1337 getStyles(childMapping, styles, tags, ids,
1338 classes, counter + 1, numElements,
1339 alreadyChecked);
1340 }
1341 childMapping = parentMapping
1342 .getChildSelectorMapping("." + className,
1343 false);
1344 if (childMapping != null) {
1345 getStyles(childMapping, styles, tags, ids,
1346 classes, counter + 1, numElements,
1347 alreadyChecked);
1348 }
1349 }
1350 if (ids[counter] != null) {
1351 String idName = ids[counter];
1352 childMapping = parentMapping
1353 .getChildSelectorMapping(tagString + "#"
1354 + idName, false);
1355 if (childMapping != null) {
1356 getStyles(childMapping, styles, tags, ids,
1357 classes, counter + 1, numElements,
1358 alreadyChecked);
1359 }
1360 childMapping = parentMapping
1361 .getChildSelectorMapping("#" + idName,
1362 false);
1363 if (childMapping != null) {
1364 getStyles(childMapping, styles, tags, ids,
1365 classes, counter + 1, numElements,
1366 alreadyChecked);
1367 }
1368 }
1369 }
1370 }
1371 }
1372
1373 /**
1374 * Creates and returns a Style containing all the rules that match
1375 * <code>selector</code>.
1376 */
1377 private synchronized Style createResolvedStyle(String selector,
1378 String[] tags, String[] ids, String[] classes) {
1379 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1380 Vector tempVector = sb.getVector();
1381 Hashtable tempHashtable = sb.getHashtable();
1382 // Determine all the Styles that are appropriate, placing them
1383 // in tempVector
1384 try {
1385 SelectorMapping mapping = getRootSelectorMapping();
1386 int numElements = tags.length;
1387 String tagString = tags[0];
1388 SelectorMapping childMapping = mapping
1389 .getChildSelectorMapping(tagString, false);
1390 if (childMapping != null) {
1391 getStyles(childMapping, tempVector, tags, ids, classes,
1392 1, numElements, tempHashtable);
1393 }
1394 if (classes[0] != null) {
1395 String className = classes[0];
1396 childMapping = mapping.getChildSelectorMapping(
1397 tagString + "." + className, false);
1398 if (childMapping != null) {
1399 getStyles(childMapping, tempVector, tags, ids,
1400 classes, 1, numElements, tempHashtable);
1401 }
1402 childMapping = mapping.getChildSelectorMapping("."
1403 + className, false);
1404 if (childMapping != null) {
1405 getStyles(childMapping, tempVector, tags, ids,
1406 classes, 1, numElements, tempHashtable);
1407 }
1408 }
1409 if (ids[0] != null) {
1410 String idName = ids[0];
1411 childMapping = mapping.getChildSelectorMapping(
1412 tagString + "#" + idName, false);
1413 if (childMapping != null) {
1414 getStyles(childMapping, tempVector, tags, ids,
1415 classes, 1, numElements, tempHashtable);
1416 }
1417 childMapping = mapping.getChildSelectorMapping("#"
1418 + idName, false);
1419 if (childMapping != null) {
1420 getStyles(childMapping, tempVector, tags, ids,
1421 classes, 1, numElements, tempHashtable);
1422 }
1423 }
1424 // Create a new Style that will delegate to all the matching
1425 // Styles.
1426 int numLinkedSS = (linkedStyleSheets != null) ? linkedStyleSheets
1427 .size()
1428 : 0;
1429 int numStyles = tempVector.size();
1430 AttributeSet[] attrs = new AttributeSet[numStyles
1431 + numLinkedSS];
1432 for (int counter = 0; counter < numStyles; counter++) {
1433 attrs[counter] = ((SelectorMapping) tempVector
1434 .elementAt(counter)).getStyle();
1435 }
1436 // Get the AttributeSet from linked style sheets.
1437 for (int counter = 0; counter < numLinkedSS; counter++) {
1438 AttributeSet attr = ((StyleSheet) linkedStyleSheets
1439 .elementAt(counter)).getRule(selector);
1440 if (attr == null) {
1441 attrs[counter + numStyles] = SimpleAttributeSet.EMPTY;
1442 } else {
1443 attrs[counter + numStyles] = attr;
1444 }
1445 }
1446 ResolvedStyle retStyle = new ResolvedStyle(selector, attrs,
1447 numStyles);
1448 resolvedStyles.put(selector, retStyle);
1449 return retStyle;
1450 } finally {
1451 SearchBuffer.releaseSearchBuffer(sb);
1452 }
1453 }
1454
1455 /**
1456 * Creates and returns a Style containing all the rules that
1457 * matches <code>selector</code>.
1458 *
1459 * @param elements a Vector of all the Elements
1460 * the style is being asked for. The
1461 * first Element is the deepest Element, with the last Element
1462 * representing the root.
1463 * @param t the Tag to use for
1464 * the first Element in <code>elements</code>
1465 */
1466 private Style createResolvedStyle(String selector, Vector elements,
1467 HTML.Tag t) {
1468 int numElements = elements.size();
1469 // Build three arrays, one for tags, one for class's, and one for
1470 // id's
1471 String tags[] = new String[numElements];
1472 String ids[] = new String[numElements];
1473 String classes[] = new String[numElements];
1474 for (int counter = 0; counter < numElements; counter++) {
1475 Element e = (Element) elements.elementAt(counter);
1476 AttributeSet attr = e.getAttributes();
1477 if (counter == 0 && e.isLeaf()) {
1478 // For leafs, we use the second tier attributes.
1479 Object testAttr = attr.getAttribute(t);
1480 if (testAttr instanceof AttributeSet) {
1481 attr = (AttributeSet) testAttr;
1482 } else {
1483 attr = null;
1484 }
1485 }
1486 if (attr != null) {
1487 HTML.Tag tag = (HTML.Tag) attr
1488 .getAttribute(StyleConstants.NameAttribute);
1489 if (tag != null) {
1490 tags[counter] = tag.toString();
1491 } else {
1492 tags[counter] = null;
1493 }
1494 if (attr.isDefined(HTML.Attribute.CLASS)) {
1495 classes[counter] = attr.getAttribute(
1496 HTML.Attribute.CLASS).toString();
1497 } else {
1498 classes[counter] = null;
1499 }
1500 if (attr.isDefined(HTML.Attribute.ID)) {
1501 ids[counter] = attr.getAttribute(HTML.Attribute.ID)
1502 .toString();
1503 } else {
1504 ids[counter] = null;
1505 }
1506 } else {
1507 tags[counter] = ids[counter] = classes[counter] = null;
1508 }
1509 }
1510 tags[0] = t.toString();
1511 return createResolvedStyle(selector, tags, ids, classes);
1512 }
1513
1514 /**
1515 * Creates and returns a Style containing all the rules that match
1516 * <code>selector</code>. It is assumed that each simple selector
1517 * in <code>selector</code> is separated by a space.
1518 */
1519 private Style createResolvedStyle(String selector) {
1520 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1521 // Will contain the tags, ids, and classes, in that order.
1522 Vector elements = sb.getVector();
1523 try {
1524 boolean done;
1525 int dotIndex = 0;
1526 int spaceIndex = 0;
1527 int poundIndex = 0;
1528 int lastIndex = 0;
1529 int length = selector.length();
1530 while (lastIndex < length) {
1531 if (dotIndex == lastIndex) {
1532 dotIndex = selector.indexOf('.', lastIndex);
1533 }
1534 if (poundIndex == lastIndex) {
1535 poundIndex = selector.indexOf('#', lastIndex);
1536 }
1537 spaceIndex = selector.indexOf(' ', lastIndex);
1538 if (spaceIndex == -1) {
1539 spaceIndex = length;
1540 }
1541 if (dotIndex != -1 && poundIndex != -1
1542 && dotIndex < spaceIndex
1543 && poundIndex < spaceIndex) {
1544 if (poundIndex < dotIndex) {
1545 // #.
1546 if (lastIndex == poundIndex) {
1547 elements.addElement("");
1548 } else {
1549 elements.addElement(selector.substring(
1550 lastIndex, poundIndex));
1551 }
1552 if ((dotIndex + 1) < spaceIndex) {
1553 elements.addElement(selector.substring(
1554 dotIndex + 1, spaceIndex));
1555 } else {
1556 elements.addElement(null);
1557 }
1558 if ((poundIndex + 1) == dotIndex) {
1559 elements.addElement(null);
1560 } else {
1561 elements.addElement(selector.substring(
1562 poundIndex + 1, dotIndex));
1563 }
1564 } else if (poundIndex < spaceIndex) {
1565 // .#
1566 if (lastIndex == dotIndex) {
1567 elements.addElement("");
1568 } else {
1569 elements.addElement(selector.substring(
1570 lastIndex, dotIndex));
1571 }
1572 if ((dotIndex + 1) < poundIndex) {
1573 elements.addElement(selector.substring(
1574 dotIndex + 1, poundIndex));
1575 } else {
1576 elements.addElement(null);
1577 }
1578 if ((poundIndex + 1) == spaceIndex) {
1579 elements.addElement(null);
1580 } else {
1581 elements.addElement(selector.substring(
1582 poundIndex + 1, spaceIndex));
1583 }
1584 }
1585 dotIndex = poundIndex = spaceIndex + 1;
1586 } else if (dotIndex != -1 && dotIndex < spaceIndex) {
1587 // .
1588 if (dotIndex == lastIndex) {
1589 elements.addElement("");
1590 } else {
1591 elements.addElement(selector.substring(
1592 lastIndex, dotIndex));
1593 }
1594 if ((dotIndex + 1) == spaceIndex) {
1595 elements.addElement(null);
1596 } else {
1597 elements.addElement(selector.substring(
1598 dotIndex + 1, spaceIndex));
1599 }
1600 elements.addElement(null);
1601 dotIndex = spaceIndex + 1;
1602 } else if (poundIndex != -1 && poundIndex < spaceIndex) {
1603 // #
1604 if (poundIndex == lastIndex) {
1605 elements.addElement("");
1606 } else {
1607 elements.addElement(selector.substring(
1608 lastIndex, poundIndex));
1609 }
1610 elements.addElement(null);
1611 if ((poundIndex + 1) == spaceIndex) {
1612 elements.addElement(null);
1613 } else {
1614 elements.addElement(selector.substring(
1615 poundIndex + 1, spaceIndex));
1616 }
1617 poundIndex = spaceIndex + 1;
1618 } else {
1619 // id
1620 elements.addElement(selector.substring(lastIndex,
1621 spaceIndex));
1622 elements.addElement(null);
1623 elements.addElement(null);
1624 }
1625 lastIndex = spaceIndex + 1;
1626 }
1627 // Create the tag, id, and class arrays.
1628 int total = elements.size();
1629 int numTags = total / 3;
1630 String[] tags = new String[numTags];
1631 String[] ids = new String[numTags];
1632 String[] classes = new String[numTags];
1633 for (int index = 0, eIndex = total - 3; index < numTags; index++, eIndex -= 3) {
1634 tags[index] = (String) elements.elementAt(eIndex);
1635 classes[index] = (String) elements
1636 .elementAt(eIndex + 1);
1637 ids[index] = (String) elements.elementAt(eIndex + 2);
1638 }
1639 return createResolvedStyle(selector, tags, ids, classes);
1640 } finally {
1641 SearchBuffer.releaseSearchBuffer(sb);
1642 }
1643 }
1644
1645 /**
1646 * Should be invoked when a new rule is added that did not previously
1647 * exist. Goes through and refreshes the necessary resolved
1648 * rules.
1649 */
1650 private synchronized void refreshResolvedRules(String selectorName,
1651 String[] selector, Style newStyle, int specificity) {
1652 if (resolvedStyles.size() > 0) {
1653 Enumeration values = resolvedStyles.elements();
1654 while (values.hasMoreElements()) {
1655 ResolvedStyle style = (ResolvedStyle) values
1656 .nextElement();
1657 if (style.matches(selectorName)) {
1658 style.insertStyle(newStyle, specificity);
1659 }
1660 }
1661 }
1662 }
1663
1664 /**
1665 * A temporary class used to hold a Vector, a StringBuffer and a
1666 * Hashtable. This is used to avoid allocing a lot of garbage when
1667 * searching for rules. Use the static method obtainSearchBuffer and
1668 * releaseSearchBuffer to get a SearchBuffer, and release it when
1669 * done.
1670 */
1671 private static class SearchBuffer {
1672 /** A stack containing instances of SearchBuffer. Used in getting
1673 * rules. */
1674 static Stack searchBuffers = new Stack();
1675 // A set of temporary variables that can be used in whatever way.
1676 Vector vector = null;
1677 StringBuffer stringBuffer = null;
1678 Hashtable hashtable = null;
1679
1680 /**
1681 * Returns an instance of SearchBuffer. Be sure and issue
1682 * a releaseSearchBuffer when done with it.
1683 */
1684 static SearchBuffer obtainSearchBuffer() {
1685 SearchBuffer sb;
1686 try {
1687 if (!searchBuffers.empty()) {
1688 sb = (SearchBuffer) searchBuffers.pop();
1689 } else {
1690 sb = new SearchBuffer();
1691 }
1692 } catch (EmptyStackException ese) {
1693 sb = new SearchBuffer();
1694 }
1695 return sb;
1696 }
1697
1698 /**
1699 * Adds <code>sb</code> to the stack of SearchBuffers that can
1700 * be used.
1701 */
1702 static void releaseSearchBuffer(SearchBuffer sb) {
1703 sb.empty();
1704 searchBuffers.push(sb);
1705 }
1706
1707 StringBuffer getStringBuffer() {
1708 if (stringBuffer == null) {
1709 stringBuffer = new StringBuffer();
1710 }
1711 return stringBuffer;
1712 }
1713
1714 Vector getVector() {
1715 if (vector == null) {
1716 vector = new Vector();
1717 }
1718 return vector;
1719 }
1720
1721 Hashtable getHashtable() {
1722 if (hashtable == null) {
1723 hashtable = new Hashtable();
1724 }
1725 return hashtable;
1726 }
1727
1728 void empty() {
1729 if (stringBuffer != null) {
1730 stringBuffer.setLength(0);
1731 }
1732 if (vector != null) {
1733 vector.removeAllElements();
1734 }
1735 if (hashtable != null) {
1736 hashtable.clear();
1737 }
1738 }
1739 }
1740
1741 static final Border noBorder = new EmptyBorder(0, 0, 0, 0);
1742
1743 /**
1744 * Class to carry out some of the duties of
1745 * CSS formatting. Implementations of this
1746 * class enable views to present the CSS formatting
1747 * while not knowing anything about how the CSS values
1748 * are being cached.
1749 * <p>
1750 * As a delegate of Views, this object is responsible for
1751 * the insets of a View and making sure the background
1752 * is maintained according to the CSS attributes.
1753 */
1754 public static class BoxPainter implements Serializable {
1755
1756 BoxPainter(AttributeSet a, CSS css, StyleSheet ss) {
1757 this .ss = ss;
1758 this .css = css;
1759 border = getBorder(a);
1760 binsets = border.getBorderInsets(null);
1761 topMargin = getLength(CSS.Attribute.MARGIN_TOP, a);
1762 bottomMargin = getLength(CSS.Attribute.MARGIN_BOTTOM, a);
1763 leftMargin = getLength(CSS.Attribute.MARGIN_LEFT, a);
1764 rightMargin = getLength(CSS.Attribute.MARGIN_RIGHT, a);
1765 bg = ss.getBackground(a);
1766 if (ss.getBackgroundImage(a) != null) {
1767 bgPainter = new BackgroundImagePainter(a, css, ss);
1768 }
1769 }
1770
1771 /**
1772 * Fetches a border to render for the given attributes.
1773 * PENDING(prinz) This is pretty badly hacked at the
1774 * moment.
1775 */
1776 Border getBorder(AttributeSet a) {
1777 Border b = noBorder;
1778 Object o = a.getAttribute(CSS.Attribute.BORDER_STYLE);
1779 if (o != null) {
1780 String bstyle = o.toString();
1781 int bw = (int) getLength(
1782 CSS.Attribute.BORDER_TOP_WIDTH, a);
1783 if (bw > 0) {
1784 if (bstyle.equals("inset")) {
1785 Color c = getBorderColor(a);
1786 b = new BevelBorder(BevelBorder.LOWERED, c
1787 .brighter(), c.darker());
1788 } else if (bstyle.equals("outset")) {
1789 Color c = getBorderColor(a);
1790 b = new BevelBorder(BevelBorder.RAISED, c
1791 .brighter(), c.darker());
1792 } else if (bstyle.equals("solid")) {
1793 Color c = getBorderColor(a);
1794 b = new LineBorder(c, bw);
1795 }
1796 }
1797 }
1798 return b;
1799 }
1800
1801 /**
1802 * Fetches the color to use for borders. This will either be
1803 * the value specified by the border-color attribute (which
1804 * is not inherited), or it will default to the color attribute
1805 * (which is inherited).
1806 */
1807 Color getBorderColor(AttributeSet a) {
1808 Color color = css.getColor(a, CSS.Attribute.BORDER_COLOR);
1809 if (color == null) {
1810 color = css.getColor(a, CSS.Attribute.COLOR);
1811 if (color == null) {
1812 return Color.black;
1813 }
1814 }
1815 return color;
1816 }
1817
1818 /**
1819 * Fetches the inset needed on a given side to
1820 * account for the margin, border, and padding.
1821 *
1822 * @param side The size of the box to fetch the
1823 * inset for. This can be View.TOP,
1824 * View.LEFT, View.BOTTOM, or View.RIGHT.
1825 * @param v the view making the request. This is
1826 * used to get the AttributeSet, and may be used to
1827 * resolve percentage arguments.
1828 * @exception IllegalArgumentException for an invalid direction
1829 */
1830 public float getInset(int side, View v) {
1831 AttributeSet a = v.getAttributes();
1832 float inset = 0;
1833 switch (side) {
1834 case View.LEFT:
1835 inset += getOrientationMargin(HorizontalMargin.LEFT,
1836 leftMargin, a, isLeftToRight(v));
1837 inset += binsets.left;
1838 inset += getLength(CSS.Attribute.PADDING_LEFT, a);
1839 break;
1840 case View.RIGHT:
1841 inset += getOrientationMargin(HorizontalMargin.RIGHT,
1842 rightMargin, a, isLeftToRight(v));
1843 inset += binsets.right;
1844 inset += getLength(CSS.Attribute.PADDING_RIGHT, a);
1845 break;
1846 case View.TOP:
1847 inset += topMargin;
1848 inset += binsets.top;
1849 inset += getLength(CSS.Attribute.PADDING_TOP, a);
1850 break;
1851 case View.BOTTOM:
1852 inset += bottomMargin;
1853 inset += binsets.bottom;
1854 inset += getLength(CSS.Attribute.PADDING_BOTTOM, a);
1855 break;
1856 default:
1857 throw new IllegalArgumentException("Invalid side: "
1858 + side);
1859 }
1860 return inset;
1861 }
1862
1863 /**
1864 * Paints the CSS box according to the attributes
1865 * given. This should paint the border, padding,
1866 * and background.
1867 *
1868 * @param g the rendering surface.
1869 * @param x the x coordinate of the allocated area to
1870 * render into.
1871 * @param y the y coordinate of the allocated area to
1872 * render into.
1873 * @param w the width of the allocated area to render into.
1874 * @param h the height of the allocated area to render into.
1875 * @param v the view making the request. This is
1876 * used to get the AttributeSet, and may be used to
1877 * resolve percentage arguments.
1878 */
1879 public void paint(Graphics g, float x, float y, float w,
1880 float h, View v) {
1881 // PENDING(prinz) implement real rendering... which would
1882 // do full set of border and background capabilities.
1883 // remove margin
1884
1885 float dx = 0;
1886 float dy = 0;
1887 float dw = 0;
1888 float dh = 0;
1889 AttributeSet a = v.getAttributes();
1890 boolean isLeftToRight = isLeftToRight(v);
1891 float localLeftMargin = getOrientationMargin(
1892 HorizontalMargin.LEFT, leftMargin, a, isLeftToRight);
1893 float localRightMargin = getOrientationMargin(
1894 HorizontalMargin.RIGHT, rightMargin, a,
1895 isLeftToRight);
1896 if (!(v instanceof HTMLEditorKit.HTMLFactory.BodyBlockView)) {
1897 dx = localLeftMargin;
1898 dy = topMargin;
1899 dw = -(localLeftMargin + localRightMargin);
1900 dh = -(topMargin + bottomMargin);
1901 }
1902 if (bg != null) {
1903 g.setColor(bg);
1904 g.fillRect((int) (x + dx), (int) (y + dy),
1905 (int) (w + dw), (int) (h + dh));
1906 }
1907 if (bgPainter != null) {
1908 bgPainter.paint(g, x + dx, y + dy, w + dw, h + dh, v);
1909 }
1910 x += localLeftMargin;
1911 y += topMargin;
1912 w -= localLeftMargin + localRightMargin;
1913 h -= topMargin + bottomMargin;
1914 if (border instanceof BevelBorder) {
1915 //BevelBorder does not support border width
1916 int bw = (int) getLength(
1917 CSS.Attribute.BORDER_TOP_WIDTH, a);
1918 for (int i = bw - 1; i >= 0; i--) {
1919 border.paintBorder(null, g, (int) x + i, (int) y
1920 + i, (int) w - 2 * i, (int) h - 2 * i);
1921 }
1922 } else {
1923 border.paintBorder(null, g, (int) x, (int) y, (int) w,
1924 (int) h);
1925 }
1926 }
1927
1928 float getLength(CSS.Attribute key, AttributeSet a) {
1929 return css.getLength(a, key, ss);
1930 }
1931
1932 static boolean isLeftToRight(View v) {
1933 boolean ret = true;
1934 if (isOrientationAware(v)) {
1935 Container container = null;
1936 if (v != null && (container = v.getContainer()) != null) {
1937 ret = container.getComponentOrientation()
1938 .isLeftToRight();
1939 }
1940 }
1941 return ret;
1942 }
1943
1944 /*
1945 * only certain tags are concerned about orientation
1946 * <dir>, <menu>, <ul>, <ol>
1947 * for all others we return true. It is implemented this way
1948 * for performance purposes
1949 */
1950 static boolean isOrientationAware(View v) {
1951 boolean ret = false;
1952 AttributeSet attr = null;
1953 Object obj = null;
1954 if (v != null
1955 && (attr = v.getElement().getAttributes()) != null
1956 && (obj = attr
1957 .getAttribute(StyleConstants.NameAttribute)) instanceof HTML.Tag
1958 && (obj == HTML.Tag.DIR || obj == HTML.Tag.MENU
1959 || obj == HTML.Tag.UL || obj == HTML.Tag.OL)) {
1960 ret = true;
1961 }
1962
1963 return ret;
1964 }
1965
1966 static enum HorizontalMargin {
1967 LEFT, RIGHT
1968 };
1969
1970 /**
1971 * for <dir>, <menu>, <ul> etc.
1972 * margins are Left-To-Right/Right-To-Left depended.
1973 * see 5088268 for more details
1974 * margin-(left|right)-(ltr|rtl) were introduced to describe it
1975 * if margin-(left|right) is present we are to use it.
1976 *
1977 * @param side The horizontal side to fetch margin for
1978 * This can be HorizontalMargin.LEFT or HorizontalMargin.RIGHT
1979 * @param cssMargin margin from css
1980 * @param a AttributeSet for the View we getting margin for
1981 * @param isLeftToRight
1982 * @return orientation depended margin
1983 */
1984 float getOrientationMargin(HorizontalMargin side,
1985 float cssMargin, AttributeSet a, boolean isLeftToRight) {
1986 float margin = cssMargin;
1987 float orientationMargin = cssMargin;
1988 Object cssMarginValue = null;
1989 switch (side) {
1990 case RIGHT: {
1991 orientationMargin = (isLeftToRight) ? getLength(
1992 CSS.Attribute.MARGIN_RIGHT_LTR, a) : getLength(
1993 CSS.Attribute.MARGIN_RIGHT_RTL, a);
1994 cssMarginValue = a
1995 .getAttribute(CSS.Attribute.MARGIN_RIGHT);
1996 }
1997 break;
1998 case LEFT: {
1999 orientationMargin = (isLeftToRight) ? getLength(
2000 CSS.Attribute.MARGIN_LEFT_LTR, a) : getLength(
2001 CSS.Attribute.MARGIN_LEFT_RTL, a);
2002 cssMarginValue = a
2003 .getAttribute(CSS.Attribute.MARGIN_LEFT);
2004 }
2005 break;
2006 }
2007
2008 if (cssMarginValue == null
2009 && orientationMargin != Integer.MIN_VALUE) {
2010 margin = orientationMargin;
2011 }
2012 return margin;
2013 }
2014
2015 float topMargin;
2016 float bottomMargin;
2017 float leftMargin;
2018 float rightMargin;
2019 // Bitmask, used to indicate what margins are relative:
2020 // bit 0 for top, 1 for bottom, 2 for left and 3 for right.
2021 short marginFlags;
2022 Border border;
2023 Insets binsets;
2024 CSS css;
2025 StyleSheet ss;
2026 Color bg;
2027 BackgroundImagePainter bgPainter;
2028 }
2029
2030 /**
2031 * Class to carry out some of the duties of CSS list
2032 * formatting. Implementations of this
2033 * class enable views to present the CSS formatting
2034 * while not knowing anything about how the CSS values
2035 * are being cached.
2036 */
2037 public static class ListPainter implements Serializable {
2038
2039 ListPainter(AttributeSet attr, StyleSheet ss) {
2040 this .ss = ss;
2041 /* Get the image to use as a list bullet */
2042 String imgstr = (String) attr
2043 .getAttribute(CSS.Attribute.LIST_STYLE_IMAGE);
2044 type = null;
2045 if (imgstr != null && !imgstr.equals("none")) {
2046 String tmpstr = null;
2047 try {
2048 StringTokenizer st = new StringTokenizer(imgstr,
2049 "()");
2050 if (st.hasMoreTokens())
2051 tmpstr = st.nextToken();
2052 if (st.hasMoreTokens())
2053 tmpstr = st.nextToken();
2054 URL u = new URL(tmpstr);
2055 img = new ImageIcon(u);
2056 } catch (MalformedURLException e) {
2057 if (tmpstr != null && ss != null
2058 && ss.getBase() != null) {
2059 try {
2060 URL u = new URL(ss.getBase(), tmpstr);
2061 img = new ImageIcon(u);
2062 } catch (MalformedURLException murle) {
2063 img = null;
2064 }
2065 } else {
2066 img = null;
2067 }
2068 }
2069 }
2070
2071 /* Get the type of bullet to use in the list */
2072 if (img == null) {
2073 type = (CSS.Value) attr
2074 .getAttribute(CSS.Attribute.LIST_STYLE_TYPE);
2075 }
2076 start = 1;
2077
2078 paintRect = new Rectangle();
2079 }
2080
2081 /**
2082 * Returns a string that represents the value
2083 * of the HTML.Attribute.TYPE attribute.
2084 * If this attributes is not defined, then
2085 * then the type defaults to "disc" unless
2086 * the tag is on Ordered list. In the case
2087 * of the latter, the default type is "decimal".
2088 */
2089 private CSS.Value getChildType(View childView) {
2090 CSS.Value childtype = (CSS.Value) childView.getAttributes()
2091 .getAttribute(CSS.Attribute.LIST_STYLE_TYPE);
2092
2093 if (childtype == null) {
2094 if (type == null) {
2095 // Parent view.
2096 View v = childView.getParent();
2097 HTMLDocument doc = (HTMLDocument) v.getDocument();
2098 if (doc.matchNameAttribute(v.getElement()
2099 .getAttributes(), HTML.Tag.OL)) {
2100 childtype = CSS.Value.DECIMAL;
2101 } else {
2102 childtype = CSS.Value.DISC;
2103 }
2104 } else {
2105 childtype = type;
2106 }
2107 }
2108 return childtype;
2109 }
2110
2111 /**
2112 * Obtains the starting index from <code>parent</code>.
2113 */
2114 private void getStart(View parent) {
2115 checkedForStart = true;
2116 Element element = parent.getElement();
2117 if (element != null) {
2118 AttributeSet attr = element.getAttributes();
2119 Object startValue;
2120 if (attr != null
2121 && attr.isDefined(HTML.Attribute.START)
2122 && (startValue = attr
2123 .getAttribute(HTML.Attribute.START)) != null
2124 && (startValue instanceof String)) {
2125
2126 try {
2127 start = Integer.parseInt((String) startValue);
2128 } catch (NumberFormatException nfe) {
2129 }
2130 }
2131 }
2132 }
2133
2134 /**
2135 * Returns an integer that should be used to render the child at
2136 * <code>childIndex</code> with. The retValue will usually be
2137 * <code>childIndex</code> + 1, unless <code>parentView</code>
2138 * has some Views that do not represent LI's, or one of the views
2139 * has a HTML.Attribute.START specified.
2140 */
2141 private int getRenderIndex(View parentView, int childIndex) {
2142 if (!checkedForStart) {
2143 getStart(parentView);
2144 }
2145 int retIndex = childIndex;
2146 for (int counter = childIndex; counter >= 0; counter--) {
2147 AttributeSet as = parentView.getElement().getElement(
2148 counter).getAttributes();
2149 if (as.getAttribute(StyleConstants.NameAttribute) != HTML.Tag.LI) {
2150 retIndex--;
2151 } else if (as.isDefined(HTML.Attribute.VALUE)) {
2152 Object value = as
2153 .getAttribute(HTML.Attribute.VALUE);
2154 if (value != null && (value instanceof String)) {
2155 try {
2156 int iValue = Integer
2157 .parseInt((String) value);
2158 return retIndex - counter + iValue;
2159 } catch (NumberFormatException nfe) {
2160 }
2161 }
2162 }
2163 }
2164 return retIndex + start;
2165 }
2166
2167 /**
2168 * Paints the CSS list decoration according to the
2169 * attributes given.
2170 *
2171 * @param g the rendering surface.
2172 * @param x the x coordinate of the list item allocation
2173 * @param y the y coordinate of the list item allocation
2174 * @param w the width of the list item allocation
2175 * @param h the height of the list item allocation
2176 * @param v the allocated area to paint into.
2177 * @param item which list item is being painted. This
2178 * is a number greater than or equal to 0.
2179 */
2180 public void paint(Graphics g, float x, float y, float w,
2181 float h, View v, int item) {
2182 View cv = v.getView(item);
2183 Object name = cv.getElement().getAttributes().getAttribute(
2184 StyleConstants.NameAttribute);
2185 // Only draw something if the View is a list item. This won't
2186 // be the case for comments.
2187 if (!(name instanceof HTML.Tag) || name != HTML.Tag.LI) {
2188 return;
2189 }
2190 // deside on what side draw bullets, etc.
2191 isLeftToRight = cv.getContainer().getComponentOrientation()
2192 .isLeftToRight();
2193
2194 // How the list indicator is aligned is not specified, it is
2195 // left up to the UA. IE and NS differ on this behavior.
2196 // This is closer to NS where we align to the first line of text.
2197 // If the child is not text we draw the indicator at the
2198 // origin (0).
2199 float align = 0;
2200 if (cv.getViewCount() > 0) {
2201 View pView = cv.getView(0);
2202 Object cName = pView.getElement().getAttributes()
2203 .getAttribute(StyleConstants.NameAttribute);
2204 if ((cName == HTML.Tag.P || cName == HTML.Tag.IMPLIED)
2205 && pView.getViewCount() > 0) {
2206 paintRect.setBounds((int) x, (int) y, (int) w,
2207 (int) h);
2208 Shape shape = cv.getChildAllocation(0, paintRect);
2209 if (shape != null
2210 && (shape = pView.getView(0)
2211 .getChildAllocation(0, shape)) != null) {
2212 Rectangle rect = (shape instanceof Rectangle) ? (Rectangle) shape
2213 : shape.getBounds();
2214
2215 align = pView.getView(0).getAlignment(
2216 View.Y_AXIS);
2217 y = rect.y;
2218 h = rect.height;
2219 }
2220 }
2221 }
2222
2223 // set the color of a decoration
2224 if (ss != null) {
2225 g.setColor(ss.getForeground(cv.getAttributes()));
2226 } else {
2227 g.setColor(Color.black);
2228 }
2229
2230 if (img != null) {
2231 drawIcon(g, (int) x, (int) y, (int) w, (int) h, align,
2232 v.getContainer());
2233 return;
2234 }
2235 CSS.Value childtype = getChildType(cv);
2236 Font font = ((StyledDocument) cv.getDocument()).getFont(cv
2237 .getAttributes());
2238 if (font != null) {
2239 g.setFont(font);
2240 }
2241 if (childtype == CSS.Value.SQUARE
2242 || childtype == CSS.Value.CIRCLE
2243 || childtype == CSS.Value.DISC) {
2244 drawShape(g, childtype, (int) x, (int) y, (int) w,
2245 (int) h, align);
2246 } else if (childtype == CSS.Value.DECIMAL) {
2247 drawLetter(g, '1', (int) x, (int) y, (int) w, (int) h,
2248 align, getRenderIndex(v, item));
2249 } else if (childtype == CSS.Value.LOWER_ALPHA) {
2250 drawLetter(g, 'a', (int) x, (int) y, (int) w, (int) h,
2251 align, getRenderIndex(v, item));
2252 } else if (childtype == CSS.Value.UPPER_ALPHA) {
2253 drawLetter(g, 'A', (int) x, (int) y, (int) w, (int) h,
2254 align, getRenderIndex(v, item));
2255 } else if (childtype == CSS.Value.LOWER_ROMAN) {
2256 drawLetter(g, 'i', (int) x, (int) y, (int) w, (int) h,
2257 align, getRenderIndex(v, item));
2258 } else if (childtype == CSS.Value.UPPER_ROMAN) {
2259 drawLetter(g, 'I', (int) x, (int) y, (int) w, (int) h,
2260 align, getRenderIndex(v, item));
2261 }
2262 }
2263
2264 /**
2265 * Draws the bullet icon specified by the list-style-image argument.
2266 *
2267 * @param g the graphics context
2268 * @param ax x coordinate to place the bullet
2269 * @param ay y coordinate to place the bullet
2270 * @param aw width of the container the bullet is placed in
2271 * @param ah height of the container the bullet is placed in
2272 * @param align preferred alignment factor for the child view
2273 */
2274 void drawIcon(Graphics g, int ax, int ay, int aw, int ah,
2275 float align, Component c) {
2276 // Align to bottom of icon.
2277 int gap = isLeftToRight ? -(img.getIconWidth() + bulletgap)
2278 : (aw + bulletgap);
2279 int x = ax + gap;
2280 int y = Math.max(ay, ay + (int) (align * ah)
2281 - img.getIconHeight());
2282
2283 img.paintIcon(c, g, x, y);
2284 }
2285
2286 /**
2287 * Draws the graphical bullet item specified by the type argument.
2288 *
2289 * @param g the graphics context
2290 * @param type type of bullet to draw (circle, square, disc)
2291 * @param ax x coordinate to place the bullet
2292 * @param ay y coordinate to place the bullet
2293 * @param aw width of the container the bullet is placed in
2294 * @param ah height of the container the bullet is placed in
2295 * @param align preferred alignment factor for the child view
2296 */
2297 void drawShape(Graphics g, CSS.Value type, int ax, int ay,
2298 int aw, int ah, float align) {
2299 // Align to bottom of shape.
2300 int gap = isLeftToRight ? -(bulletgap + 8)
2301 : (aw + bulletgap);
2302 int x = ax + gap;
2303 int y = Math.max(ay, ay + (int) (align * ah) - 8);
2304
2305 if (type == CSS.Value.SQUARE) {
2306 g.drawRect(x, y, 8, 8);
2307 } else if (type == CSS.Value.CIRCLE) {
2308 g.drawOval(x, y, 8, 8);
2309 } else {
2310 g.fillOval(x, y, 8, 8);
2311 }
2312 }
2313
2314 /**
2315 * Draws the letter or number for an ordered list.
2316 *
2317 * @param g the graphics context
2318 * @param letter type of ordered list to draw
2319 * @param ax x coordinate to place the bullet
2320 * @param ay y coordinate to place the bullet
2321 * @param aw width of the container the bullet is placed in
2322 * @param ah height of the container the bullet is placed in
2323 * @param index position of the list item in the list
2324 */
2325 void drawLetter(Graphics g, char letter, int ax, int ay,
2326 int aw, int ah, float align, int index) {
2327 String str = formatItemNum(index, letter);
2328 str = isLeftToRight ? str + "." : "." + str;
2329 FontMetrics fm = SwingUtilities2.getFontMetrics(null, g);
2330 int stringwidth = SwingUtilities2
2331 .stringWidth(null, fm, str);
2332 int gap = isLeftToRight ? -(stringwidth + bulletgap)
2333 : (aw + bulletgap);
2334 int x = ax + gap;
2335 int y = Math.max(ay + fm.getAscent(), ay
2336 + (int) (ah * align));
2337 SwingUtilities2.drawString(null, g, str, x, y);
2338 }
2339
2340 /**
2341 * Converts the item number into the ordered list number
2342 * (i.e. 1 2 3, i ii iii, a b c, etc.
2343 *
2344 * @param itemNum number to format
2345 * @param type type of ordered list
2346 */
2347 String formatItemNum(int itemNum, char type) {
2348 String numStyle = "1";
2349
2350 boolean uppercase = false;
2351
2352 String formattedNum;
2353
2354 switch (type) {
2355 case '1':
2356 default:
2357 formattedNum = String.valueOf(itemNum);
2358 break;
2359
2360 case 'A':
2361 uppercase = true;
2362 // fall through
2363 case 'a':
2364 formattedNum = formatAlphaNumerals(itemNum);
2365 break;
2366
2367 case 'I':
2368 uppercase = true;
2369 // fall through
2370 case 'i':
2371 formattedNum = formatRomanNumerals(itemNum);
2372 }
2373
2374 if (uppercase) {
2375 formattedNum = formattedNum.toUpperCase();
2376 }
2377
2378 return formattedNum;
2379 }
2380
2381 /**
2382 * Converts the item number into an alphabetic character
2383 *
2384 * @param itemNum number to format
2385 */
2386 String formatAlphaNumerals(int itemNum) {
2387 String result = "";
2388
2389 if (itemNum > 26) {
2390 result = formatAlphaNumerals(itemNum / 26)
2391 + formatAlphaNumerals(itemNum % 26);
2392 } else {
2393 // -1 because item is 1 based.
2394 result = String.valueOf((char) ('a' + itemNum - 1));
2395 }
2396
2397 return result;
2398 }
2399
2400 /* list of roman numerals */
2401 static final char romanChars[][] = { { 'i', 'v' },
2402 { 'x', 'l' }, { 'c', 'd' }, { 'm', '?' }, };
2403
2404 /**
2405 * Converts the item number into a roman numeral
2406 *
2407 * @param num number to format
2408 */
2409 String formatRomanNumerals(int num) {
2410 return formatRomanNumerals(0, num);
2411 }
2412
2413 /**
2414 * Converts the item number into a roman numeral
2415 *
2416 * @param num number to format
2417 */
2418 String formatRomanNumerals(int level, int num) {
2419 if (num < 10) {
2420 return formatRomanDigit(level, num);
2421 } else {
2422 return formatRomanNumerals(level + 1, num / 10)
2423 + formatRomanDigit(level, num % 10);
2424 }
2425 }
2426
2427 /**
2428 * Converts the item number into a roman numeral
2429 *
2430 * @param level position
2431 * @param num digit to format
2432 */
2433 String formatRomanDigit(int level, int digit) {
2434 String result = "";
2435 if (digit == 9) {
2436 result = result + romanChars[level][0];
2437 result = result + romanChars[level + 1][0];
2438 return result;
2439 } else if (digit == 4) {
2440 result = result + romanChars[level][0];
2441 result = result + romanChars[level][1];
2442 return result;
2443 } else if (digit >= 5) {
2444 result = result + romanChars[level][1];
2445 digit -= 5;
2446 }
2447
2448 for (int i = 0; i < digit; i++) {
2449 result = result + romanChars[level][0];
2450 }
2451
2452 return result;
2453 }
2454
2455 private Rectangle paintRect;
2456 private boolean checkedForStart;
2457 private int start;
2458 private CSS.Value type;
2459 URL imageurl;
2460 private StyleSheet ss = null;
2461 Icon img = null;
2462 private int bulletgap = 5;
2463 private boolean isLeftToRight;
2464 }
2465
2466 /**
2467 * Paints the background image.
2468 */
2469 static class BackgroundImagePainter implements Serializable {
2470 ImageIcon backgroundImage;
2471 float hPosition;
2472 float vPosition;
2473 // bit mask: 0 for repeat x, 1 for repeat y, 2 for horiz relative,
2474 // 3 for vert relative
2475 short flags;
2476 // These are used when painting, updatePaintCoordinates updates them.
2477 private int paintX;
2478 private int paintY;
2479 private int paintMaxX;
2480 private int paintMaxY;
2481
2482 BackgroundImagePainter(AttributeSet a, CSS css, StyleSheet ss) {
2483 backgroundImage = ss.getBackgroundImage(a);
2484 // Determine the position.
2485 CSS.BackgroundPosition pos = (CSS.BackgroundPosition) a
2486 .getAttribute(CSS.Attribute.BACKGROUND_POSITION);
2487 if (pos != null) {
2488 hPosition = pos.getHorizontalPosition();
2489 vPosition = pos.getVerticalPosition();
2490 if (pos.isHorizontalPositionRelativeToSize()) {
2491 flags |= 4;
2492 } else if (pos.isHorizontalPositionRelativeToSize()) {
2493 hPosition *= css.getFontSize(a, 12, ss);
2494 }
2495 if (pos.isVerticalPositionRelativeToSize()) {
2496 flags |= 8;
2497 } else if (pos.isVerticalPositionRelativeToFontSize()) {
2498 vPosition *= css.getFontSize(a, 12, ss);
2499 }
2500 }
2501 // Determine any repeating values.
2502 CSS.Value repeats = (CSS.Value) a
2503 .getAttribute(CSS.Attribute.BACKGROUND_REPEAT);
2504 if (repeats == null
2505 || repeats == CSS.Value.BACKGROUND_REPEAT) {
2506 flags |= 3;
2507 } else if (repeats == CSS.Value.BACKGROUND_REPEAT_X) {
2508 flags |= 1;
2509 } else if (repeats == CSS.Value.BACKGROUND_REPEAT_Y) {
2510 flags |= 2;
2511 }
2512 }
2513
2514 void paint(Graphics g, float x, float y, float w, float h,
2515 View v) {
2516 Rectangle clip = g.getClipRect();
2517 if (clip != null) {
2518 // Constrain the clip so that images don't draw outside the
2519 // legal bounds.
2520 g.clipRect((int) x, (int) y, (int) w, (int) h);
2521 }
2522 if ((flags & 3) == 0) {
2523 // no repeating
2524 int width = backgroundImage.getIconWidth();
2525 int height = backgroundImage.getIconWidth();
2526 if ((flags & 4) == 4) {
2527 paintX = (int) (x + w * hPosition - (float) width
2528 * hPosition);
2529 } else {
2530 paintX = (int) x + (int) hPosition;
2531 }
2532 if ((flags & 8) == 8) {
2533 paintY = (int) (y + h * vPosition - (float) height
2534 * vPosition);
2535 } else {
2536 paintY = (int) y + (int) vPosition;
2537 }
2538 if (clip == null
2539 || !((paintX + width <= clip.x)
2540 || (paintY + height <= clip.y)
2541 || (paintX >= clip.x + clip.width) || (paintY >= clip.y
2542 + clip.height))) {
2543 backgroundImage.paintIcon(null, g, paintX, paintY);
2544 }
2545 } else {
2546 int width = backgroundImage.getIconWidth();
2547 int height = backgroundImage.getIconHeight();
2548 if (width > 0 && height > 0) {
2549 paintX = (int) x;
2550 paintY = (int) y;
2551 paintMaxX = (int) (x + w);
2552 paintMaxY = (int) (y + h);
2553 if (updatePaintCoordinates(clip, width, height)) {
2554 while (paintX < paintMaxX) {
2555 int ySpot = paintY;
2556 while (ySpot < paintMaxY) {
2557 backgroundImage.paintIcon(null, g,
2558 paintX, ySpot);
2559 ySpot += height;
2560 }
2561 paintX += width;
2562 }
2563 }
2564 }
2565 }
2566 if (clip != null) {
2567 // Reset clip.
2568 g.setClip(clip.x, clip.y, clip.width, clip.height);
2569 }
2570 }
2571
2572 private boolean updatePaintCoordinates(Rectangle clip,
2573 int width, int height) {
2574 if ((flags & 3) == 1) {
2575 paintMaxY = paintY + 1;
2576 } else if ((flags & 3) == 2) {
2577 paintMaxX = paintX + 1;
2578 }
2579 if (clip != null) {
2580 if ((flags & 3) == 1
2581 && ((paintY + height <= clip.y) || (paintY > clip.y
2582 + clip.height))) {
2583 // not visible.
2584 return false;
2585 }
2586 if ((flags & 3) == 2
2587 && ((paintX + width <= clip.x) || (paintX > clip.x
2588 + clip.width))) {
2589 // not visible.
2590 return false;
2591 }
2592 if ((flags & 1) == 1) {
2593 if ((clip.x + clip.width) < paintMaxX) {
2594 if ((clip.x + clip.width - paintX) % width == 0) {
2595 paintMaxX = clip.x + clip.width;
2596 } else {
2597 paintMaxX = ((clip.x + clip.width - paintX)
2598 / width + 1)
2599 * width + paintX;
2600 }
2601 }
2602 if (clip.x > paintX) {
2603 paintX = (clip.x - paintX) / width * width
2604 + paintX;
2605 }
2606 }
2607 if ((flags & 2) == 2) {
2608 if ((clip.y + clip.height) < paintMaxY) {
2609 if ((clip.y + clip.height - paintY) % height == 0) {
2610 paintMaxY = clip.y + clip.height;
2611 } else {
2612 paintMaxY = ((clip.y + clip.height - paintY)
2613 / height + 1)
2614 * height + paintY;
2615 }
2616 }
2617 if (clip.y > paintY) {
2618 paintY = (clip.y - paintY) / height * height
2619 + paintY;
2620 }
2621 }
2622 }
2623 // Valid
2624 return true;
2625 }
2626 }
2627
2628 /**
2629 * A subclass of MuxingAttributeSet that translates between
2630 * CSS and HTML and StyleConstants. The AttributeSets used are
2631 * the CSS rules that match the Views Elements.
2632 */
2633 class ViewAttributeSet extends MuxingAttributeSet {
2634 ViewAttributeSet(View v) {
2635 host = v;
2636
2637 // PENDING(prinz) fix this up to be a more realistic
2638 // implementation.
2639 Document doc = v.getDocument();
2640 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
2641 Vector muxList = sb.getVector();
2642 try {
2643 if (doc instanceof HTMLDocument) {
2644 StyleSheet styles = StyleSheet.this ;
2645 Element elem = v.getElement();
2646 AttributeSet a = elem.getAttributes();
2647 AttributeSet htmlAttr = styles
2648 .translateHTMLToCSS(a);
2649
2650 if (htmlAttr.getAttributeCount() != 0) {
2651 muxList.addElement(htmlAttr);
2652 }
2653 if (elem.isLeaf()) {
2654 Enumeration keys = a.getAttributeNames();
2655 while (keys.hasMoreElements()) {
2656 Object key = keys.nextElement();
2657 if (key instanceof HTML.Tag) {
2658 if ((HTML.Tag) key == HTML.Tag.A) {
2659 Object o = a
2660 .getAttribute((HTML.Tag) key);
2661 /**
2662 In the case of an A tag, the css rules
2663 apply only for tags that have their
2664 href attribute defined and not for
2665 anchors that only have their name attributes
2666 defined, i.e anchors that function as
2667 destinations. Hence we do not add the
2668 attributes for that latter kind of
2669 anchors. When CSS2 support is added,
2670 it will be possible to specificity this
2671 kind of conditional behaviour in the
2672 stylesheet.
2673 **/
2674 if (o != null
2675 && o instanceof AttributeSet) {
2676 AttributeSet attr = (AttributeSet) o;
2677 if (attr
2678 .getAttribute(HTML.Attribute.HREF) == null) {
2679 continue;
2680 }
2681 }
2682 }
2683 AttributeSet cssRule = styles.getRule(
2684 (HTML.Tag) key, elem);
2685 if (cssRule != null) {
2686 muxList.addElement(cssRule);
2687 }
2688 }
2689 }
2690 } else {
2691 HTML.Tag t = (HTML.Tag) a
2692 .getAttribute(StyleConstants.NameAttribute);
2693 AttributeSet cssRule = styles.getRule(t, elem);
2694 if (cssRule != null) {
2695 muxList.addElement(cssRule);
2696 }
2697 }
2698 }
2699 AttributeSet[] attrs = new AttributeSet[muxList.size()];
2700 muxList.copyInto(attrs);
2701 setAttributes(attrs);
2702 } finally {
2703 SearchBuffer.releaseSearchBuffer(sb);
2704 }
2705 }
2706
2707 // --- AttributeSet methods ----------------------------
2708
2709 /**
2710 * Checks whether a given attribute is defined.
2711 * This will convert the key over to CSS if the
2712 * key is a StyleConstants key that has a CSS
2713 * mapping.
2714 *
2715 * @param key the attribute key
2716 * @return true if the attribute is defined
2717 * @see AttributeSet#isDefined
2718 */
2719 public boolean isDefined(Object key) {
2720 if (key instanceof StyleConstants) {
2721 Object cssKey = css
2722 .styleConstantsKeyToCSSKey((StyleConstants) key);
2723 if (cssKey != null) {
2724 key = cssKey;
2725 }
2726 }
2727 return super .isDefined(key);
2728 }
2729
2730 /**
2731 * Gets the value of an attribute. If the requested
2732 * attribute is a StyleConstants attribute that has
2733 * a CSS mapping, the request will be converted.
2734 *
2735 * @param key the attribute name
2736 * @return the attribute value
2737 * @see AttributeSet#getAttribute
2738 */
2739 public Object getAttribute(Object key) {
2740 if (key instanceof StyleConstants) {
2741 Object cssKey = css
2742 .styleConstantsKeyToCSSKey((StyleConstants) key);
2743 if (cssKey != null) {
2744 Object value = doGetAttribute(cssKey);
2745 if (value instanceof CSS.CssValue) {
2746 return ((CSS.CssValue) value).toStyleConstants(
2747 (StyleConstants) key, host);
2748 }
2749 }
2750 }
2751 return doGetAttribute(key);
2752 }
2753
2754 Object doGetAttribute(Object key) {
2755 Object retValue = super .getAttribute(key);
2756 if (retValue != null) {
2757 return retValue;
2758 }
2759 // didn't find it... try parent if it's a css attribute
2760 // that is inherited.
2761 if (key instanceof CSS.Attribute) {
2762 CSS.Attribute css = (CSS.Attribute) key;
2763 if (css.isInherited()) {
2764 AttributeSet parent = getResolveParent();
2765 if (parent != null)
2766 return parent.getAttribute(key);
2767 }
2768 }
2769 return null;
2770 }
2771
2772 /**
2773 * If not overriden, the resolving parent defaults to
2774 * the parent element.
2775 *
2776 * @return the attributes from the parent
2777 * @see AttributeSet#getResolveParent
2778 */
2779 public AttributeSet getResolveParent() {
2780 if (host == null) {
2781 return null;
2782 }
2783 View parent = host.getParent();
2784 return (parent != null) ? parent.getAttributes() : null;
2785 }
2786
2787 /** View created for. */
2788 View host;
2789 }
2790
2791 /**
2792 * A subclass of MuxingAttributeSet that implements Style. Currently
2793 * the MutableAttributeSet methods are unimplemented, that is they
2794 * do nothing.
2795 */
2796 // PENDING(sky): Decide what to do with this. Either make it
2797 // contain a SimpleAttributeSet that modify methods are delegated to,
2798 // or change getRule to return an AttributeSet and then don't make this
2799 // implement Style.
2800 static class ResolvedStyle extends MuxingAttributeSet implements
2801 Serializable, Style {
2802 ResolvedStyle(String name, AttributeSet[] attrs,
2803 int extendedIndex) {
2804 super (attrs);
2805 this .name = name;
2806 this .extendedIndex = extendedIndex;
2807 }
2808
2809 /**
2810 * Inserts a Style into the receiver so that the styles the
2811 * receiver represents are still ordered by specificity.
2812 * <code>style</code> will be added before any extended styles, that
2813 * is before extendedIndex.
2814 */
2815 synchronized void insertStyle(Style style, int specificity) {
2816 AttributeSet[] attrs = getAttributes();
2817 int maxCounter = attrs.length;
2818 int counter = 0;
2819 for (; counter < extendedIndex; counter++) {
2820 if (specificity > getSpecificity(((Style) attrs[counter])
2821 .getName())) {
2822 break;
2823 }
2824 }
2825 insertAttributeSetAt(style, counter);
2826 extendedIndex++;
2827 }
2828
2829 /**
2830 * Removes a previously added style. This will do nothing if
2831 * <code>style</code> is not referenced by the receiver.
2832 */
2833 synchronized void removeStyle(Style style) {
2834 AttributeSet[] attrs = getAttributes();
2835
2836 for (int counter = attrs.length - 1; counter >= 0; counter--) {
2837 if (attrs[counter] == style) {
2838 removeAttributeSetAt(counter);
2839 if (counter < extendedIndex) {
2840 extendedIndex--;
2841 }
2842 break;
2843 }
2844 }
2845 }
2846
2847 /**
2848 * Adds <code>s</code> as one of the Attributesets to look up
2849 * attributes in.
2850 */
2851 synchronized void insertExtendedStyleAt(Style attr, int index) {
2852 insertAttributeSetAt(attr, extendedIndex + index);
2853 }
2854
2855 /**
2856 * Adds <code>s</code> as one of the AttributeSets to look up
2857 * attributes in. It will be the AttributeSet last checked.
2858 */
2859 synchronized void addExtendedStyle(Style attr) {
2860 insertAttributeSetAt(attr, getAttributes().length);
2861 }
2862
2863 /**
2864 * Removes the style at <code>index</code> +
2865 * <code>extendedIndex</code>.
2866 */
2867 synchronized void removeExtendedStyleAt(int index) {
2868 removeAttributeSetAt(extendedIndex + index);
2869 }
2870
2871 /**
2872 * Returns true if the receiver matches <code>selector</code>, where
2873 * a match is defined by the CSS rule matching.
2874 * Each simple selector must be separated by a single space.
2875 */
2876 protected boolean matches(String selector) {
2877 int sLast = selector.length();
2878
2879 if (sLast == 0) {
2880 return false;
2881 }
2882 int this Last = name.length();
2883 int sCurrent = selector.lastIndexOf(' ');
2884 int this Current = name.lastIndexOf(' ');
2885 if (sCurrent >= 0) {
2886 sCurrent++;
2887 }
2888 if (this Current >= 0) {
2889 this Current++;
2890 }
2891 if (!matches(selector, sCurrent, sLast, this Current,
2892 this Last)) {
2893 return false;
2894 }
2895 while (sCurrent != -1) {
2896 sLast = sCurrent - 1;
2897 sCurrent = selector.lastIndexOf(' ', sLast - 1);
2898 if (sCurrent >= 0) {
2899 sCurrent++;
2900 }
2901 boolean match = false;
2902 while (!match && this Current != -1) {
2903 this Last = this Current - 1;
2904 this Current = name.lastIndexOf(' ', this Last - 1);
2905 if (this Current >= 0) {
2906 this Current++;
2907 }
2908 match = matches(selector, sCurrent, sLast,
2909 this Current, this Last);
2910 }
2911 if (!match) {
2912 return false;
2913 }
2914 }
2915 return true;
2916 }
2917
2918 /**
2919 * Returns true if the substring of the receiver, in the range
2920 * thisCurrent, thisLast matches the substring of selector in
2921 * the ranme sCurrent to sLast based on CSS selector matching.
2922 */
2923 boolean matches(String selector, int sCurrent, int sLast,
2924 int this Current, int this Last) {
2925 sCurrent = Math.max(sCurrent, 0);
2926 this Current = Math.max(this Current, 0);
2927 int this DotIndex = boundedIndexOf(name, '.', this Current,
2928 this Last);
2929 int this PoundIndex = boundedIndexOf(name, '#', this Current,
2930 this Last);
2931 int sDotIndex = boundedIndexOf(selector, '.', sCurrent,
2932 sLast);
2933 int sPoundIndex = boundedIndexOf(selector, '#', sCurrent,
2934 sLast);
2935 if (sDotIndex != -1) {
2936 // Selector has a '.', which indicates name must match it,
2937 // or if the '.' starts the selector than name must have
2938 // the same class (doesn't matter what element name).
2939 if (this DotIndex == -1) {
2940 return false;
2941 }
2942 if (sCurrent == sDotIndex) {
2943 if ((this Last - this DotIndex) != (sLast - sDotIndex)
2944 || !selector.regionMatches(sCurrent, name,
2945 this DotIndex,
2946 (this Last - this DotIndex))) {
2947 return false;
2948 }
2949 } else {
2950 // Has to fully match.
2951 if ((sLast - sCurrent) != (this Last - this Current)
2952 || !selector.regionMatches(sCurrent, name,
2953 this Current,
2954 (this Last - this Current))) {
2955 return false;
2956 }
2957 }
2958 return true;
2959 }
2960 if (sPoundIndex != -1) {
2961 // Selector has a '#', which indicates name must match it,
2962 // or if the '#' starts the selector than name must have
2963 // the same id (doesn't matter what element name).
2964 if (this PoundIndex == -1) {
2965 return false;
2966 }
2967 if (sCurrent == sPoundIndex) {
2968 if ((this Last - this PoundIndex) != (sLast - sPoundIndex)
2969 || !selector.regionMatches(sCurrent, name,
2970 this PoundIndex,
2971 (this Last - this PoundIndex))) {
2972 return false;
2973 }
2974 } else {
2975 // Has to fully match.
2976 if ((sLast - sCurrent) != (this Last - this Current)
2977 || !selector.regionMatches(sCurrent, name,
2978 this Current,
2979 (this Last - this Current))) {
2980 return false;
2981 }
2982 }
2983 return true;
2984 }
2985 if (this DotIndex != -1) {
2986 // Reciever references a class, just check element name.
2987 return (((this DotIndex - this Current) == (sLast - sCurrent)) && selector
2988 .regionMatches(sCurrent, name, this Current,
2989 this DotIndex - this Current));
2990 }
2991 if (this PoundIndex != -1) {
2992 // Reciever references an id, just check element name.
2993 return (((this PoundIndex - this Current) == (sLast - sCurrent)) && selector
2994 .regionMatches(sCurrent, name, this Current,
2995 this PoundIndex - this Current));
2996 }
2997 // Fail through, no classes or ides, just check string.
2998 return (((this Last - this Current) == (sLast - sCurrent)) && selector
2999 .regionMatches(sCurrent, name, this Current,
3000 this Last - this Current));
3001 }
3002
3003 /**
3004 * Similiar to String.indexOf, but allows an upper bound
3005 * (this is slower in that it will still check string starting at
3006 * start.
3007 */
3008 int boundedIndexOf(String string, char search, int start,
3009 int end) {
3010 int retValue = string.indexOf(search, start);
3011 if (retValue >= end) {
3012 return -1;
3013 }
3014 return retValue;
3015 }
3016
3017 public void addAttribute(Object name, Object value) {
3018 }
3019
3020 public void addAttributes(AttributeSet attributes) {
3021 }
3022
3023 public void removeAttribute(Object name) {
3024 }
3025
3026 public void removeAttributes(Enumeration<?> names) {
3027 }
3028
3029 public void removeAttributes(AttributeSet attributes) {
3030 }
3031
3032 public void setResolveParent(AttributeSet parent) {
3033 }
3034
3035 public String getName() {
3036 return name;
3037 }
3038
3039 public void addChangeListener(ChangeListener l) {
3040 }
3041
3042 public void removeChangeListener(ChangeListener l) {
3043 }
3044
3045 public ChangeListener[] getChangeListeners() {
3046 return new ChangeListener[0];
3047 }
3048
3049 /** The name of the Style, which is the selector.
3050 * This will NEVER change!
3051 */
3052 String name;
3053 /** Start index of styles coming from other StyleSheets. */
3054 private int extendedIndex;
3055 }
3056
3057 /**
3058 * SelectorMapping contains a specifitiy, as an integer, and an associated
3059 * Style. It can also reference children <code>SelectorMapping</code>s,
3060 * so that it behaves like a tree.
3061 * <p>
3062 * This is not thread safe, it is assumed the caller will take the
3063 * necessary precations if this is to be used in a threaded environment.
3064 */
3065 static class SelectorMapping implements Serializable {
3066 public SelectorMapping(int specificity) {
3067 this .specificity = specificity;
3068 }
3069
3070 /**
3071 * Returns the specificity this mapping represents.
3072 */
3073 public int getSpecificity() {
3074 return specificity;
3075 }
3076
3077 /**
3078 * Sets the Style associated with this mapping.
3079 */
3080 public void setStyle(Style style) {
3081 this .style = style;
3082 }
3083
3084 /**
3085 * Returns the Style associated with this mapping.
3086 */
3087 public Style getStyle() {
3088 return style;
3089 }
3090
3091 /**
3092 * Returns the child mapping identified by the simple selector
3093 * <code>selector</code>. If a child mapping does not exist for
3094 *<code>selector</code>, and <code>create</code> is true, a new
3095 * one will be created.
3096 */
3097 public SelectorMapping getChildSelectorMapping(String selector,
3098 boolean create) {
3099 SelectorMapping retValue = null;
3100
3101 if (children != null) {
3102 retValue = (SelectorMapping) children.get(selector);
3103 } else if (create) {
3104 children = new HashMap(7);
3105 }
3106 if (retValue == null && create) {
3107 int specificity = getChildSpecificity(selector);
3108
3109 retValue = createChildSelectorMapping(specificity);
3110 children.put(selector, retValue);
3111 }
3112 return retValue;
3113 }
3114
3115 /**
3116 * Creates a child <code>SelectorMapping</code> with the specified
3117 * <code>specificity</code>.
3118 */
3119 protected SelectorMapping createChildSelectorMapping(
3120 int specificity) {
3121 return new SelectorMapping(specificity);
3122 }
3123
3124 /**
3125 * Returns the specificity for the child selector
3126 * <code>selector</code>.
3127 */
3128 protected int getChildSpecificity(String selector) {
3129 // class (.) 100
3130 // id (#) 10000
3131 char firstChar = selector.charAt(0);
3132 int specificity = getSpecificity();
3133
3134 if (firstChar == '.') {
3135 specificity += 100;
3136 } else if (firstChar == '#') {
3137 specificity += 10000;
3138 } else {
3139 specificity += 1;
3140 if (selector.indexOf('.') != -1) {
3141 specificity += 100;
3142 }
3143 if (selector.indexOf('#') != -1) {
3144 specificity += 10000;
3145 }
3146 }
3147 return specificity;
3148 }
3149
3150 /**
3151 * The specificity for this selector.
3152 */
3153 private int specificity;
3154 /**
3155 * Style for this selector.
3156 */
3157 private Style style;
3158 /**
3159 * Any sub selectors. Key will be String, and value will be
3160 * another SelectorMapping.
3161 */
3162 private HashMap children;
3163 }
3164
3165 // ---- Variables ---------------------------------------------
3166
3167 final static int DEFAULT_FONT_SIZE = 3;
3168
3169 private CSS css;
3170
3171 /**
3172 * An inverted graph of the selectors.
3173 */
3174 private SelectorMapping selectorMapping;
3175
3176 /** Maps from selector (as a string) to Style that includes all
3177 * relevant styles. */
3178 private Hashtable resolvedStyles;
3179
3180 /** Vector of StyleSheets that the rules are to reference.
3181 */
3182 private Vector linkedStyleSheets;
3183
3184 /** Where the style sheet was found. Used for relative imports. */
3185 private URL base;
3186
3187 /**
3188 * Default parser for CSS specifications that get loaded into
3189 * the StyleSheet.<p>
3190 * This class is NOT thread safe, do not ask it to parse while it is
3191 * in the middle of parsing.
3192 */
3193 class CssParser implements CSSParser.CSSParserCallback {
3194
3195 /**
3196 * Parses the passed in CSS declaration into an AttributeSet.
3197 */
3198 public AttributeSet parseDeclaration(String string) {
3199 try {
3200 return parseDeclaration(new StringReader(string));
3201 } catch (IOException ioe) {
3202 }
3203 return null;
3204 }
3205
3206 /**
3207 * Parses the passed in CSS declaration into an AttributeSet.
3208 */
3209 public AttributeSet parseDeclaration(Reader r)
3210 throws IOException {
3211 parse(base, r, true, false);
3212 return declaration.copyAttributes();
3213 }
3214
3215 /**
3216 * Parse the given CSS stream
3217 */
3218 public void parse(URL base, Reader r, boolean parseDeclaration,
3219 boolean isLink) throws IOException {
3220 this .base = base;
3221 this .isLink = isLink;
3222 this .parsingDeclaration = parseDeclaration;
3223 declaration.removeAttributes(declaration);
3224 selectorTokens.removeAllElements();
3225 selectors.removeAllElements();
3226 propertyName = null;
3227 parser.parse(r, this , parseDeclaration);
3228 }
3229
3230 //
3231 // CSSParserCallback methods, public to implement the interface.
3232 //
3233
3234 /**
3235 * Invoked when a valid @import is encountered, will call
3236 * <code>importStyleSheet</code> if a
3237 * <code>MalformedURLException</code> is not thrown in creating
3238 * the URL.
3239 */
3240 public void handleImport(String importString) {
3241 URL url = CSS.getURL(base, importString);
3242 if (url != null) {
3243 importStyleSheet(url);
3244 }
3245 }
3246
3247 /**
3248 * A selector has been encountered.
3249 */
3250 public void handleSelector(String selector) {
3251 //class and index selectors are case sensitive
3252 if (!(selector.startsWith(".") || selector.startsWith("#"))) {
3253 selector = selector.toLowerCase();
3254 }
3255 int length = selector.length();
3256
3257 if (selector.endsWith(",")) {
3258 if (length > 1) {
3259 selector = selector.substring(0, length - 1);
3260 selectorTokens.addElement(selector);
3261 }
3262 addSelector();
3263 } else if (length > 0) {
3264 selectorTokens.addElement(selector);
3265 }
3266 }
3267
3268 /**
3269 * Invoked when the start of a rule is encountered.
3270 */
3271 public void startRule() {
3272 if (selectorTokens.size() > 0) {
3273 addSelector();
3274 }
3275 propertyName = null;
3276 }
3277
3278 /**
3279 * Invoked when a property name is encountered.
3280 */
3281 public void handleProperty(String property) {
3282 propertyName = property;
3283 }
3284
3285 /**
3286 * Invoked when a property value is encountered.
3287 */
3288 public void handleValue(String value) {
3289 if (propertyName != null && value != null
3290 && value.length() > 0) {
3291 CSS.Attribute cssKey = CSS.getAttribute(propertyName);
3292 if (cssKey != null) {
3293 // There is currently no mechanism to determine real
3294 // base that style sheet was loaded from. For the time
3295 // being, this maps for LIST_STYLE_IMAGE, which appear
3296 // to be the only one that currently matters. A more
3297 // general mechanism is definately needed.
3298 if (cssKey == CSS.Attribute.LIST_STYLE_IMAGE) {
3299 if (value != null && !value.equals("none")) {
3300 URL url = CSS.getURL(base, value);
3301
3302 if (url != null) {
3303 value = url.toString();
3304 }
3305 }
3306 }
3307 addCSSAttribute(declaration, cssKey, value);
3308 }
3309 propertyName = null;
3310 }
3311 }
3312
3313 /**
3314 * Invoked when the end of a rule is encountered.
3315 */
3316 public void endRule() {
3317 int n = selectors.size();
3318 for (int i = 0; i < n; i++) {
3319 String[] selector = (String[]) selectors.elementAt(i);
3320 if (selector.length > 0) {
3321 StyleSheet.this .addRule(selector, declaration,
3322 isLink);
3323 }
3324 }
3325 declaration.removeAttributes(declaration);
3326 selectors.removeAllElements();
3327 }
3328
3329 private void addSelector() {
3330 String[] selector = new String[selectorTokens.size()];
3331 selectorTokens.copyInto(selector);
3332 selectors.addElement(selector);
3333 selectorTokens.removeAllElements();
3334 }
3335
3336 Vector selectors = new Vector();
3337 Vector selectorTokens = new Vector();
3338 /** Name of the current property. */
3339 String propertyName;
3340 MutableAttributeSet declaration = new SimpleAttributeSet();
3341 /** True if parsing a declaration, that is the Reader will not
3342 * contain a selector. */
3343 boolean parsingDeclaration;
3344 /** True if the attributes are coming from a linked/imported style. */
3345 boolean isLink;
3346 /** Where the CSS stylesheet lives. */
3347 URL base;
3348 CSSParser parser = new CSSParser();
3349 }
3350
3351 void rebaseSizeMap(int base) {
3352 final int minimalFontSize = 4;
3353 sizeMap = new int[sizeMapDefault.length];
3354 for (int i = 0; i < sizeMapDefault.length; i++) {
3355 sizeMap[i] = Math.max(base * sizeMapDefault[i]
3356 / sizeMapDefault[CSS.baseFontSizeIndex],
3357 minimalFontSize);
3358 }
3359
3360 }
3361
3362 int[] getSizeMap() {
3363 return sizeMap;
3364 }
3365
3366 boolean isW3CLengthUnits() {
3367 return w3cLengthUnits;
3368 }
3369
3370 /**
3371 * The HTML/CSS size model has seven slots
3372 * that one can assign sizes to.
3373 */
3374 static final int sizeMapDefault[] = { 8, 10, 12, 14, 18, 24, 36 };
3375
3376 private int sizeMap[] = sizeMapDefault;
3377 private boolean w3cLengthUnits = false;
3378 }
|