1 /* 2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing.text.html; 26 27 import sun.swing.SwingUtilities2; 28 import java.util.*; 29 import java.awt.*; 30 import java.io.*; 31 import java.net.*; 32 import javax.swing.Icon; 33 import javax.swing.ImageIcon; 34 import javax.swing.UIManager; 35 import javax.swing.border.*; 36 import javax.swing.event.ChangeListener; 37 import javax.swing.text.*; 38 39 /** 40 * Support for defining the visual characteristics of 41 * HTML views being rendered. The StyleSheet is used to 42 * translate the HTML model into visual characteristics. 43 * This enables views to be customized by a look-and-feel, 44 * multiple views over the same model can be rendered 45 * differently, etc. This can be thought of as a CSS 46 * rule repository. The key for CSS attributes is an 47 * object of type CSS.Attribute. The type of the value 48 * is up to the StyleSheet implementation, but the 49 * <code>toString</code> method is required 50 * to return a string representation of CSS value. 51 * <p> 52 * The primary entry point for HTML View implementations 53 * to get their attributes is the 54 * {@link #getViewAttributes getViewAttributes} 55 * method. This should be implemented to establish the 56 * desired policy used to associate attributes with the view. 57 * Each HTMLEditorKit (i.e. and therefore each associated 58 * JEditorPane) can have its own StyleSheet, but by default one 59 * sheet will be shared by all of the HTMLEditorKit instances. 60 * HTMLDocument instance can also have a StyleSheet, which 61 * holds the document-specific CSS specifications. 62 * <p> 63 * In order for Views to store less state and therefore be 64 * more lightweight, the StyleSheet can act as a factory for 65 * painters that handle some of the rendering tasks. This allows 66 * implementations to determine what they want to cache 67 * and have the sharing potentially at the level that a 68 * selector is common to multiple views. Since the StyleSheet 69 * may be used by views over multiple documents and typically 70 * the HTML attributes don't effect the selector being used, 71 * the potential for sharing is significant. 72 * <p> 73 * The rules are stored as named styles, and other information 74 * is stored to translate the context of an element to a 75 * rule quickly. The following code fragment will display 76 * the named styles, and therefore the CSS rules contained. 77 * <pre><code> 78 * 79 * import java.util.*; 80 * import javax.swing.text.*; 81 * import javax.swing.text.html.*; 82 * 83 * public class ShowStyles { 84 * 85 * public static void main(String[] args) { 86 * HTMLEditorKit kit = new HTMLEditorKit(); 87 * HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument(); 88 * StyleSheet styles = doc.getStyleSheet(); 89 * 90 * Enumeration rules = styles.getStyleNames(); 91 * while (rules.hasMoreElements()) { 92 * String name = (String) rules.nextElement(); 93 * Style rule = styles.getStyle(name); 94 * System.out.println(rule.toString()); 95 * } 96 * System.exit(0); 97 * } 98 * } 99 * 100 * </code></pre> 101 * <p> 102 * The semantics for when a CSS style should overide visual attributes 103 * defined by an element are not well defined. For example, the html 104 * <code><body bgcolor=red></code> makes the body have a red 105 * background. But if the html file also contains the CSS rule 106 * <code>body { background: blue }</code> it becomes less clear as to 107 * what color the background of the body should be. The current 108 * implementation gives visual attributes defined in the element the 109 * highest precedence, that is they are always checked before any styles. 110 * Therefore, in the previous example the background would have a 111 * red color as the body element defines the background color to be red. 112 * <p> 113 * As already mentioned this supports CSS. We don't support the full CSS 114 * spec. Refer to the javadoc of the CSS class to see what properties 115 * we support. The two major CSS parsing related 116 * concepts we do not currently 117 * support are pseudo selectors, such as <code>A:link { color: red }</code>, 118 * and the <code>important</code> modifier. 119 * <p> 120 * @implNote This implementation is currently 121 * incomplete. It can be replaced with alternative implementations 122 * that are complete. Future versions of this class will provide 123 * better CSS support. 124 * 125 * @author Timothy Prinzing 126 * @author Sunita Mani 127 * @author Sara Swanson 128 * @author Jill Nakata 129 */ 130 @SuppressWarnings("serial") // Superclass is not serializable across versions 131 public class StyleSheet extends StyleContext { 132 // As the javadoc states, this class maintains a mapping between 133 // a CSS selector (such as p.bar) and a Style. 134 // This consists of a number of parts: 135 // . Each selector is broken down into its constituent simple selectors, 136 // and stored in an inverted graph, for example: 137 // p { color: red } ol p { font-size: 10pt } ul p { font-size: 12pt } 138 // results in the graph: 139 // root 140 // | 141 // p 142 // / \ 143 // ol ul 144 // each node (an instance of SelectorMapping) has an associated 145 // specificity and potentially a Style. 146 // . Every rule that is asked for (either by way of getRule(String) or 147 // getRule(HTML.Tag, Element)) results in a unique instance of 148 // ResolvedStyle. ResolvedStyles contain the AttributeSets from the 149 // SelectorMapping. 150 // . When a new rule is created it is inserted into the graph, and 151 // the AttributeSets of each ResolvedStyles are updated appropriately. 152 // . This class creates special AttributeSets, LargeConversionSet and 153 // SmallConversionSet, that maintain a mapping between StyleConstants 154 // and CSS so that developers that wish to use the StyleConstants 155 // methods can do so. 156 // . When one of the AttributeSets is mutated by way of a 157 // StyleConstants key, all the associated CSS keys are removed. This is 158 // done so that the two representations don't get out of sync. For 159 // example, if the developer adds StyleConsants.BOLD, FALSE to an 160 // AttributeSet that contains HTML.Tag.B, the HTML.Tag.B entry will 161 // be removed. 162 163 /** 164 * Construct a StyleSheet 165 */ 166 public StyleSheet() { 167 super(); 168 selectorMapping = new SelectorMapping(0); 169 resolvedStyles = new Hashtable<String, ResolvedStyle>(); 170 if (css == null) { 171 css = new CSS(); 172 } 173 } 174 175 /** 176 * Fetches the style to use to render the given type 177 * of HTML tag. The element given is representing 178 * the tag and can be used to determine the nesting 179 * for situations where the attributes will differ 180 * if nesting inside of elements. 181 * 182 * @param t the type to translate to visual attributes 183 * @param e the element representing the tag; the element 184 * can be used to determine the nesting for situations where 185 * the attributes will differ if nested inside of other 186 * elements 187 * @return the set of CSS attributes to use to render 188 * the tag 189 */ 190 public Style getRule(HTML.Tag t, Element e) { 191 SearchBuffer sb = SearchBuffer.obtainSearchBuffer(); 192 193 try { 194 // Build an array of all the parent elements. 195 Vector<Element> searchContext = sb.getVector(); 196 197 for (Element p = e; p != null; p = p.getParentElement()) { 198 searchContext.addElement(p); 199 } 200 201 // Build a fully qualified selector. 202 int n = searchContext.size(); 203 StringBuffer cacheLookup = sb.getStringBuffer(); 204 AttributeSet attr; 205 String eName; 206 Object name; 207 208 // >= 1 as the HTML.Tag for the 0th element is passed in. 209 for (int counter = n - 1; counter >= 1; counter--) { 210 e = searchContext.elementAt(counter); 211 attr = e.getAttributes(); 212 name = attr.getAttribute(StyleConstants.NameAttribute); 213 eName = name.toString(); 214 cacheLookup.append(eName); 215 if (attr != null) { 216 if (attr.isDefined(HTML.Attribute.ID)) { 217 cacheLookup.append('#'); 218 cacheLookup.append(attr.getAttribute 219 (HTML.Attribute.ID)); 220 } 221 else if (attr.isDefined(HTML.Attribute.CLASS)) { 222 cacheLookup.append('.'); 223 cacheLookup.append(attr.getAttribute 224 (HTML.Attribute.CLASS)); 225 } 226 } 227 cacheLookup.append(' '); 228 } 229 cacheLookup.append(t.toString()); 230 e = searchContext.elementAt(0); 231 attr = e.getAttributes(); 232 if (e.isLeaf()) { 233 // For leafs, we use the second tier attributes. 234 Object testAttr = attr.getAttribute(t); 235 if (testAttr instanceof AttributeSet) { 236 attr = (AttributeSet)testAttr; 237 } 238 else { 239 attr = null; 240 } 241 } 242 if (attr != null) { 243 if (attr.isDefined(HTML.Attribute.ID)) { 244 cacheLookup.append('#'); 245 cacheLookup.append(attr.getAttribute(HTML.Attribute.ID)); 246 } 247 else if (attr.isDefined(HTML.Attribute.CLASS)) { 248 cacheLookup.append('.'); 249 cacheLookup.append(attr.getAttribute 250 (HTML.Attribute.CLASS)); 251 } 252 } 253 254 Style style = getResolvedStyle(cacheLookup.toString(), 255 searchContext, t); 256 return style; 257 } 258 finally { 259 SearchBuffer.releaseSearchBuffer(sb); 260 } 261 } 262 263 /** 264 * Fetches the rule that best matches the selector given 265 * in string form. Where <code>selector</code> is a space separated 266 * String of the element names. For example, <code>selector</code> 267 * might be 'html body tr td''<p> 268 * The attributes of the returned Style will change 269 * as rules are added and removed. That is if you to ask for a rule 270 * with a selector "table p" and a new rule was added with a selector 271 * of "p" the returned Style would include the new attributes from 272 * the rule "p". 273 */ 274 public Style getRule(String selector) { 275 selector = cleanSelectorString(selector); 276 if (selector != null) { 277 Style style = getResolvedStyle(selector); 278 return style; 279 } 280 return null; 281 } 282 283 /** 284 * Adds a set of rules to the sheet. The rules are expected to 285 * be in valid CSS format. Typically this would be called as 286 * a result of parsing a <style> tag. 287 */ 288 public void addRule(String rule) { 289 if (rule != null) { 290 //tweaks to control display properties 291 //see BasicEditorPaneUI 292 final String baseUnitsDisable = "BASE_SIZE_DISABLE"; 293 final String baseUnits = "BASE_SIZE "; 294 final String w3cLengthUnitsEnable = "W3C_LENGTH_UNITS_ENABLE"; 295 final String w3cLengthUnitsDisable = "W3C_LENGTH_UNITS_DISABLE"; 296 if (rule == baseUnitsDisable) { 297 sizeMap = sizeMapDefault; 298 } else if (rule.startsWith(baseUnits)) { 299 rebaseSizeMap(Integer. 300 parseInt(rule.substring(baseUnits.length()))); 301 } else if (rule == w3cLengthUnitsEnable) { 302 w3cLengthUnits = true; 303 } else if (rule == w3cLengthUnitsDisable) { 304 w3cLengthUnits = false; 305 } else { 306 CssParser parser = new CssParser(); 307 try { 308 parser.parse(getBase(), new StringReader(rule), false, false); 309 } catch (IOException ioe) { } 310 } 311 } 312 } 313 314 /** 315 * Translates a CSS declaration to an AttributeSet that represents 316 * the CSS declaration. Typically this would be called as a 317 * result of encountering an HTML style attribute. 318 */ 319 public AttributeSet getDeclaration(String decl) { 320 if (decl == null) { 321 return SimpleAttributeSet.EMPTY; 322 } 323 CssParser parser = new CssParser(); 324 return parser.parseDeclaration(decl); 325 } 326 327 /** 328 * Loads a set of rules that have been specified in terms of 329 * CSS1 grammar. If there are collisions with existing rules, 330 * the newly specified rule will win. 331 * 332 * @param in the stream to read the CSS grammar from 333 * @param ref the reference URL. This value represents the 334 * location of the stream and may be null. All relative 335 * URLs specified in the stream will be based upon this 336 * parameter. 337 */ 338 public void loadRules(Reader in, URL ref) throws IOException { 339 CssParser parser = new CssParser(); 340 parser.parse(ref, in, false, false); 341 } 342 343 /** 344 * Fetches a set of attributes to use in the view for 345 * displaying. This is basically a set of attributes that 346 * can be used for View.getAttributes. 347 */ 348 public AttributeSet getViewAttributes(View v) { 349 return new ViewAttributeSet(v); 350 } 351 352 /** 353 * Removes a named style previously added to the document. 354 * 355 * @param nm the name of the style to remove 356 */ 357 public void removeStyle(String nm) { 358 Style aStyle = getStyle(nm); 359 360 if (aStyle != null) { 361 String selector = cleanSelectorString(nm); 362 String[] selectors = getSimpleSelectors(selector); 363 synchronized(this) { 364 SelectorMapping mapping = getRootSelectorMapping(); 365 for (int i = selectors.length - 1; i >= 0; i--) { 366 mapping = mapping.getChildSelectorMapping(selectors[i], 367 true); 368 } 369 Style rule = mapping.getStyle(); 370 if (rule != null) { 371 mapping.setStyle(null); 372 if (resolvedStyles.size() > 0) { 373 Enumeration<ResolvedStyle> values = resolvedStyles.elements(); 374 while (values.hasMoreElements()) { 375 ResolvedStyle style = values.nextElement(); 376 style.removeStyle(rule); 377 } 378 } 379 } 380 } 381 } 382 super.removeStyle(nm); 383 } 384 385 /** 386 * Adds the rules from the StyleSheet <code>ss</code> to those of 387 * the receiver. <code>ss's</code> rules will override the rules of 388 * any previously added style sheets. An added StyleSheet will never 389 * override the rules of the receiving style sheet. 390 * 391 * @since 1.3 392 */ 393 public void addStyleSheet(StyleSheet ss) { 394 synchronized(this) { 395 if (linkedStyleSheets == null) { 396 linkedStyleSheets = new Vector<StyleSheet>(); 397 } 398 if (!linkedStyleSheets.contains(ss)) { 399 int index = 0; 400 if (ss instanceof javax.swing.plaf.UIResource 401 && linkedStyleSheets.size() > 1) { 402 index = linkedStyleSheets.size() - 1; 403 } 404 linkedStyleSheets.insertElementAt(ss, index); 405 linkStyleSheetAt(ss, index); 406 } 407 } 408 } 409 410 /** 411 * Removes the StyleSheet <code>ss</code> from those of the receiver. 412 * 413 * @since 1.3 414 */ 415 public void removeStyleSheet(StyleSheet ss) { 416 synchronized(this) { 417 if (linkedStyleSheets != null) { 418 int index = linkedStyleSheets.indexOf(ss); 419 if (index != -1) { 420 linkedStyleSheets.removeElementAt(index); 421 unlinkStyleSheet(ss, index); 422 if (index == 0 && linkedStyleSheets.size() == 0) { 423 linkedStyleSheets = null; 424 } 425 } 426 } 427 } 428 } 429 430 // 431 // The following is used to import style sheets. 432 // 433 434 /** 435 * Returns an array of the linked StyleSheets. Will return null 436 * if there are no linked StyleSheets. 437 * 438 * @since 1.3 439 */ 440 public StyleSheet[] getStyleSheets() { 441 StyleSheet[] retValue; 442 443 synchronized(this) { 444 if (linkedStyleSheets != null) { 445 retValue = new StyleSheet[linkedStyleSheets.size()]; 446 linkedStyleSheets.copyInto(retValue); 447 } 448 else { 449 retValue = null; 450 } 451 } 452 return retValue; 453 } 454 455 /** 456 * Imports a style sheet from <code>url</code>. The resulting rules 457 * are directly added to the receiver. If you do not want the rules 458 * to become part of the receiver, create a new StyleSheet and use 459 * addStyleSheet to link it in. 460 * 461 * @since 1.3 462 */ 463 public void importStyleSheet(URL url) { 464 try { 465 InputStream is; 466 467 is = url.openStream(); 468 Reader r = new BufferedReader(new InputStreamReader(is)); 469 CssParser parser = new CssParser(); 470 parser.parse(url, r, false, true); 471 r.close(); 472 is.close(); 473 } catch (Throwable e) { 474 // on error we simply have no styles... the html 475 // will look mighty wrong but still function. 476 } 477 } 478 479 /** 480 * Sets the base. All import statements that are relative, will be 481 * relative to <code>base</code>. 482 * 483 * @since 1.3 484 */ 485 public void setBase(URL base) { 486 this.base = base; 487 } 488 489 /** 490 * Returns the base. 491 * 492 * @since 1.3 493 */ 494 public URL getBase() { 495 return base; 496 } 497 498 /** 499 * Adds a CSS attribute to the given set. 500 * 501 * @since 1.3 502 */ 503 public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key, 504 String value) { 505 css.addInternalCSSValue(attr, key, value); 506 } 507 508 /** 509 * Adds a CSS attribute to the given set. 510 * 511 * @since 1.3 512 */ 513 public boolean addCSSAttributeFromHTML(MutableAttributeSet attr, 514 CSS.Attribute key, String value) { 515 Object iValue = css.getCssValue(key, value); 516 if (iValue != null) { 517 attr.addAttribute(key, iValue); 518 return true; 519 } 520 return false; 521 } 522 523 // ---- Conversion functionality --------------------------------- 524 525 /** 526 * Converts a set of HTML attributes to an equivalent 527 * set of CSS attributes. 528 * 529 * @param htmlAttrSet AttributeSet containing the HTML attributes. 530 */ 531 public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) { 532 AttributeSet cssAttrSet = css.translateHTMLToCSS(htmlAttrSet); 533 534 MutableAttributeSet cssStyleSet = addStyle(null, null); 535 cssStyleSet.addAttributes(cssAttrSet); 536 537 return cssStyleSet; 538 } 539 540 /** 541 * Adds an attribute to the given set, and returns 542 * the new representative set. This is reimplemented to 543 * convert StyleConstant attributes to CSS prior to forwarding 544 * to the superclass behavior. The StyleConstants attribute 545 * has no corresponding CSS entry, the StyleConstants attribute 546 * is stored (but will likely be unused). 547 * 548 * @param old the old attribute set 549 * @param key the non-null attribute key 550 * @param value the attribute value 551 * @return the updated attribute set 552 * @see MutableAttributeSet#addAttribute 553 */ 554 public AttributeSet addAttribute(AttributeSet old, Object key, 555 Object value) { 556 if (css == null) { 557 // supers constructor will call this before returning, 558 // and we need to make sure CSS is non null. 559 css = new CSS(); 560 } 561 if (key instanceof StyleConstants) { 562 HTML.Tag tag = HTML.getTagForStyleConstantsKey( 563 (StyleConstants)key); 564 565 if (tag != null && old.isDefined(tag)) { 566 old = removeAttribute(old, tag); 567 } 568 569 Object cssValue = css.styleConstantsValueToCSSValue 570 ((StyleConstants)key, value); 571 if (cssValue != null) { 572 Object cssKey = css.styleConstantsKeyToCSSKey 573 ((StyleConstants)key); 574 if (cssKey != null) { 575 return super.addAttribute(old, cssKey, cssValue); 576 } 577 } 578 } 579 return super.addAttribute(old, key, value); 580 } 581 582 /** 583 * Adds a set of attributes to the element. If any of these attributes 584 * are StyleConstants attributes, they will be converted to CSS prior 585 * to forwarding to the superclass behavior. 586 * 587 * @param old the old attribute set 588 * @param attr the attributes to add 589 * @return the updated attribute set 590 * @see MutableAttributeSet#addAttribute 591 */ 592 public AttributeSet addAttributes(AttributeSet old, AttributeSet attr) { 593 if (!(attr instanceof HTMLDocument.TaggedAttributeSet)) { 594 old = removeHTMLTags(old, attr); 595 } 596 return super.addAttributes(old, convertAttributeSet(attr)); 597 } 598 599 /** 600 * Removes an attribute from the set. If the attribute is a StyleConstants 601 * attribute, the request will be converted to a CSS attribute prior to 602 * forwarding to the superclass behavior. 603 * 604 * @param old the old set of attributes 605 * @param key the non-null attribute name 606 * @return the updated attribute set 607 * @see MutableAttributeSet#removeAttribute 608 */ 609 public AttributeSet removeAttribute(AttributeSet old, Object key) { 610 if (key instanceof StyleConstants) { 611 HTML.Tag tag = HTML.getTagForStyleConstantsKey( 612 (StyleConstants)key); 613 if (tag != null) { 614 old = super.removeAttribute(old, tag); 615 } 616 617 Object cssKey = css.styleConstantsKeyToCSSKey((StyleConstants)key); 618 if (cssKey != null) { 619 return super.removeAttribute(old, cssKey); 620 } 621 } 622 return super.removeAttribute(old, key); 623 } 624 625 /** 626 * Removes a set of attributes for the element. If any of the attributes 627 * is a StyleConstants attribute, the request will be converted to a CSS 628 * attribute prior to forwarding to the superclass behavior. 629 * 630 * @param old the old attribute set 631 * @param names the attribute names 632 * @return the updated attribute set 633 * @see MutableAttributeSet#removeAttributes 634 */ 635 public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) { 636 // PENDING: Should really be doing something similar to 637 // removeHTMLTags here, but it is rather expensive to have to 638 // clone names 639 return super.removeAttributes(old, names); 640 } 641 642 /** 643 * Removes a set of attributes. If any of the attributes 644 * is a StyleConstants attribute, the request will be converted to a CSS 645 * attribute prior to forwarding to the superclass behavior. 646 * 647 * @param old the old attribute set 648 * @param attrs the attributes 649 * @return the updated attribute set 650 * @see MutableAttributeSet#removeAttributes 651 */ 652 public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs) { 653 if (old != attrs) { 654 old = removeHTMLTags(old, attrs); 655 } 656 return super.removeAttributes(old, convertAttributeSet(attrs)); 657 } 658 659 /** 660 * Creates a compact set of attributes that might be shared. 661 * This is a hook for subclasses that want to alter the 662 * behavior of SmallAttributeSet. This can be reimplemented 663 * to return an AttributeSet that provides some sort of 664 * attribute conversion. 665 * 666 * @param a The set of attributes to be represented in the 667 * the compact form. 668 */ 669 protected SmallAttributeSet createSmallAttributeSet(AttributeSet a) { 670 return new SmallConversionSet(a); 671 } 672 673 /** 674 * Creates a large set of attributes that should trade off 675 * space for time. This set will not be shared. This is 676 * a hook for subclasses that want to alter the behavior 677 * of the larger attribute storage format (which is 678 * SimpleAttributeSet by default). This can be reimplemented 679 * to return a MutableAttributeSet that provides some sort of 680 * attribute conversion. 681 * 682 * @param a The set of attributes to be represented in the 683 * the larger form. 684 */ 685 protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) { 686 return new LargeConversionSet(a); 687 } 688 689 /** 690 * For any StyleConstants key in attr that has an associated HTML.Tag, 691 * it is removed from old. The resulting AttributeSet is then returned. 692 */ 693 private AttributeSet removeHTMLTags(AttributeSet old, AttributeSet attr) { 694 if (!(attr instanceof LargeConversionSet) && 695 !(attr instanceof SmallConversionSet)) { 696 Enumeration names = attr.getAttributeNames(); 697 698 while (names.hasMoreElements()) { 699 Object key = names.nextElement(); 700 701 if (key instanceof StyleConstants) { 702 HTML.Tag tag = HTML.getTagForStyleConstantsKey( 703 (StyleConstants)key); 704 705 if (tag != null && old.isDefined(tag)) { 706 old = super.removeAttribute(old, tag); 707 } 708 } 709 } 710 } 711 return old; 712 } 713 714 /** 715 * Converts a set of attributes (if necessary) so that 716 * any attributes that were specified as StyleConstants 717 * attributes and have a CSS mapping, will be converted 718 * to CSS attributes. 719 */ 720 AttributeSet convertAttributeSet(AttributeSet a) { 721 if ((a instanceof LargeConversionSet) || 722 (a instanceof SmallConversionSet)) { 723 // known to be converted. 724 return a; 725 } 726 // in most cases, there are no StyleConstants attributes 727 // so we iterate the collection of keys to avoid creating 728 // a new set. 729 Enumeration names = a.getAttributeNames(); 730 while (names.hasMoreElements()) { 731 Object name = names.nextElement(); 732 if (name instanceof StyleConstants) { 733 // we really need to do a conversion, iterate again 734 // building a new set. 735 MutableAttributeSet converted = new LargeConversionSet(); 736 Enumeration keys = a.getAttributeNames(); 737 while (keys.hasMoreElements()) { 738 Object key = keys.nextElement(); 739 Object cssValue = null; 740 if (key instanceof StyleConstants) { 741 // convert the StyleConstants attribute if possible 742 Object cssKey = css.styleConstantsKeyToCSSKey 743 ((StyleConstants)key); 744 if (cssKey != null) { 745 Object value = a.getAttribute(key); 746 cssValue = css.styleConstantsValueToCSSValue 747 ((StyleConstants)key, value); 748 if (cssValue != null) { 749 converted.addAttribute(cssKey, cssValue); 750 } 751 } 752 } 753 if (cssValue == null) { 754 converted.addAttribute(key, a.getAttribute(key)); 755 } 756 } 757 return converted; 758 } 759 } 760 return a; 761 } 762 763 /** 764 * Large set of attributes that does conversion of requests 765 * for attributes of type StyleConstants. 766 */ 767 @SuppressWarnings("serial") // Superclass is not serializable across versions 768 class LargeConversionSet extends SimpleAttributeSet { 769 770 /** 771 * Creates a new attribute set based on a supplied set of attributes. 772 * 773 * @param source the set of attributes 774 */ 775 public LargeConversionSet(AttributeSet source) { 776 super(source); 777 } 778 779 public LargeConversionSet() { 780 super(); 781 } 782 783 /** 784 * Checks whether a given attribute is defined. 785 * 786 * @param key the attribute key 787 * @return true if the attribute is defined 788 * @see AttributeSet#isDefined 789 */ 790 public boolean isDefined(Object key) { 791 if (key instanceof StyleConstants) { 792 Object cssKey = css.styleConstantsKeyToCSSKey 793 ((StyleConstants)key); 794 if (cssKey != null) { 795 return super.isDefined(cssKey); 796 } 797 } 798 return super.isDefined(key); 799 } 800 801 /** 802 * Gets the value of an attribute. 803 * 804 * @param key the attribute name 805 * @return the attribute value 806 * @see AttributeSet#getAttribute 807 */ 808 public Object getAttribute(Object key) { 809 if (key instanceof StyleConstants) { 810 Object cssKey = css.styleConstantsKeyToCSSKey 811 ((StyleConstants)key); 812 if (cssKey != null) { 813 Object value = super.getAttribute(cssKey); 814 if (value != null) { 815 return css.cssValueToStyleConstantsValue 816 ((StyleConstants)key, value); 817 } 818 } 819 } 820 return super.getAttribute(key); 821 } 822 } 823 824 /** 825 * Small set of attributes that does conversion of requests 826 * for attributes of type StyleConstants. 827 */ 828 class SmallConversionSet extends SmallAttributeSet { 829 830 /** 831 * Creates a new attribute set based on a supplied set of attributes. 832 * 833 * @param attrs the set of attributes 834 */ 835 public SmallConversionSet(AttributeSet attrs) { 836 super(attrs); 837 } 838 839 /** 840 * Checks whether a given attribute is defined. 841 * 842 * @param key the attribute key 843 * @return true if the attribute is defined 844 * @see AttributeSet#isDefined 845 */ 846 public boolean isDefined(Object key) { 847 if (key instanceof StyleConstants) { 848 Object cssKey = css.styleConstantsKeyToCSSKey 849 ((StyleConstants)key); 850 if (cssKey != null) { 851 return super.isDefined(cssKey); 852 } 853 } 854 return super.isDefined(key); 855 } 856 857 /** 858 * Gets the value of an attribute. 859 * 860 * @param key the attribute name 861 * @return the attribute value 862 * @see AttributeSet#getAttribute 863 */ 864 public Object getAttribute(Object key) { 865 if (key instanceof StyleConstants) { 866 Object cssKey = css.styleConstantsKeyToCSSKey 867 ((StyleConstants)key); 868 if (cssKey != null) { 869 Object value = super.getAttribute(cssKey); 870 if (value != null) { 871 return css.cssValueToStyleConstantsValue 872 ((StyleConstants)key, value); 873 } 874 } 875 } 876 return super.getAttribute(key); 877 } 878 } 879 880 // ---- Resource handling ---------------------------------------- 881 882 /** 883 * Fetches the font to use for the given set of attributes. 884 */ 885 public Font getFont(AttributeSet a) { 886 return css.getFont(this, a, 12, this); 887 } 888 889 /** 890 * Takes a set of attributes and turn it into a foreground color 891 * specification. This might be used to specify things 892 * like brighter, more hue, etc. 893 * 894 * @param a the set of attributes 895 * @return the color 896 */ 897 public Color getForeground(AttributeSet a) { 898 Color c = css.getColor(a, CSS.Attribute.COLOR); 899 if (c == null) { 900 return Color.black; 901 } 902 return c; 903 } 904 905 /** 906 * Takes a set of attributes and turn it into a background color 907 * specification. This might be used to specify things 908 * like brighter, more hue, etc. 909 * 910 * @param a the set of attributes 911 * @return the color 912 */ 913 public Color getBackground(AttributeSet a) { 914 return css.getColor(a, CSS.Attribute.BACKGROUND_COLOR); 915 } 916 917 /** 918 * Fetches the box formatter to use for the given set 919 * of CSS attributes. 920 */ 921 public BoxPainter getBoxPainter(AttributeSet a) { 922 return new BoxPainter(a, css, this); 923 } 924 925 /** 926 * Fetches the list formatter to use for the given set 927 * of CSS attributes. 928 */ 929 public ListPainter getListPainter(AttributeSet a) { 930 return new ListPainter(a, this); 931 } 932 933 /** 934 * Sets the base font size, with valid values between 1 and 7. 935 */ 936 public void setBaseFontSize(int sz) { 937 css.setBaseFontSize(sz); 938 } 939 940 /** 941 * Sets the base font size from the passed in String. The string 942 * can either identify a specific font size, with legal values between 943 * 1 and 7, or identify a relative font size such as +1 or -2. 944 */ 945 public void setBaseFontSize(String size) { 946 css.setBaseFontSize(size); 947 } 948 949 public static int getIndexOfSize(float pt) { 950 return CSS.getIndexOfSize(pt, sizeMapDefault); 951 } 952 953 /** 954 * Returns the point size, given a size index. 955 */ 956 public float getPointSize(int index) { 957 return css.getPointSize(index, this); 958 } 959 960 /** 961 * Given a string such as "+2", "-2", or "2", 962 * returns a point size value. 963 */ 964 public float getPointSize(String size) { 965 return css.getPointSize(size, this); 966 } 967 968 /** 969 * Converts a color string such as "RED" or "#NNNNNN" to a Color. 970 * Note: This will only convert the HTML3.2 color strings 971 * or a string of length 7; 972 * otherwise, it will return null. 973 */ 974 public Color stringToColor(String string) { 975 return CSS.stringToColor(string); 976 } 977 978 /** 979 * Returns the ImageIcon to draw in the background for 980 * <code>attr</code>. 981 */ 982 ImageIcon getBackgroundImage(AttributeSet attr) { 983 Object value = attr.getAttribute(CSS.Attribute.BACKGROUND_IMAGE); 984 985 if (value != null) { 986 return ((CSS.BackgroundImage)value).getImage(getBase()); 987 } 988 return null; 989 } 990 991 /** 992 * Adds a rule into the StyleSheet. 993 * 994 * @param selector the selector to use for the rule. 995 * This will be a set of simple selectors, and must 996 * be a length of 1 or greater. 997 * @param declaration the set of CSS attributes that 998 * make up the rule. 999 */ 1000 void addRule(String[] selector, AttributeSet declaration, 1001 boolean isLinked) { 1002 int n = selector.length; 1003 StringBuilder sb = new StringBuilder(); 1004 sb.append(selector[0]); 1005 for (int counter = 1; counter < n; counter++) { 1006 sb.append(' '); 1007 sb.append(selector[counter]); 1008 } 1009 String selectorName = sb.toString(); 1010 Style rule = getStyle(selectorName); 1011 if (rule == null) { 1012 // Notice how the rule is first created, and it not part of 1013 // the synchronized block. It is done like this as creating 1014 // a new rule will fire a ChangeEvent. We do not want to be 1015 // holding the lock when calling to other objects, it can 1016 // result in deadlock. 1017 Style altRule = addStyle(selectorName, null); 1018 synchronized(this) { 1019 SelectorMapping mapping = getRootSelectorMapping(); 1020 for (int i = n - 1; i >= 0; i--) { 1021 mapping = mapping.getChildSelectorMapping 1022 (selector[i], true); 1023 } 1024 rule = mapping.getStyle(); 1025 if (rule == null) { 1026 rule = altRule; 1027 mapping.setStyle(rule); 1028 refreshResolvedRules(selectorName, selector, rule, 1029 mapping.getSpecificity()); 1030 } 1031 } 1032 } 1033 if (isLinked) { 1034 rule = getLinkedStyle(rule); 1035 } 1036 rule.addAttributes(declaration); 1037 } 1038 1039 // 1040 // The following gaggle of methods is used in maintaining the rules from 1041 // the sheet. 1042 // 1043 1044 /** 1045 * Updates the attributes of the rules to reference any related 1046 * rules in <code>ss</code>. 1047 */ 1048 private synchronized void linkStyleSheetAt(StyleSheet ss, int index) { 1049 if (resolvedStyles.size() > 0) { 1050 Enumeration<ResolvedStyle> values = resolvedStyles.elements(); 1051 while (values.hasMoreElements()) { 1052 ResolvedStyle rule = values.nextElement(); 1053 rule.insertExtendedStyleAt(ss.getRule(rule.getName()), 1054 index); 1055 } 1056 } 1057 } 1058 1059 /** 1060 * Removes references to the rules in <code>ss</code>. 1061 * <code>index</code> gives the index the StyleSheet was at, that is 1062 * how many StyleSheets had been added before it. 1063 */ 1064 private synchronized void unlinkStyleSheet(StyleSheet ss, int index) { 1065 if (resolvedStyles.size() > 0) { 1066 Enumeration<ResolvedStyle> values = resolvedStyles.elements(); 1067 while (values.hasMoreElements()) { 1068 ResolvedStyle rule = values.nextElement(); 1069 rule.removeExtendedStyleAt(index); 1070 } 1071 } 1072 } 1073 1074 /** 1075 * Returns the simple selectors that comprise selector. 1076 */ 1077 /* protected */ 1078 String[] getSimpleSelectors(String selector) { 1079 selector = cleanSelectorString(selector); 1080 SearchBuffer sb = SearchBuffer.obtainSearchBuffer(); 1081 Vector<String> selectors = sb.getVector(); 1082 int lastIndex = 0; 1083 int length = selector.length(); 1084 while (lastIndex != -1) { 1085 int newIndex = selector.indexOf(' ', lastIndex); 1086 if (newIndex != -1) { 1087 selectors.addElement(selector.substring(lastIndex, newIndex)); 1088 if (++newIndex == length) { 1089 lastIndex = -1; 1090 } 1091 else { 1092 lastIndex = newIndex; 1093 } 1094 } 1095 else { 1096 selectors.addElement(selector.substring(lastIndex)); 1097 lastIndex = -1; 1098 } 1099 } 1100 String[] retValue = new String[selectors.size()]; 1101 selectors.copyInto(retValue); 1102 SearchBuffer.releaseSearchBuffer(sb); 1103 return retValue; 1104 } 1105 1106 /** 1107 * Returns a string that only has one space between simple selectors, 1108 * which may be the passed in String. 1109 */ 1110 /*protected*/ String cleanSelectorString(String selector) { 1111 boolean lastWasSpace = true; 1112 for (int counter = 0, maxCounter = selector.length(); 1113 counter < maxCounter; counter++) { 1114 switch(selector.charAt(counter)) { 1115 case ' ': 1116 if (lastWasSpace) { 1117 return _cleanSelectorString(selector); 1118 } 1119 lastWasSpace = true; 1120 break; 1121 case '\n': 1122 case '\r': 1123 case '\t': 1124 return _cleanSelectorString(selector); 1125 default: 1126 lastWasSpace = false; 1127 } 1128 } 1129 if (lastWasSpace) { 1130 return _cleanSelectorString(selector); 1131 } 1132 // It was fine. 1133 return selector; 1134 } 1135 1136 /** 1137 * Returns a new String that contains only one space between non 1138 * white space characters. 1139 */ 1140 private String _cleanSelectorString(String selector) { 1141 SearchBuffer sb = SearchBuffer.obtainSearchBuffer(); 1142 StringBuffer buff = sb.getStringBuffer(); 1143 boolean lastWasSpace = true; 1144 int lastIndex = 0; 1145 char[] chars = selector.toCharArray(); 1146 int numChars = chars.length; 1147 String retValue = null; 1148 try { 1149 for (int counter = 0; counter < numChars; counter++) { 1150 switch(chars[counter]) { 1151 case ' ': 1152 if (!lastWasSpace) { 1153 lastWasSpace = true; 1154 if (lastIndex < counter) { 1155 buff.append(chars, lastIndex, 1156 1 + counter - lastIndex); 1157 } 1158 } 1159 lastIndex = counter + 1; 1160 break; 1161 case '\n': 1162 case '\r': 1163 case '\t': 1164 if (!lastWasSpace) { 1165 lastWasSpace = true; 1166 if (lastIndex < counter) { 1167 buff.append(chars, lastIndex, 1168 counter - lastIndex); 1169 buff.append(' '); 1170 } 1171 } 1172 lastIndex = counter + 1; 1173 break; 1174 default: 1175 lastWasSpace = false; 1176 break; 1177 } 1178 } 1179 if (lastWasSpace && buff.length() > 0) { 1180 // Remove last space. 1181 buff.setLength(buff.length() - 1); 1182 } 1183 else if (lastIndex < numChars) { 1184 buff.append(chars, lastIndex, numChars - lastIndex); 1185 } 1186 retValue = buff.toString(); 1187 } 1188 finally { 1189 SearchBuffer.releaseSearchBuffer(sb); 1190 } 1191 return retValue; 1192 } 1193 1194 /** 1195 * Returns the root selector mapping that all selectors are relative 1196 * to. This is an inverted graph of the selectors. 1197 */ 1198 private SelectorMapping getRootSelectorMapping() { 1199 return selectorMapping; 1200 } 1201 1202 /** 1203 * Returns the specificity of the passed in String. It assumes the 1204 * passed in string doesn't contain junk, that is each selector is 1205 * separated by a space and each selector at most contains one . or one 1206 * #. A simple selector has a weight of 1, an id selector has a weight 1207 * of 100, and a class selector has a weight of 10000. 1208 */ 1209 /*protected*/ static int getSpecificity(String selector) { 1210 int specificity = 0; 1211 boolean lastWasSpace = true; 1212 1213 for (int counter = 0, maxCounter = selector.length(); 1214 counter < maxCounter; counter++) { 1215 switch(selector.charAt(counter)) { 1216 case '.': 1217 specificity += 100; 1218 break; 1219 case '#': 1220 specificity += 10000; 1221 break; 1222 case ' ': 1223 lastWasSpace = true; 1224 break; 1225 default: 1226 if (lastWasSpace) { 1227 lastWasSpace = false; 1228 specificity += 1; 1229 } 1230 } 1231 } 1232 return specificity; 1233 } 1234 1235 /** 1236 * Returns the style that linked attributes should be added to. This 1237 * will create the style if necessary. 1238 */ 1239 private Style getLinkedStyle(Style localStyle) { 1240 // NOTE: This is not synchronized, and the caller of this does 1241 // not synchronize. There is the chance for one of the callers to 1242 // overwrite the existing resolved parent, but it is quite rare. 1243 // The reason this is left like this is because setResolveParent 1244 // will fire a ChangeEvent. It is really, REALLY bad for us to 1245 // hold a lock when calling outside of us, it may cause a deadlock. 1246 Style retStyle = (Style)localStyle.getResolveParent(); 1247 if (retStyle == null) { 1248 retStyle = addStyle(null, null); 1249 localStyle.setResolveParent(retStyle); 1250 } 1251 return retStyle; 1252 } 1253 1254 /** 1255 * Returns the resolved style for <code>selector</code>. This will 1256 * create the resolved style, if necessary. 1257 */ 1258 private synchronized Style getResolvedStyle(String selector, 1259 Vector elements, 1260 HTML.Tag t) { 1261 Style retStyle = resolvedStyles.get(selector); 1262 if (retStyle == null) { 1263 retStyle = createResolvedStyle(selector, elements, t); 1264 } 1265 return retStyle; 1266 } 1267 1268 /** 1269 * Returns the resolved style for <code>selector</code>. This will 1270 * create the resolved style, if necessary. 1271 */ 1272 private synchronized Style getResolvedStyle(String selector) { 1273 Style retStyle = resolvedStyles.get(selector); 1274 if (retStyle == null) { 1275 retStyle = createResolvedStyle(selector); 1276 } 1277 return retStyle; 1278 } 1279 1280 /** 1281 * Adds <code>mapping</code> to <code>elements</code>. It is added 1282 * such that <code>elements</code> will remain ordered by 1283 * specificity. 1284 */ 1285 private void addSortedStyle(SelectorMapping mapping, Vector<SelectorMapping> elements) { 1286 int size = elements.size(); 1287 1288 if (size > 0) { 1289 int specificity = mapping.getSpecificity(); 1290 1291 for (int counter = 0; counter < size; counter++) { 1292 if (specificity >= elements.elementAt(counter).getSpecificity()) { 1293 elements.insertElementAt(mapping, counter); 1294 return; 1295 } 1296 } 1297 } 1298 elements.addElement(mapping); 1299 } 1300 1301 /** 1302 * Adds <code>parentMapping</code> to <code>styles</code>, and 1303 * recursively calls this method if <code>parentMapping</code> has 1304 * any child mappings for any of the Elements in <code>elements</code>. 1305 */ 1306 private synchronized void getStyles(SelectorMapping parentMapping, 1307 Vector<SelectorMapping> styles, 1308 String[] tags, String[] ids, String[] classes, 1309 int index, int numElements, 1310 Hashtable<SelectorMapping, SelectorMapping> alreadyChecked) { 1311 // Avoid desending the same mapping twice. 1312 if (alreadyChecked.contains(parentMapping)) { 1313 return; 1314 } 1315 alreadyChecked.put(parentMapping, parentMapping); 1316 Style style = parentMapping.getStyle(); 1317 if (style != null) { 1318 addSortedStyle(parentMapping, styles); 1319 } 1320 for (int counter = index; counter < numElements; counter++) { 1321 String tagString = tags[counter]; 1322 if (tagString != null) { 1323 SelectorMapping childMapping = parentMapping. 1324 getChildSelectorMapping(tagString, false); 1325 if (childMapping != null) { 1326 getStyles(childMapping, styles, tags, ids, classes, 1327 counter + 1, numElements, alreadyChecked); 1328 } 1329 if (classes[counter] != null) { 1330 String className = classes[counter]; 1331 childMapping = parentMapping.getChildSelectorMapping( 1332 tagString + "." + className, false); 1333 if (childMapping != null) { 1334 getStyles(childMapping, styles, tags, ids, classes, 1335 counter + 1, numElements, alreadyChecked); 1336 } 1337 childMapping = parentMapping.getChildSelectorMapping( 1338 "." + className, false); 1339 if (childMapping != null) { 1340 getStyles(childMapping, styles, tags, ids, classes, 1341 counter + 1, numElements, alreadyChecked); 1342 } 1343 } 1344 if (ids[counter] != null) { 1345 String idName = ids[counter]; 1346 childMapping = parentMapping.getChildSelectorMapping( 1347 tagString + "#" + idName, false); 1348 if (childMapping != null) { 1349 getStyles(childMapping, styles, tags, ids, classes, 1350 counter + 1, numElements, alreadyChecked); 1351 } 1352 childMapping = parentMapping.getChildSelectorMapping( 1353 "#" + idName, false); 1354 if (childMapping != null) { 1355 getStyles(childMapping, styles, tags, ids, classes, 1356 counter + 1, numElements, alreadyChecked); 1357 } 1358 } 1359 } 1360 } 1361 } 1362 1363 /** 1364 * Creates and returns a Style containing all the rules that match 1365 * <code>selector</code>. 1366 */ 1367 private synchronized Style createResolvedStyle(String selector, 1368 String[] tags, 1369 String[] ids, String[] classes) { 1370 SearchBuffer sb = SearchBuffer.obtainSearchBuffer(); 1371 Vector<SelectorMapping> tempVector = sb.getVector(); 1372 Hashtable<SelectorMapping, SelectorMapping> tempHashtable = sb.getHashtable(); 1373 // Determine all the Styles that are appropriate, placing them 1374 // in tempVector 1375 try { 1376 SelectorMapping mapping = getRootSelectorMapping(); 1377 int numElements = tags.length; 1378 String tagString = tags[0]; 1379 SelectorMapping childMapping = mapping.getChildSelectorMapping( 1380 tagString, false); 1381 if (childMapping != null) { 1382 getStyles(childMapping, tempVector, tags, ids, classes, 1, 1383 numElements, tempHashtable); 1384 } 1385 if (classes[0] != null) { 1386 String className = classes[0]; 1387 childMapping = mapping.getChildSelectorMapping( 1388 tagString + "." + className, false); 1389 if (childMapping != null) { 1390 getStyles(childMapping, tempVector, tags, ids, classes, 1, 1391 numElements, tempHashtable); 1392 } 1393 childMapping = mapping.getChildSelectorMapping( 1394 "." + className, false); 1395 if (childMapping != null) { 1396 getStyles(childMapping, tempVector, tags, ids, classes, 1397 1, numElements, tempHashtable); 1398 } 1399 } 1400 if (ids[0] != null) { 1401 String idName = ids[0]; 1402 childMapping = mapping.getChildSelectorMapping( 1403 tagString + "#" + idName, false); 1404 if (childMapping != null) { 1405 getStyles(childMapping, tempVector, tags, ids, classes, 1406 1, numElements, tempHashtable); 1407 } 1408 childMapping = mapping.getChildSelectorMapping( 1409 "#" + idName, false); 1410 if (childMapping != null) { 1411 getStyles(childMapping, tempVector, tags, ids, classes, 1412 1, numElements, tempHashtable); 1413 } 1414 } 1415 // Create a new Style that will delegate to all the matching 1416 // Styles. 1417 int numLinkedSS = (linkedStyleSheets != null) ? 1418 linkedStyleSheets.size() : 0; 1419 int numStyles = tempVector.size(); 1420 AttributeSet[] attrs = new AttributeSet[numStyles + numLinkedSS]; 1421 for (int counter = 0; counter < numStyles; counter++) { 1422 attrs[counter] = tempVector.elementAt(counter).getStyle(); 1423 } 1424 // Get the AttributeSet from linked style sheets. 1425 for (int counter = 0; counter < numLinkedSS; counter++) { 1426 AttributeSet attr = linkedStyleSheets.elementAt(counter).getRule(selector); 1427 if (attr == null) { 1428 attrs[counter + numStyles] = SimpleAttributeSet.EMPTY; 1429 } 1430 else { 1431 attrs[counter + numStyles] = attr; 1432 } 1433 } 1434 ResolvedStyle retStyle = new ResolvedStyle(selector, attrs, 1435 numStyles); 1436 resolvedStyles.put(selector, retStyle); 1437 return retStyle; 1438 } 1439 finally { 1440 SearchBuffer.releaseSearchBuffer(sb); 1441 } 1442 } 1443 1444 /** 1445 * Creates and returns a Style containing all the rules that 1446 * matches <code>selector</code>. 1447 * 1448 * @param elements a Vector of all the Elements 1449 * the style is being asked for. The 1450 * first Element is the deepest Element, with the last Element 1451 * representing the root. 1452 * @param t the Tag to use for 1453 * the first Element in <code>elements</code> 1454 */ 1455 private Style createResolvedStyle(String selector, Vector elements, 1456 HTML.Tag t) { 1457 int numElements = elements.size(); 1458 // Build three arrays, one for tags, one for class's, and one for 1459 // id's 1460 String tags[] = new String[numElements]; 1461 String ids[] = new String[numElements]; 1462 String classes[] = new String[numElements]; 1463 for (int counter = 0; counter < numElements; counter++) { 1464 Element e = (Element)elements.elementAt(counter); 1465 AttributeSet attr = e.getAttributes(); 1466 if (counter == 0 && e.isLeaf()) { 1467 // For leafs, we use the second tier attributes. 1468 Object testAttr = attr.getAttribute(t); 1469 if (testAttr instanceof AttributeSet) { 1470 attr = (AttributeSet)testAttr; 1471 } 1472 else { 1473 attr = null; 1474 } 1475 } 1476 if (attr != null) { 1477 HTML.Tag tag = (HTML.Tag)attr.getAttribute(StyleConstants. 1478 NameAttribute); 1479 if (tag != null) { 1480 tags[counter] = tag.toString(); 1481 } 1482 else { 1483 tags[counter] = null; 1484 } 1485 if (attr.isDefined(HTML.Attribute.CLASS)) { 1486 classes[counter] = attr.getAttribute 1487 (HTML.Attribute.CLASS).toString(); 1488 } 1489 else { 1490 classes[counter] = null; 1491 } 1492 if (attr.isDefined(HTML.Attribute.ID)) { 1493 ids[counter] = attr.getAttribute(HTML.Attribute.ID). 1494 toString(); 1495 } 1496 else { 1497 ids[counter] = null; 1498 } 1499 } 1500 else { 1501 tags[counter] = ids[counter] = classes[counter] = null; 1502 } 1503 } 1504 tags[0] = t.toString(); 1505 return createResolvedStyle(selector, tags, ids, classes); 1506 } 1507 1508 /** 1509 * Creates and returns a Style containing all the rules that match 1510 * <code>selector</code>. It is assumed that each simple selector 1511 * in <code>selector</code> is separated by a space. 1512 */ 1513 private Style createResolvedStyle(String selector) { 1514 SearchBuffer sb = SearchBuffer.obtainSearchBuffer(); 1515 // Will contain the tags, ids, and classes, in that order. 1516 Vector<String> elements = sb.getVector(); 1517 try { 1518 boolean done; 1519 int dotIndex = 0; 1520 int spaceIndex; 1521 int poundIndex = 0; 1522 int lastIndex = 0; 1523 int length = selector.length(); 1524 while (lastIndex < length) { 1525 if (dotIndex == lastIndex) { 1526 dotIndex = selector.indexOf('.', lastIndex); 1527 } 1528 if (poundIndex == lastIndex) { 1529 poundIndex = selector.indexOf('#', lastIndex); 1530 } 1531 spaceIndex = selector.indexOf(' ', lastIndex); 1532 if (spaceIndex == -1) { 1533 spaceIndex = length; 1534 } 1535 if (dotIndex != -1 && poundIndex != -1 && 1536 dotIndex < spaceIndex && poundIndex < spaceIndex) { 1537 if (poundIndex < dotIndex) { 1538 // #. 1539 if (lastIndex == poundIndex) { 1540 elements.addElement(""); 1541 } 1542 else { 1543 elements.addElement(selector.substring(lastIndex, 1544 poundIndex)); 1545 } 1546 if ((dotIndex + 1) < spaceIndex) { 1547 elements.addElement(selector.substring 1548 (dotIndex + 1, spaceIndex)); 1549 } 1550 else { 1551 elements.addElement(null); 1552 } 1553 if ((poundIndex + 1) == dotIndex) { 1554 elements.addElement(null); 1555 } 1556 else { 1557 elements.addElement(selector.substring 1558 (poundIndex + 1, dotIndex)); 1559 } 1560 } 1561 else if(poundIndex < spaceIndex) { 1562 // .# 1563 if (lastIndex == dotIndex) { 1564 elements.addElement(""); 1565 } 1566 else { 1567 elements.addElement(selector.substring(lastIndex, 1568 dotIndex)); 1569 } 1570 if ((dotIndex + 1) < poundIndex) { 1571 elements.addElement(selector.substring 1572 (dotIndex + 1, poundIndex)); 1573 } 1574 else { 1575 elements.addElement(null); 1576 } 1577 if ((poundIndex + 1) == spaceIndex) { 1578 elements.addElement(null); 1579 } 1580 else { 1581 elements.addElement(selector.substring 1582 (poundIndex + 1, spaceIndex)); 1583 } 1584 } 1585 dotIndex = poundIndex = spaceIndex + 1; 1586 } 1587 else if (dotIndex != -1 && dotIndex < spaceIndex) { 1588 // . 1589 if (dotIndex == lastIndex) { 1590 elements.addElement(""); 1591 } 1592 else { 1593 elements.addElement(selector.substring(lastIndex, 1594 dotIndex)); 1595 } 1596 if ((dotIndex + 1) == spaceIndex) { 1597 elements.addElement(null); 1598 } 1599 else { 1600 elements.addElement(selector.substring(dotIndex + 1, 1601 spaceIndex)); 1602 } 1603 elements.addElement(null); 1604 dotIndex = spaceIndex + 1; 1605 } 1606 else if (poundIndex != -1 && poundIndex < spaceIndex) { 1607 // # 1608 if (poundIndex == lastIndex) { 1609 elements.addElement(""); 1610 } 1611 else { 1612 elements.addElement(selector.substring(lastIndex, 1613 poundIndex)); 1614 } 1615 elements.addElement(null); 1616 if ((poundIndex + 1) == spaceIndex) { 1617 elements.addElement(null); 1618 } 1619 else { 1620 elements.addElement(selector.substring(poundIndex + 1, 1621 spaceIndex)); 1622 } 1623 poundIndex = spaceIndex + 1; 1624 } 1625 else { 1626 // id 1627 elements.addElement(selector.substring(lastIndex, 1628 spaceIndex)); 1629 elements.addElement(null); 1630 elements.addElement(null); 1631 } 1632 lastIndex = spaceIndex + 1; 1633 } 1634 // Create the tag, id, and class arrays. 1635 int total = elements.size(); 1636 int numTags = total / 3; 1637 String[] tags = new String[numTags]; 1638 String[] ids = new String[numTags]; 1639 String[] classes = new String[numTags]; 1640 for (int index = 0, eIndex = total - 3; index < numTags; 1641 index++, eIndex -= 3) { 1642 tags[index] = elements.elementAt(eIndex); 1643 classes[index] = elements.elementAt(eIndex + 1); 1644 ids[index] = elements.elementAt(eIndex + 2); 1645 } 1646 return createResolvedStyle(selector, tags, ids, classes); 1647 } 1648 finally { 1649 SearchBuffer.releaseSearchBuffer(sb); 1650 } 1651 } 1652 1653 /** 1654 * Should be invoked when a new rule is added that did not previously 1655 * exist. Goes through and refreshes the necessary resolved 1656 * rules. 1657 */ 1658 private synchronized void refreshResolvedRules(String selectorName, 1659 String[] selector, 1660 Style newStyle, 1661 int specificity) { 1662 if (resolvedStyles.size() > 0) { 1663 Enumeration<ResolvedStyle> values = resolvedStyles.elements(); 1664 while (values.hasMoreElements()) { 1665 ResolvedStyle style = values.nextElement(); 1666 if (style.matches(selectorName)) { 1667 style.insertStyle(newStyle, specificity); 1668 } 1669 } 1670 } 1671 } 1672 1673 1674 /** 1675 * A temporary class used to hold a Vector, a StringBuffer and a 1676 * Hashtable. This is used to avoid allocing a lot of garbage when 1677 * searching for rules. Use the static method obtainSearchBuffer and 1678 * releaseSearchBuffer to get a SearchBuffer, and release it when 1679 * done. 1680 */ 1681 private static class SearchBuffer { 1682 /** A stack containing instances of SearchBuffer. Used in getting 1683 * rules. */ 1684 static Stack<SearchBuffer> searchBuffers = new Stack<SearchBuffer>(); 1685 // A set of temporary variables that can be used in whatever way. 1686 Vector vector = null; 1687 StringBuffer stringBuffer = null; 1688 Hashtable hashtable = null; 1689 1690 /** 1691 * Returns an instance of SearchBuffer. Be sure and issue 1692 * a releaseSearchBuffer when done with it. 1693 */ 1694 static SearchBuffer obtainSearchBuffer() { 1695 SearchBuffer sb; 1696 try { 1697 if(!searchBuffers.empty()) { 1698 sb = searchBuffers.pop(); 1699 } else { 1700 sb = new SearchBuffer(); 1701 } 1702 } catch (EmptyStackException ese) { 1703 sb = new SearchBuffer(); 1704 } 1705 return sb; 1706 } 1707 1708 /** 1709 * Adds <code>sb</code> to the stack of SearchBuffers that can 1710 * be used. 1711 */ 1712 static void releaseSearchBuffer(SearchBuffer sb) { 1713 sb.empty(); 1714 searchBuffers.push(sb); 1715 } 1716 1717 StringBuffer getStringBuffer() { 1718 if (stringBuffer == null) { 1719 stringBuffer = new StringBuffer(); 1720 } 1721 return stringBuffer; 1722 } 1723 1724 Vector getVector() { 1725 if (vector == null) { 1726 vector = new Vector(); 1727 } 1728 return vector; 1729 } 1730 1731 Hashtable getHashtable() { 1732 if (hashtable == null) { 1733 hashtable = new Hashtable(); 1734 } 1735 return hashtable; 1736 } 1737 1738 void empty() { 1739 if (stringBuffer != null) { 1740 stringBuffer.setLength(0); 1741 } 1742 if (vector != null) { 1743 vector.removeAllElements(); 1744 } 1745 if (hashtable != null) { 1746 hashtable.clear(); 1747 } 1748 } 1749 } 1750 1751 1752 static final Border noBorder = new EmptyBorder(0,0,0,0); 1753 1754 /** 1755 * Class to carry out some of the duties of 1756 * CSS formatting. Implementations of this 1757 * class enable views to present the CSS formatting 1758 * while not knowing anything about how the CSS values 1759 * are being cached. 1760 * <p> 1761 * As a delegate of Views, this object is responsible for 1762 * the insets of a View and making sure the background 1763 * is maintained according to the CSS attributes. 1764 */ 1765 @SuppressWarnings("serial") // Same-version serialization only 1766 public static class BoxPainter implements Serializable { 1767 1768 BoxPainter(AttributeSet a, CSS css, StyleSheet ss) { 1769 this.ss = ss; 1770 this.css = css; 1771 border = getBorder(a); 1772 binsets = border.getBorderInsets(null); 1773 topMargin = getLength(CSS.Attribute.MARGIN_TOP, a); 1774 bottomMargin = getLength(CSS.Attribute.MARGIN_BOTTOM, a); 1775 leftMargin = getLength(CSS.Attribute.MARGIN_LEFT, a); 1776 rightMargin = getLength(CSS.Attribute.MARGIN_RIGHT, a); 1777 bg = ss.getBackground(a); 1778 if (ss.getBackgroundImage(a) != null) { 1779 bgPainter = new BackgroundImagePainter(a, css, ss); 1780 } 1781 } 1782 1783 /** 1784 * Fetches a border to render for the given attributes. 1785 * PENDING(prinz) This is pretty badly hacked at the 1786 * moment. 1787 */ 1788 Border getBorder(AttributeSet a) { 1789 return new CSSBorder(a); 1790 } 1791 1792 /** 1793 * Fetches the color to use for borders. This will either be 1794 * the value specified by the border-color attribute (which 1795 * is not inherited), or it will default to the color attribute 1796 * (which is inherited). 1797 */ 1798 Color getBorderColor(AttributeSet a) { 1799 Color color = css.getColor(a, CSS.Attribute.BORDER_COLOR); 1800 if (color == null) { 1801 color = css.getColor(a, CSS.Attribute.COLOR); 1802 if (color == null) { 1803 return Color.black; 1804 } 1805 } 1806 return color; 1807 } 1808 1809 /** 1810 * Fetches the inset needed on a given side to 1811 * account for the margin, border, and padding. 1812 * 1813 * @param side The size of the box to fetch the 1814 * inset for. This can be View.TOP, 1815 * View.LEFT, View.BOTTOM, or View.RIGHT. 1816 * @param v the view making the request. This is 1817 * used to get the AttributeSet, and may be used to 1818 * resolve percentage arguments. 1819 * @exception IllegalArgumentException for an invalid direction 1820 */ 1821 public float getInset(int side, View v) { 1822 AttributeSet a = v.getAttributes(); 1823 float inset = 0; 1824 switch(side) { 1825 case View.LEFT: 1826 inset += getOrientationMargin(HorizontalMargin.LEFT, 1827 leftMargin, a, isLeftToRight(v)); 1828 inset += binsets.left; 1829 inset += getLength(CSS.Attribute.PADDING_LEFT, a); 1830 break; 1831 case View.RIGHT: 1832 inset += getOrientationMargin(HorizontalMargin.RIGHT, 1833 rightMargin, a, isLeftToRight(v)); 1834 inset += binsets.right; 1835 inset += getLength(CSS.Attribute.PADDING_RIGHT, a); 1836 break; 1837 case View.TOP: 1838 inset += topMargin; 1839 inset += binsets.top; 1840 inset += getLength(CSS.Attribute.PADDING_TOP, a); 1841 break; 1842 case View.BOTTOM: 1843 inset += bottomMargin; 1844 inset += binsets.bottom; 1845 inset += getLength(CSS.Attribute.PADDING_BOTTOM, a); 1846 break; 1847 default: 1848 throw new IllegalArgumentException("Invalid side: " + side); 1849 } 1850 return inset; 1851 } 1852 1853 /** 1854 * Paints the CSS box according to the attributes 1855 * given. This should paint the border, padding, 1856 * and background. 1857 * 1858 * @param g the rendering surface. 1859 * @param x the x coordinate of the allocated area to 1860 * render into. 1861 * @param y the y coordinate of the allocated area to 1862 * render into. 1863 * @param w the width of the allocated area to render into. 1864 * @param h the height of the allocated area to render into. 1865 * @param v the view making the request. This is 1866 * used to get the AttributeSet, and may be used to 1867 * resolve percentage arguments. 1868 */ 1869 public void paint(Graphics g, float x, float y, float w, float h, View v) { 1870 // PENDING(prinz) implement real rendering... which would 1871 // do full set of border and background capabilities. 1872 // remove margin 1873 1874 float dx = 0; 1875 float dy = 0; 1876 float dw = 0; 1877 float dh = 0; 1878 AttributeSet a = v.getAttributes(); 1879 boolean isLeftToRight = isLeftToRight(v); 1880 float localLeftMargin = getOrientationMargin(HorizontalMargin.LEFT, 1881 leftMargin, 1882 a, isLeftToRight); 1883 float localRightMargin = getOrientationMargin(HorizontalMargin.RIGHT, 1884 rightMargin, 1885 a, isLeftToRight); 1886 if (!(v instanceof HTMLEditorKit.HTMLFactory.BodyBlockView)) { 1887 dx = localLeftMargin; 1888 dy = topMargin; 1889 dw = -(localLeftMargin + localRightMargin); 1890 dh = -(topMargin + bottomMargin); 1891 } 1892 if (bg != null) { 1893 g.setColor(bg); 1894 g.fillRect((int) (x + dx), 1895 (int) (y + dy), 1896 (int) (w + dw), 1897 (int) (h + dh)); 1898 } 1899 if (bgPainter != null) { 1900 bgPainter.paint(g, x + dx, y + dy, w + dw, h + dh, v); 1901 } 1902 x += localLeftMargin; 1903 y += topMargin; 1904 w -= localLeftMargin + localRightMargin; 1905 h -= topMargin + bottomMargin; 1906 if (border instanceof BevelBorder) { 1907 //BevelBorder does not support border width 1908 int bw = (int) getLength(CSS.Attribute.BORDER_TOP_WIDTH, a); 1909 for (int i = bw - 1; i >= 0; i--) { 1910 border.paintBorder(null, g, (int) x + i, (int) y + i, 1911 (int) w - 2 * i, (int) h - 2 * i); 1912 } 1913 } else { 1914 border.paintBorder(null, g, (int) x, (int) y, (int) w, (int) h); 1915 } 1916 } 1917 1918 float getLength(CSS.Attribute key, AttributeSet a) { 1919 return css.getLength(a, key, ss); 1920 } 1921 1922 static boolean isLeftToRight(View v) { 1923 boolean ret = true; 1924 if (isOrientationAware(v)) { 1925 Container container; 1926 if (v != null && (container = v.getContainer()) != null) { 1927 ret = container.getComponentOrientation().isLeftToRight(); 1928 } 1929 } 1930 return ret; 1931 } 1932 1933 /* 1934 * only certain tags are concerned about orientation 1935 * <dir>, <menu>, <ul>, <ol> 1936 * for all others we return true. It is implemented this way 1937 * for performance purposes 1938 */ 1939 static boolean isOrientationAware(View v) { 1940 boolean ret = false; 1941 AttributeSet attr; 1942 Object obj; 1943 if (v != null 1944 && (attr = v.getElement().getAttributes()) != null 1945 && (obj = attr.getAttribute(StyleConstants.NameAttribute)) instanceof HTML.Tag 1946 && (obj == HTML.Tag.DIR 1947 || obj == HTML.Tag.MENU 1948 || obj == HTML.Tag.UL 1949 || obj == HTML.Tag.OL)) { 1950 ret = true; 1951 } 1952 1953 return ret; 1954 } 1955 1956 static enum HorizontalMargin { LEFT, RIGHT } 1957 1958 /** 1959 * for <dir>, <menu>, <ul> etc. 1960 * margins are Left-To-Right/Right-To-Left depended. 1961 * see 5088268 for more details 1962 * margin-(left|right)-(ltr|rtl) were introduced to describe it 1963 * if margin-(left|right) is present we are to use it. 1964 * 1965 * @param side The horizontal side to fetch margin for 1966 * This can be HorizontalMargin.LEFT or HorizontalMargin.RIGHT 1967 * @param cssMargin margin from css 1968 * @param a AttributeSet for the View we getting margin for 1969 * @param isLeftToRight 1970 * @return orientation depended margin 1971 */ 1972 float getOrientationMargin(HorizontalMargin side, float cssMargin, 1973 AttributeSet a, boolean isLeftToRight) { 1974 float margin = cssMargin; 1975 float orientationMargin = cssMargin; 1976 Object cssMarginValue = null; 1977 switch (side) { 1978 case RIGHT: 1979 { 1980 orientationMargin = (isLeftToRight) ? 1981 getLength(CSS.Attribute.MARGIN_RIGHT_LTR, a) : 1982 getLength(CSS.Attribute.MARGIN_RIGHT_RTL, a); 1983 cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_RIGHT); 1984 } 1985 break; 1986 case LEFT : 1987 { 1988 orientationMargin = (isLeftToRight) ? 1989 getLength(CSS.Attribute.MARGIN_LEFT_LTR, a) : 1990 getLength(CSS.Attribute.MARGIN_LEFT_RTL, a); 1991 cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_LEFT); 1992 } 1993 break; 1994 } 1995 1996 if (cssMarginValue == null 1997 && orientationMargin != Integer.MIN_VALUE) { 1998 margin = orientationMargin; 1999 } 2000 return margin; 2001 } 2002 2003 float topMargin; 2004 float bottomMargin; 2005 float leftMargin; 2006 float rightMargin; 2007 // Bitmask, used to indicate what margins are relative: 2008 // bit 0 for top, 1 for bottom, 2 for left and 3 for right. 2009 short marginFlags; 2010 Border border; 2011 Insets binsets; 2012 CSS css; 2013 StyleSheet ss; 2014 Color bg; 2015 BackgroundImagePainter bgPainter; 2016 } 2017 2018 /** 2019 * Class to carry out some of the duties of CSS list 2020 * formatting. Implementations of this 2021 * class enable views to present the CSS formatting 2022 * while not knowing anything about how the CSS values 2023 * are being cached. 2024 */ 2025 @SuppressWarnings("serial") // Same-version serialization only 2026 public static class ListPainter implements Serializable { 2027 2028 ListPainter(AttributeSet attr, StyleSheet ss) { 2029 this.ss = ss; 2030 /* Get the image to use as a list bullet */ 2031 String imgstr = (String)attr.getAttribute(CSS.Attribute. 2032 LIST_STYLE_IMAGE); 2033 type = null; 2034 if (imgstr != null && !imgstr.equals("none")) { 2035 String tmpstr = null; 2036 try { 2037 StringTokenizer st = new StringTokenizer(imgstr, "()"); 2038 if (st.hasMoreTokens()) 2039 tmpstr = st.nextToken(); 2040 if (st.hasMoreTokens()) 2041 tmpstr = st.nextToken(); 2042 URL u = new URL(tmpstr); 2043 img = new ImageIcon(u); 2044 } catch (MalformedURLException e) { 2045 if (tmpstr != null && ss != null && ss.getBase() != null) { 2046 try { 2047 URL u = new URL(ss.getBase(), tmpstr); 2048 img = new ImageIcon(u); 2049 } catch (MalformedURLException murle) { 2050 img = null; 2051 } 2052 } 2053 else { 2054 img = null; 2055 } 2056 } 2057 } 2058 2059 /* Get the type of bullet to use in the list */ 2060 if (img == null) { 2061 type = (CSS.Value)attr.getAttribute(CSS.Attribute. 2062 LIST_STYLE_TYPE); 2063 } 2064 start = 1; 2065 2066 paintRect = new Rectangle(); 2067 } 2068 2069 /** 2070 * Returns a string that represents the value 2071 * of the HTML.Attribute.TYPE attribute. 2072 * If this attributes is not defined, then 2073 * then the type defaults to "disc" unless 2074 * the tag is on Ordered list. In the case 2075 * of the latter, the default type is "decimal". 2076 */ 2077 private CSS.Value getChildType(View childView) { 2078 CSS.Value childtype = (CSS.Value)childView.getAttributes(). 2079 getAttribute(CSS.Attribute.LIST_STYLE_TYPE); 2080 2081 if (childtype == null) { 2082 if (type == null) { 2083 // Parent view. 2084 View v = childView.getParent(); 2085 HTMLDocument doc = (HTMLDocument)v.getDocument(); 2086 if (HTMLDocument.matchNameAttribute(v.getElement().getAttributes(), 2087 HTML.Tag.OL)) { 2088 childtype = CSS.Value.DECIMAL; 2089 } else { 2090 childtype = CSS.Value.DISC; 2091 } 2092 } else { 2093 childtype = type; 2094 } 2095 } 2096 return childtype; 2097 } 2098 2099 /** 2100 * Obtains the starting index from <code>parent</code>. 2101 */ 2102 private void getStart(View parent) { 2103 checkedForStart = true; 2104 Element element = parent.getElement(); 2105 if (element != null) { 2106 AttributeSet attr = element.getAttributes(); 2107 Object startValue; 2108 if (attr != null && attr.isDefined(HTML.Attribute.START) && 2109 (startValue = attr.getAttribute 2110 (HTML.Attribute.START)) != null && 2111 (startValue instanceof String)) { 2112 2113 try { 2114 start = Integer.parseInt((String)startValue); 2115 } 2116 catch (NumberFormatException nfe) {} 2117 } 2118 } 2119 } 2120 2121 /** 2122 * Returns an integer that should be used to render the child at 2123 * <code>childIndex</code> with. The retValue will usually be 2124 * <code>childIndex</code> + 1, unless <code>parentView</code> 2125 * has some Views that do not represent LI's, or one of the views 2126 * has a HTML.Attribute.START specified. 2127 */ 2128 private int getRenderIndex(View parentView, int childIndex) { 2129 if (!checkedForStart) { 2130 getStart(parentView); 2131 } 2132 int retIndex = childIndex; 2133 for (int counter = childIndex; counter >= 0; counter--) { 2134 AttributeSet as = parentView.getElement().getElement(counter). 2135 getAttributes(); 2136 if (as.getAttribute(StyleConstants.NameAttribute) != 2137 HTML.Tag.LI) { 2138 retIndex--; 2139 } else if (as.isDefined(HTML.Attribute.VALUE)) { 2140 Object value = as.getAttribute(HTML.Attribute.VALUE); 2141 if (value != null && 2142 (value instanceof String)) { 2143 try { 2144 int iValue = Integer.parseInt((String)value); 2145 return retIndex - counter + iValue; 2146 } 2147 catch (NumberFormatException nfe) {} 2148 } 2149 } 2150 } 2151 return retIndex + start; 2152 } 2153 2154 /** 2155 * Paints the CSS list decoration according to the 2156 * attributes given. 2157 * 2158 * @param g the rendering surface. 2159 * @param x the x coordinate of the list item allocation 2160 * @param y the y coordinate of the list item allocation 2161 * @param w the width of the list item allocation 2162 * @param h the height of the list item allocation 2163 * @param v the allocated area to paint into. 2164 * @param item which list item is being painted. This 2165 * is a number greater than or equal to 0. 2166 */ 2167 public void paint(Graphics g, float x, float y, float w, float h, View v, int item) { 2168 View cv = v.getView(item); 2169 Container host = v.getContainer(); 2170 Object name = cv.getElement().getAttributes().getAttribute 2171 (StyleConstants.NameAttribute); 2172 // Only draw something if the View is a list item. This won't 2173 // be the case for comments. 2174 if (!(name instanceof HTML.Tag) || 2175 name != HTML.Tag.LI) { 2176 return; 2177 } 2178 // deside on what side draw bullets, etc. 2179 isLeftToRight = 2180 host.getComponentOrientation().isLeftToRight(); 2181 2182 // How the list indicator is aligned is not specified, it is 2183 // left up to the UA. IE and NS differ on this behavior. 2184 // This is closer to NS where we align to the first line of text. 2185 // If the child is not text we draw the indicator at the 2186 // origin (0). 2187 float align = 0; 2188 if (cv.getViewCount() > 0) { 2189 View pView = cv.getView(0); 2190 Object cName = pView.getElement().getAttributes(). 2191 getAttribute(StyleConstants.NameAttribute); 2192 if ((cName == HTML.Tag.P || cName == HTML.Tag.IMPLIED) && 2193 pView.getViewCount() > 0) { 2194 paintRect.setBounds((int)x, (int)y, (int)w, (int)h); 2195 Shape shape = cv.getChildAllocation(0, paintRect); 2196 if (shape != null && (shape = pView.getView(0). 2197 getChildAllocation(0, shape)) != null) { 2198 Rectangle rect = (shape instanceof Rectangle) ? 2199 (Rectangle)shape : shape.getBounds(); 2200 2201 align = pView.getView(0).getAlignment(View.Y_AXIS); 2202 y = rect.y; 2203 h = rect.height; 2204 } 2205 } 2206 } 2207 2208 // set the color of a decoration 2209 Color c = (host.isEnabled() 2210 ? (ss != null 2211 ? ss.getForeground(cv.getAttributes()) 2212 : host.getForeground()) 2213 : UIManager.getColor("textInactiveText")); 2214 g.setColor(c); 2215 2216 if (img != null) { 2217 drawIcon(g, (int) x, (int) y, (int) w, (int) h, align, host); 2218 return; 2219 } 2220 CSS.Value childtype = getChildType(cv); 2221 Font font = ((StyledDocument)cv.getDocument()). 2222 getFont(cv.getAttributes()); 2223 if (font != null) { 2224 g.setFont(font); 2225 } 2226 if (childtype == CSS.Value.SQUARE || childtype == CSS.Value.CIRCLE 2227 || childtype == CSS.Value.DISC) { 2228 drawShape(g, childtype, (int) x, (int) y, 2229 (int) w, (int) h, align); 2230 } else if (childtype == CSS.Value.DECIMAL) { 2231 drawLetter(g, '1', (int) x, (int) y, (int) w, (int) h, align, 2232 getRenderIndex(v, item)); 2233 } else if (childtype == CSS.Value.LOWER_ALPHA) { 2234 drawLetter(g, 'a', (int) x, (int) y, (int) w, (int) h, align, 2235 getRenderIndex(v, item)); 2236 } else if (childtype == CSS.Value.UPPER_ALPHA) { 2237 drawLetter(g, 'A', (int) x, (int) y, (int) w, (int) h, align, 2238 getRenderIndex(v, item)); 2239 } else if (childtype == CSS.Value.LOWER_ROMAN) { 2240 drawLetter(g, 'i', (int) x, (int) y, (int) w, (int) h, align, 2241 getRenderIndex(v, item)); 2242 } else if (childtype == CSS.Value.UPPER_ROMAN) { 2243 drawLetter(g, 'I', (int) x, (int) y, (int) w, (int) h, align, 2244 getRenderIndex(v, item)); 2245 } 2246 } 2247 2248 /** 2249 * Draws the bullet icon specified by the list-style-image argument. 2250 * 2251 * @param g the graphics context 2252 * @param ax x coordinate to place the bullet 2253 * @param ay y coordinate to place the bullet 2254 * @param aw width of the container the bullet is placed in 2255 * @param ah height of the container the bullet is placed in 2256 * @param align preferred alignment factor for the child view 2257 */ 2258 void drawIcon(Graphics g, int ax, int ay, int aw, int ah, 2259 float align, Component c) { 2260 // Align to bottom of icon. 2261 int gap = isLeftToRight ? - (img.getIconWidth() + bulletgap) : 2262 (aw + bulletgap); 2263 int x = ax + gap; 2264 int y = Math.max(ay, ay + (int)(align * ah) -img.getIconHeight()); 2265 2266 img.paintIcon(c, g, x, y); 2267 } 2268 2269 /** 2270 * Draws the graphical bullet item specified by the type argument. 2271 * 2272 * @param g the graphics context 2273 * @param type type of bullet to draw (circle, square, disc) 2274 * @param ax x coordinate to place the bullet 2275 * @param ay y coordinate to place the bullet 2276 * @param aw width of the container the bullet is placed in 2277 * @param ah height of the container the bullet is placed in 2278 * @param align preferred alignment factor for the child view 2279 */ 2280 void drawShape(Graphics g, CSS.Value type, int ax, int ay, int aw, 2281 int ah, float align) { 2282 // Align to bottom of shape. 2283 int gap = isLeftToRight ? - (bulletgap + 8) : (aw + bulletgap); 2284 int x = ax + gap; 2285 int y = Math.max(ay, ay + (int)(align * ah) - 8); 2286 2287 if (type == CSS.Value.SQUARE) { 2288 g.drawRect(x, y, 8, 8); 2289 } else if (type == CSS.Value.CIRCLE) { 2290 g.drawOval(x, y, 8, 8); 2291 } else { 2292 g.fillOval(x, y, 8, 8); 2293 } 2294 } 2295 2296 /** 2297 * Draws the letter or number for an ordered list. 2298 * 2299 * @param g the graphics context 2300 * @param letter type of ordered list to draw 2301 * @param ax x coordinate to place the bullet 2302 * @param ay y coordinate to place the bullet 2303 * @param aw width of the container the bullet is placed in 2304 * @param ah height of the container the bullet is placed in 2305 * @param index position of the list item in the list 2306 */ 2307 void drawLetter(Graphics g, char letter, int ax, int ay, int aw, 2308 int ah, float align, int index) { 2309 String str = formatItemNum(index, letter); 2310 str = isLeftToRight ? str + "." : "." + str; 2311 FontMetrics fm = SwingUtilities2.getFontMetrics(null, g); 2312 int stringwidth = SwingUtilities2.stringWidth(null, fm, str); 2313 int gap = isLeftToRight ? - (stringwidth + bulletgap) : 2314 (aw + bulletgap); 2315 int x = ax + gap; 2316 int y = Math.max(ay + fm.getAscent(), ay + (int)(ah * align)); 2317 SwingUtilities2.drawString(null, g, str, x, y); 2318 } 2319 2320 /** 2321 * Converts the item number into the ordered list number 2322 * (i.e. 1 2 3, i ii iii, a b c, etc. 2323 * 2324 * @param itemNum number to format 2325 * @param type type of ordered list 2326 */ 2327 String formatItemNum(int itemNum, char type) { 2328 String numStyle = "1"; 2329 2330 boolean uppercase = false; 2331 2332 String formattedNum; 2333 2334 switch (type) { 2335 case '1': 2336 default: 2337 formattedNum = String.valueOf(itemNum); 2338 break; 2339 2340 case 'A': 2341 uppercase = true; 2342 // fall through 2343 case 'a': 2344 formattedNum = formatAlphaNumerals(itemNum); 2345 break; 2346 2347 case 'I': 2348 uppercase = true; 2349 // fall through 2350 case 'i': 2351 formattedNum = formatRomanNumerals(itemNum); 2352 } 2353 2354 if (uppercase) { 2355 formattedNum = formattedNum.toUpperCase(); 2356 } 2357 2358 return formattedNum; 2359 } 2360 2361 /** 2362 * Converts the item number into an alphabetic character 2363 * 2364 * @param itemNum number to format 2365 */ 2366 String formatAlphaNumerals(int itemNum) { 2367 String result; 2368 2369 if (itemNum > 26) { 2370 result = formatAlphaNumerals(itemNum / 26) + 2371 formatAlphaNumerals(itemNum % 26); 2372 } else { 2373 // -1 because item is 1 based. 2374 result = String.valueOf((char)('a' + itemNum - 1)); 2375 } 2376 2377 return result; 2378 } 2379 2380 /* list of roman numerals */ 2381 static final char romanChars[][] = { 2382 {'i', 'v'}, 2383 {'x', 'l' }, 2384 {'c', 'd' }, 2385 {'m', '?' }, 2386 }; 2387 2388 /** 2389 * Converts the item number into a roman numeral 2390 * 2391 * @param num number to format 2392 */ 2393 String formatRomanNumerals(int num) { 2394 return formatRomanNumerals(0, num); 2395 } 2396 2397 /** 2398 * Converts the item number into a roman numeral 2399 * 2400 * @param num number to format 2401 */ 2402 String formatRomanNumerals(int level, int num) { 2403 if (num < 10) { 2404 return formatRomanDigit(level, num); 2405 } else { 2406 return formatRomanNumerals(level + 1, num / 10) + 2407 formatRomanDigit(level, num % 10); 2408 } 2409 } 2410 2411 2412 /** 2413 * Converts the item number into a roman numeral 2414 * 2415 * @param level position 2416 * @param digit digit to format 2417 */ 2418 String formatRomanDigit(int level, int digit) { 2419 String result = ""; 2420 if (digit == 9) { 2421 result = result + romanChars[level][0]; 2422 result = result + romanChars[level + 1][0]; 2423 return result; 2424 } else if (digit == 4) { 2425 result = result + romanChars[level][0]; 2426 result = result + romanChars[level][1]; 2427 return result; 2428 } else if (digit >= 5) { 2429 result = result + romanChars[level][1]; 2430 digit -= 5; 2431 } 2432 2433 for (int i = 0; i < digit; i++) { 2434 result = result + romanChars[level][0]; 2435 } 2436 2437 return result; 2438 } 2439 2440 private Rectangle paintRect; 2441 private boolean checkedForStart; 2442 private int start; 2443 private CSS.Value type; 2444 URL imageurl; 2445 private StyleSheet ss = null; 2446 Icon img = null; 2447 private int bulletgap = 5; 2448 private boolean isLeftToRight; 2449 } 2450 2451 2452 /** 2453 * Paints the background image. 2454 */ 2455 @SuppressWarnings("serial") // Same-version serialization only 2456 static class BackgroundImagePainter implements Serializable { 2457 ImageIcon backgroundImage; 2458 float hPosition; 2459 float vPosition; 2460 // bit mask: 0 for repeat x, 1 for repeat y, 2 for horiz relative, 2461 // 3 for vert relative 2462 short flags; 2463 // These are used when painting, updatePaintCoordinates updates them. 2464 private int paintX; 2465 private int paintY; 2466 private int paintMaxX; 2467 private int paintMaxY; 2468 2469 BackgroundImagePainter(AttributeSet a, CSS css, StyleSheet ss) { 2470 backgroundImage = ss.getBackgroundImage(a); 2471 // Determine the position. 2472 CSS.BackgroundPosition pos = (CSS.BackgroundPosition)a.getAttribute 2473 (CSS.Attribute.BACKGROUND_POSITION); 2474 if (pos != null) { 2475 hPosition = pos.getHorizontalPosition(); 2476 vPosition = pos.getVerticalPosition(); 2477 if (pos.isHorizontalPositionRelativeToSize()) { 2478 flags |= 4; 2479 } 2480 else if (pos.isHorizontalPositionRelativeToSize()) { 2481 hPosition *= CSS.getFontSize(a, 12, ss); 2482 } 2483 if (pos.isVerticalPositionRelativeToSize()) { 2484 flags |= 8; 2485 } 2486 else if (pos.isVerticalPositionRelativeToFontSize()) { 2487 vPosition *= CSS.getFontSize(a, 12, ss); 2488 } 2489 } 2490 // Determine any repeating values. 2491 CSS.Value repeats = (CSS.Value)a.getAttribute(CSS.Attribute. 2492 BACKGROUND_REPEAT); 2493 if (repeats == null || repeats == CSS.Value.BACKGROUND_REPEAT) { 2494 flags |= 3; 2495 } 2496 else if (repeats == CSS.Value.BACKGROUND_REPEAT_X) { 2497 flags |= 1; 2498 } 2499 else if (repeats == CSS.Value.BACKGROUND_REPEAT_Y) { 2500 flags |= 2; 2501 } 2502 } 2503 2504 void paint(Graphics g, float x, float y, float w, float h, View v) { 2505 Rectangle clip = g.getClipRect(); 2506 if (clip != null) { 2507 // Constrain the clip so that images don't draw outside the 2508 // legal bounds. 2509 g.clipRect((int)x, (int)y, (int)w, (int)h); 2510 } 2511 if ((flags & 3) == 0) { 2512 // no repeating 2513 int width = backgroundImage.getIconWidth(); 2514 int height = backgroundImage.getIconWidth(); 2515 if ((flags & 4) == 4) { 2516 paintX = (int)(x + w * hPosition - 2517 (float)width * hPosition); 2518 } 2519 else { 2520 paintX = (int)x + (int)hPosition; 2521 } 2522 if ((flags & 8) == 8) { 2523 paintY = (int)(y + h * vPosition - 2524 (float)height * vPosition); 2525 } 2526 else { 2527 paintY = (int)y + (int)vPosition; 2528 } 2529 if (clip == null || 2530 !((paintX + width <= clip.x) || 2531 (paintY + height <= clip.y) || 2532 (paintX >= clip.x + clip.width) || 2533 (paintY >= clip.y + clip.height))) { 2534 backgroundImage.paintIcon(null, g, paintX, paintY); 2535 } 2536 } 2537 else { 2538 int width = backgroundImage.getIconWidth(); 2539 int height = backgroundImage.getIconHeight(); 2540 if (width > 0 && height > 0) { 2541 paintX = (int)x; 2542 paintY = (int)y; 2543 paintMaxX = (int)(x + w); 2544 paintMaxY = (int)(y + h); 2545 if (updatePaintCoordinates(clip, width, height)) { 2546 while (paintX < paintMaxX) { 2547 int ySpot = paintY; 2548 while (ySpot < paintMaxY) { 2549 backgroundImage.paintIcon(null, g, paintX, 2550 ySpot); 2551 ySpot += height; 2552 } 2553 paintX += width; 2554 } 2555 } 2556 } 2557 } 2558 if (clip != null) { 2559 // Reset clip. 2560 g.setClip(clip.x, clip.y, clip.width, clip.height); 2561 } 2562 } 2563 2564 private boolean updatePaintCoordinates 2565 (Rectangle clip, int width, int height){ 2566 if ((flags & 3) == 1) { 2567 paintMaxY = paintY + 1; 2568 } 2569 else if ((flags & 3) == 2) { 2570 paintMaxX = paintX + 1; 2571 } 2572 if (clip != null) { 2573 if ((flags & 3) == 1 && ((paintY + height <= clip.y) || 2574 (paintY > clip.y + clip.height))) { 2575 // not visible. 2576 return false; 2577 } 2578 if ((flags & 3) == 2 && ((paintX + width <= clip.x) || 2579 (paintX > clip.x + clip.width))) { 2580 // not visible. 2581 return false; 2582 } 2583 if ((flags & 1) == 1) { 2584 if ((clip.x + clip.width) < paintMaxX) { 2585 if ((clip.x + clip.width - paintX) % width == 0) { 2586 paintMaxX = clip.x + clip.width; 2587 } 2588 else { 2589 paintMaxX = ((clip.x + clip.width - paintX) / 2590 width + 1) * width + paintX; 2591 } 2592 } 2593 if (clip.x > paintX) { 2594 paintX = (clip.x - paintX) / width * width + paintX; 2595 } 2596 } 2597 if ((flags & 2) == 2) { 2598 if ((clip.y + clip.height) < paintMaxY) { 2599 if ((clip.y + clip.height - paintY) % height == 0) { 2600 paintMaxY = clip.y + clip.height; 2601 } 2602 else { 2603 paintMaxY = ((clip.y + clip.height - paintY) / 2604 height + 1) * height + paintY; 2605 } 2606 } 2607 if (clip.y > paintY) { 2608 paintY = (clip.y - paintY) / height * height + paintY; 2609 } 2610 } 2611 } 2612 // Valid 2613 return true; 2614 } 2615 } 2616 2617 2618 /** 2619 * A subclass of MuxingAttributeSet that translates between 2620 * CSS and HTML and StyleConstants. The AttributeSets used are 2621 * the CSS rules that match the Views Elements. 2622 */ 2623 @SuppressWarnings("serial") // Same-version serialization only 2624 class ViewAttributeSet extends MuxingAttributeSet { 2625 ViewAttributeSet(View v) { 2626 host = v; 2627 2628 // PENDING(prinz) fix this up to be a more realistic 2629 // implementation. 2630 Document doc = v.getDocument(); 2631 SearchBuffer sb = SearchBuffer.obtainSearchBuffer(); 2632 Vector<AttributeSet> muxList = sb.getVector(); 2633 try { 2634 if (doc instanceof HTMLDocument) { 2635 StyleSheet styles = StyleSheet.this; 2636 Element elem = v.getElement(); 2637 AttributeSet a = elem.getAttributes(); 2638 AttributeSet htmlAttr = styles.translateHTMLToCSS(a); 2639 2640 if (htmlAttr.getAttributeCount() != 0) { 2641 muxList.addElement(htmlAttr); 2642 } 2643 if (elem.isLeaf()) { 2644 Enumeration keys = a.getAttributeNames(); 2645 while (keys.hasMoreElements()) { 2646 Object key = keys.nextElement(); 2647 if (key instanceof HTML.Tag) { 2648 if (key == HTML.Tag.A) { 2649 Object o = a.getAttribute(key); 2650 /** 2651 In the case of an A tag, the css rules 2652 apply only for tags that have their 2653 href attribute defined and not for 2654 anchors that only have their name attributes 2655 defined, i.e anchors that function as 2656 destinations. Hence we do not add the 2657 attributes for that latter kind of 2658 anchors. When CSS2 support is added, 2659 it will be possible to specificity this 2660 kind of conditional behaviour in the 2661 stylesheet. 2662 **/ 2663 if (o != null && o instanceof AttributeSet) { 2664 AttributeSet attr = (AttributeSet)o; 2665 if (attr.getAttribute(HTML.Attribute.HREF) == null) { 2666 continue; 2667 } 2668 } 2669 } 2670 AttributeSet cssRule = styles.getRule((HTML.Tag) key, elem); 2671 if (cssRule != null) { 2672 muxList.addElement(cssRule); 2673 } 2674 } 2675 } 2676 } else { 2677 HTML.Tag t = (HTML.Tag) a.getAttribute 2678 (StyleConstants.NameAttribute); 2679 AttributeSet cssRule = styles.getRule(t, elem); 2680 if (cssRule != null) { 2681 muxList.addElement(cssRule); 2682 } 2683 } 2684 } 2685 AttributeSet[] attrs = new AttributeSet[muxList.size()]; 2686 muxList.copyInto(attrs); 2687 setAttributes(attrs); 2688 } 2689 finally { 2690 SearchBuffer.releaseSearchBuffer(sb); 2691 } 2692 } 2693 2694 // --- AttributeSet methods ---------------------------- 2695 2696 /** 2697 * Checks whether a given attribute is defined. 2698 * This will convert the key over to CSS if the 2699 * key is a StyleConstants key that has a CSS 2700 * mapping. 2701 * 2702 * @param key the attribute key 2703 * @return true if the attribute is defined 2704 * @see AttributeSet#isDefined 2705 */ 2706 public boolean isDefined(Object key) { 2707 if (key instanceof StyleConstants) { 2708 Object cssKey = css.styleConstantsKeyToCSSKey 2709 ((StyleConstants)key); 2710 if (cssKey != null) { 2711 key = cssKey; 2712 } 2713 } 2714 return super.isDefined(key); 2715 } 2716 2717 /** 2718 * Gets the value of an attribute. If the requested 2719 * attribute is a StyleConstants attribute that has 2720 * a CSS mapping, the request will be converted. 2721 * 2722 * @param key the attribute name 2723 * @return the attribute value 2724 * @see AttributeSet#getAttribute 2725 */ 2726 public Object getAttribute(Object key) { 2727 if (key instanceof StyleConstants) { 2728 Object cssKey = css.styleConstantsKeyToCSSKey 2729 ((StyleConstants)key); 2730 if (cssKey != null) { 2731 Object value = doGetAttribute(cssKey); 2732 if (value instanceof CSS.CssValue) { 2733 return ((CSS.CssValue)value).toStyleConstants 2734 ((StyleConstants)key, host); 2735 } 2736 } 2737 } 2738 return doGetAttribute(key); 2739 } 2740 2741 Object doGetAttribute(Object key) { 2742 Object retValue = super.getAttribute(key); 2743 if (retValue != null) { 2744 return retValue; 2745 } 2746 // didn't find it... try parent if it's a css attribute 2747 // that is inherited. 2748 if (key instanceof CSS.Attribute) { 2749 CSS.Attribute css = (CSS.Attribute) key; 2750 if (css.isInherited()) { 2751 AttributeSet parent = getResolveParent(); 2752 if (parent != null) 2753 return parent.getAttribute(key); 2754 } 2755 } 2756 return null; 2757 } 2758 2759 /** 2760 * If not overriden, the resolving parent defaults to 2761 * the parent element. 2762 * 2763 * @return the attributes from the parent 2764 * @see AttributeSet#getResolveParent 2765 */ 2766 public AttributeSet getResolveParent() { 2767 if (host == null) { 2768 return null; 2769 } 2770 View parent = host.getParent(); 2771 return (parent != null) ? parent.getAttributes() : null; 2772 } 2773 2774 /** View created for. */ 2775 View host; 2776 } 2777 2778 2779 /** 2780 * A subclass of MuxingAttributeSet that implements Style. Currently 2781 * the MutableAttributeSet methods are unimplemented, that is they 2782 * do nothing. 2783 */ 2784 // PENDING(sky): Decide what to do with this. Either make it 2785 // contain a SimpleAttributeSet that modify methods are delegated to, 2786 // or change getRule to return an AttributeSet and then don't make this 2787 // implement Style. 2788 @SuppressWarnings("serial") // Same-version serialization only 2789 static class ResolvedStyle extends MuxingAttributeSet implements 2790 Serializable, Style { 2791 ResolvedStyle(String name, AttributeSet[] attrs, int extendedIndex) { 2792 super(attrs); 2793 this.name = name; 2794 this.extendedIndex = extendedIndex; 2795 } 2796 2797 /** 2798 * Inserts a Style into the receiver so that the styles the 2799 * receiver represents are still ordered by specificity. 2800 * <code>style</code> will be added before any extended styles, that 2801 * is before extendedIndex. 2802 */ 2803 synchronized void insertStyle(Style style, int specificity) { 2804 AttributeSet[] attrs = getAttributes(); 2805 int maxCounter = attrs.length; 2806 int counter = 0; 2807 for (;counter < extendedIndex; counter++) { 2808 if (specificity > getSpecificity(((Style)attrs[counter]). 2809 getName())) { 2810 break; 2811 } 2812 } 2813 insertAttributeSetAt(style, counter); 2814 extendedIndex++; 2815 } 2816 2817 /** 2818 * Removes a previously added style. This will do nothing if 2819 * <code>style</code> is not referenced by the receiver. 2820 */ 2821 synchronized void removeStyle(Style style) { 2822 AttributeSet[] attrs = getAttributes(); 2823 2824 for (int counter = attrs.length - 1; counter >= 0; counter--) { 2825 if (attrs[counter] == style) { 2826 removeAttributeSetAt(counter); 2827 if (counter < extendedIndex) { 2828 extendedIndex--; 2829 } 2830 break; 2831 } 2832 } 2833 } 2834 2835 /** 2836 * Adds <code>s</code> as one of the Attributesets to look up 2837 * attributes in. 2838 */ 2839 synchronized void insertExtendedStyleAt(Style attr, int index) { 2840 insertAttributeSetAt(attr, extendedIndex + index); 2841 } 2842 2843 /** 2844 * Adds <code>s</code> as one of the AttributeSets to look up 2845 * attributes in. It will be the AttributeSet last checked. 2846 */ 2847 synchronized void addExtendedStyle(Style attr) { 2848 insertAttributeSetAt(attr, getAttributes().length); 2849 } 2850 2851 /** 2852 * Removes the style at <code>index</code> + 2853 * <code>extendedIndex</code>. 2854 */ 2855 synchronized void removeExtendedStyleAt(int index) { 2856 removeAttributeSetAt(extendedIndex + index); 2857 } 2858 2859 /** 2860 * Returns true if the receiver matches <code>selector</code>, where 2861 * a match is defined by the CSS rule matching. 2862 * Each simple selector must be separated by a single space. 2863 */ 2864 protected boolean matches(String selector) { 2865 int sLast = selector.length(); 2866 2867 if (sLast == 0) { 2868 return false; 2869 } 2870 int thisLast = name.length(); 2871 int sCurrent = selector.lastIndexOf(' '); 2872 int thisCurrent = name.lastIndexOf(' '); 2873 if (sCurrent >= 0) { 2874 sCurrent++; 2875 } 2876 if (thisCurrent >= 0) { 2877 thisCurrent++; 2878 } 2879 if (!matches(selector, sCurrent, sLast, thisCurrent, thisLast)) { 2880 return false; 2881 } 2882 while (sCurrent != -1) { 2883 sLast = sCurrent - 1; 2884 sCurrent = selector.lastIndexOf(' ', sLast - 1); 2885 if (sCurrent >= 0) { 2886 sCurrent++; 2887 } 2888 boolean match = false; 2889 while (!match && thisCurrent != -1) { 2890 thisLast = thisCurrent - 1; 2891 thisCurrent = name.lastIndexOf(' ', thisLast - 1); 2892 if (thisCurrent >= 0) { 2893 thisCurrent++; 2894 } 2895 match = matches(selector, sCurrent, sLast, thisCurrent, 2896 thisLast); 2897 } 2898 if (!match) { 2899 return false; 2900 } 2901 } 2902 return true; 2903 } 2904 2905 /** 2906 * Returns true if the substring of the receiver, in the range 2907 * thisCurrent, thisLast matches the substring of selector in 2908 * the ranme sCurrent to sLast based on CSS selector matching. 2909 */ 2910 boolean matches(String selector, int sCurrent, int sLast, 2911 int thisCurrent, int thisLast) { 2912 sCurrent = Math.max(sCurrent, 0); 2913 thisCurrent = Math.max(thisCurrent, 0); 2914 int thisDotIndex = boundedIndexOf(name, '.', thisCurrent, 2915 thisLast); 2916 int thisPoundIndex = boundedIndexOf(name, '#', thisCurrent, 2917 thisLast); 2918 int sDotIndex = boundedIndexOf(selector, '.', sCurrent, sLast); 2919 int sPoundIndex = boundedIndexOf(selector, '#', sCurrent, sLast); 2920 if (sDotIndex != -1) { 2921 // Selector has a '.', which indicates name must match it, 2922 // or if the '.' starts the selector than name must have 2923 // the same class (doesn't matter what element name). 2924 if (thisDotIndex == -1) { 2925 return false; 2926 } 2927 if (sCurrent == sDotIndex) { 2928 if ((thisLast - thisDotIndex) != (sLast - sDotIndex) || 2929 !selector.regionMatches(sCurrent, name, thisDotIndex, 2930 (thisLast - thisDotIndex))) { 2931 return false; 2932 } 2933 } 2934 else { 2935 // Has to fully match. 2936 if ((sLast - sCurrent) != (thisLast - thisCurrent) || 2937 !selector.regionMatches(sCurrent, name, thisCurrent, 2938 (thisLast - thisCurrent))) { 2939 return false; 2940 } 2941 } 2942 return true; 2943 } 2944 if (sPoundIndex != -1) { 2945 // Selector has a '#', which indicates name must match it, 2946 // or if the '#' starts the selector than name must have 2947 // the same id (doesn't matter what element name). 2948 if (thisPoundIndex == -1) { 2949 return false; 2950 } 2951 if (sCurrent == sPoundIndex) { 2952 if ((thisLast - thisPoundIndex) !=(sLast - sPoundIndex) || 2953 !selector.regionMatches(sCurrent, name, thisPoundIndex, 2954 (thisLast - thisPoundIndex))) { 2955 return false; 2956 } 2957 } 2958 else { 2959 // Has to fully match. 2960 if ((sLast - sCurrent) != (thisLast - thisCurrent) || 2961 !selector.regionMatches(sCurrent, name, thisCurrent, 2962 (thisLast - thisCurrent))) { 2963 return false; 2964 } 2965 } 2966 return true; 2967 } 2968 if (thisDotIndex != -1) { 2969 // Receiver references a class, just check element name. 2970 return (((thisDotIndex - thisCurrent) == (sLast - sCurrent)) && 2971 selector.regionMatches(sCurrent, name, thisCurrent, 2972 thisDotIndex - thisCurrent)); 2973 } 2974 if (thisPoundIndex != -1) { 2975 // Receiver references an id, just check element name. 2976 return (((thisPoundIndex - thisCurrent) ==(sLast - sCurrent))&& 2977 selector.regionMatches(sCurrent, name, thisCurrent, 2978 thisPoundIndex - thisCurrent)); 2979 } 2980 // Fail through, no classes or ides, just check string. 2981 return (((thisLast - thisCurrent) == (sLast - sCurrent)) && 2982 selector.regionMatches(sCurrent, name, thisCurrent, 2983 thisLast - thisCurrent)); 2984 } 2985 2986 /** 2987 * Similar to String.indexOf, but allows an upper bound 2988 * (this is slower in that it will still check string starting at 2989 * start. 2990 */ 2991 int boundedIndexOf(String string, char search, int start, 2992 int end) { 2993 int retValue = string.indexOf(search, start); 2994 if (retValue >= end) { 2995 return -1; 2996 } 2997 return retValue; 2998 } 2999 3000 public void addAttribute(Object name, Object value) {} 3001 public void addAttributes(AttributeSet attributes) {} 3002 public void removeAttribute(Object name) {} 3003 public void removeAttributes(Enumeration<?> names) {} 3004 public void removeAttributes(AttributeSet attributes) {} 3005 public void setResolveParent(AttributeSet parent) {} 3006 public String getName() {return name;} 3007 public void addChangeListener(ChangeListener l) {} 3008 public void removeChangeListener(ChangeListener l) {} 3009 public ChangeListener[] getChangeListeners() { 3010 return new ChangeListener[0]; 3011 } 3012 3013 /** The name of the Style, which is the selector. 3014 * This will NEVER change! 3015 */ 3016 String name; 3017 /** Start index of styles coming from other StyleSheets. */ 3018 private int extendedIndex; 3019 } 3020 3021 3022 /** 3023 * SelectorMapping contains a specifitiy, as an integer, and an associated 3024 * Style. It can also reference children <code>SelectorMapping</code>s, 3025 * so that it behaves like a tree. 3026 * <p> 3027 * This is not thread safe, it is assumed the caller will take the 3028 * necessary precations if this is to be used in a threaded environment. 3029 */ 3030 @SuppressWarnings("serial") // Same-version serialization only 3031 static class SelectorMapping implements Serializable { 3032 public SelectorMapping(int specificity) { 3033 this.specificity = specificity; 3034 } 3035 3036 /** 3037 * Returns the specificity this mapping represents. 3038 */ 3039 public int getSpecificity() { 3040 return specificity; 3041 } 3042 3043 /** 3044 * Sets the Style associated with this mapping. 3045 */ 3046 public void setStyle(Style style) { 3047 this.style = style; 3048 } 3049 3050 /** 3051 * Returns the Style associated with this mapping. 3052 */ 3053 public Style getStyle() { 3054 return style; 3055 } 3056 3057 /** 3058 * Returns the child mapping identified by the simple selector 3059 * <code>selector</code>. If a child mapping does not exist for 3060 *<code>selector</code>, and <code>create</code> is true, a new 3061 * one will be created. 3062 */ 3063 public SelectorMapping getChildSelectorMapping(String selector, 3064 boolean create) { 3065 SelectorMapping retValue = null; 3066 3067 if (children != null) { 3068 retValue = children.get(selector); 3069 } 3070 else if (create) { 3071 children = new HashMap<String, SelectorMapping>(7); 3072 } 3073 if (retValue == null && create) { 3074 int specificity = getChildSpecificity(selector); 3075 3076 retValue = createChildSelectorMapping(specificity); 3077 children.put(selector, retValue); 3078 } 3079 return retValue; 3080 } 3081 3082 /** 3083 * Creates a child <code>SelectorMapping</code> with the specified 3084 * <code>specificity</code>. 3085 */ 3086 protected SelectorMapping createChildSelectorMapping(int specificity) { 3087 return new SelectorMapping(specificity); 3088 } 3089 3090 /** 3091 * Returns the specificity for the child selector 3092 * <code>selector</code>. 3093 */ 3094 protected int getChildSpecificity(String selector) { 3095 // class (.) 100 3096 // id (#) 10000 3097 char firstChar = selector.charAt(0); 3098 int specificity = getSpecificity(); 3099 3100 if (firstChar == '.') { 3101 specificity += 100; 3102 } 3103 else if (firstChar == '#') { 3104 specificity += 10000; 3105 } 3106 else { 3107 specificity += 1; 3108 if (selector.indexOf('.') != -1) { 3109 specificity += 100; 3110 } 3111 if (selector.indexOf('#') != -1) { 3112 specificity += 10000; 3113 } 3114 } 3115 return specificity; 3116 } 3117 3118 /** 3119 * The specificity for this selector. 3120 */ 3121 private int specificity; 3122 /** 3123 * Style for this selector. 3124 */ 3125 private Style style; 3126 /** 3127 * Any sub selectors. Key will be String, and value will be 3128 * another SelectorMapping. 3129 */ 3130 private HashMap<String, SelectorMapping> children; 3131 } 3132 3133 3134 // ---- Variables --------------------------------------------- 3135 3136 final static int DEFAULT_FONT_SIZE = 3; 3137 3138 private CSS css; 3139 3140 /** 3141 * An inverted graph of the selectors. 3142 */ 3143 private SelectorMapping selectorMapping; 3144 3145 /** Maps from selector (as a string) to Style that includes all 3146 * relevant styles. */ 3147 private Hashtable<String, ResolvedStyle> resolvedStyles; 3148 3149 /** Vector of StyleSheets that the rules are to reference. 3150 */ 3151 private Vector<StyleSheet> linkedStyleSheets; 3152 3153 /** Where the style sheet was found. Used for relative imports. */ 3154 private URL base; 3155 3156 3157 /** 3158 * Default parser for CSS specifications that get loaded into 3159 * the StyleSheet.<p> 3160 * This class is NOT thread safe, do not ask it to parse while it is 3161 * in the middle of parsing. 3162 */ 3163 class CssParser implements CSSParser.CSSParserCallback { 3164 3165 /** 3166 * Parses the passed in CSS declaration into an AttributeSet. 3167 */ 3168 public AttributeSet parseDeclaration(String string) { 3169 try { 3170 return parseDeclaration(new StringReader(string)); 3171 } catch (IOException ioe) {} 3172 return null; 3173 } 3174 3175 /** 3176 * Parses the passed in CSS declaration into an AttributeSet. 3177 */ 3178 public AttributeSet parseDeclaration(Reader r) throws IOException { 3179 parse(base, r, true, false); 3180 return declaration.copyAttributes(); 3181 } 3182 3183 /** 3184 * Parse the given CSS stream 3185 */ 3186 public void parse(URL base, Reader r, boolean parseDeclaration, 3187 boolean isLink) throws IOException { 3188 this.base = base; 3189 this.isLink = isLink; 3190 this.parsingDeclaration = parseDeclaration; 3191 declaration.removeAttributes(declaration); 3192 selectorTokens.removeAllElements(); 3193 selectors.removeAllElements(); 3194 propertyName = null; 3195 parser.parse(r, this, parseDeclaration); 3196 } 3197 3198 // 3199 // CSSParserCallback methods, public to implement the interface. 3200 // 3201 3202 /** 3203 * Invoked when a valid @import is encountered, will call 3204 * <code>importStyleSheet</code> if a 3205 * <code>MalformedURLException</code> is not thrown in creating 3206 * the URL. 3207 */ 3208 public void handleImport(String importString) { 3209 URL url = CSS.getURL(base, importString); 3210 if (url != null) { 3211 importStyleSheet(url); 3212 } 3213 } 3214 3215 /** 3216 * A selector has been encountered. 3217 */ 3218 public void handleSelector(String selector) { 3219 //class and index selectors are case sensitive 3220 if (!(selector.startsWith(".") 3221 || selector.startsWith("#"))) { 3222 selector = selector.toLowerCase(); 3223 } 3224 int length = selector.length(); 3225 3226 if (selector.endsWith(",")) { 3227 if (length > 1) { 3228 selector = selector.substring(0, length - 1); 3229 selectorTokens.addElement(selector); 3230 } 3231 addSelector(); 3232 } 3233 else if (length > 0) { 3234 selectorTokens.addElement(selector); 3235 } 3236 } 3237 3238 /** 3239 * Invoked when the start of a rule is encountered. 3240 */ 3241 public void startRule() { 3242 if (selectorTokens.size() > 0) { 3243 addSelector(); 3244 } 3245 propertyName = null; 3246 } 3247 3248 /** 3249 * Invoked when a property name is encountered. 3250 */ 3251 public void handleProperty(String property) { 3252 propertyName = property; 3253 } 3254 3255 /** 3256 * Invoked when a property value is encountered. 3257 */ 3258 public void handleValue(String value) { 3259 if (propertyName != null && value != null && value.length() > 0) { 3260 CSS.Attribute cssKey = CSS.getAttribute(propertyName); 3261 if (cssKey != null) { 3262 // There is currently no mechanism to determine real 3263 // base that style sheet was loaded from. For the time 3264 // being, this maps for LIST_STYLE_IMAGE, which appear 3265 // to be the only one that currently matters. A more 3266 // general mechanism is definately needed. 3267 if (cssKey == CSS.Attribute.LIST_STYLE_IMAGE) { 3268 if (value != null && !value.equals("none")) { 3269 URL url = CSS.getURL(base, value); 3270 3271 if (url != null) { 3272 value = url.toString(); 3273 } 3274 } 3275 } 3276 addCSSAttribute(declaration, cssKey, value); 3277 } 3278 propertyName = null; 3279 } 3280 } 3281 3282 /** 3283 * Invoked when the end of a rule is encountered. 3284 */ 3285 public void endRule() { 3286 int n = selectors.size(); 3287 for (int i = 0; i < n; i++) { 3288 String[] selector = selectors.elementAt(i); 3289 if (selector.length > 0) { 3290 StyleSheet.this.addRule(selector, declaration, isLink); 3291 } 3292 } 3293 declaration.removeAttributes(declaration); 3294 selectors.removeAllElements(); 3295 } 3296 3297 private void addSelector() { 3298 String[] selector = new String[selectorTokens.size()]; 3299 selectorTokens.copyInto(selector); 3300 selectors.addElement(selector); 3301 selectorTokens.removeAllElements(); 3302 } 3303 3304 3305 Vector<String[]> selectors = new Vector<String[]>(); 3306 Vector<String> selectorTokens = new Vector<String>(); 3307 /** Name of the current property. */ 3308 String propertyName; 3309 MutableAttributeSet declaration = new SimpleAttributeSet(); 3310 /** True if parsing a declaration, that is the Reader will not 3311 * contain a selector. */ 3312 boolean parsingDeclaration; 3313 /** True if the attributes are coming from a linked/imported style. */ 3314 boolean isLink; 3315 /** Where the CSS stylesheet lives. */ 3316 URL base; 3317 CSSParser parser = new CSSParser(); 3318 } 3319 3320 void rebaseSizeMap(int base) { 3321 final int minimalFontSize = 4; 3322 sizeMap = new int[sizeMapDefault.length]; 3323 for (int i = 0; i < sizeMapDefault.length; i++) { 3324 sizeMap[i] = Math.max(base * sizeMapDefault[i] / 3325 sizeMapDefault[CSS.baseFontSizeIndex], 3326 minimalFontSize); 3327 } 3328 3329 } 3330 3331 int[] getSizeMap() { 3332 return sizeMap; 3333 } 3334 boolean isW3CLengthUnits() { 3335 return w3cLengthUnits; 3336 } 3337 3338 /** 3339 * The HTML/CSS size model has seven slots 3340 * that one can assign sizes to. 3341 */ 3342 static final int sizeMapDefault[] = { 8, 10, 12, 14, 18, 24, 36 }; 3343 3344 private int sizeMap[] = sizeMapDefault; 3345 private boolean w3cLengthUnits = false; 3346 }