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 * 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 @SuppressWarnings("fallthrough") 2328 String formatItemNum(int itemNum, char type) { 2329 String numStyle = "1"; 2330 2331 boolean uppercase = false; 2332 2333 String formattedNum; 2334 2335 switch (type) { 2336 case '1': 2337 default: 2338 formattedNum = String.valueOf(itemNum); 2339 break; 2340 2341 case 'A': 2342 uppercase = true; 2343 // fall through 2344 case 'a': 2345 formattedNum = formatAlphaNumerals(itemNum); 2346 break; 2347 2348 case 'I': 2349 uppercase = true; 2350 // fall through 2351 case 'i': 2352 formattedNum = formatRomanNumerals(itemNum); 2353 } 2354 2355 if (uppercase) { 2356 formattedNum = formattedNum.toUpperCase(); 2357 } 2358 2359 return formattedNum; 2360 } 2361 2362 /** 2363 * Converts the item number into an alphabetic character 2364 * 2365 * @param itemNum number to format 2366 */ 2367 String formatAlphaNumerals(int itemNum) { 2368 String result; 2369 2370 if (itemNum > 26) { 2371 result = formatAlphaNumerals(itemNum / 26) + 2372 formatAlphaNumerals(itemNum % 26); 2373 } else { 2374 // -1 because item is 1 based. 2375 result = String.valueOf((char)('a' + itemNum - 1)); 2376 } 2377 2378 return result; 2379 } 2380 2381 /* list of roman numerals */ 2382 static final char romanChars[][] = { 2383 {'i', 'v'}, 2384 {'x', 'l' }, 2385 {'c', 'd' }, 2386 {'m', '?' }, 2387 }; 2388 2389 /** 2390 * Converts the item number into a roman numeral 2391 * 2392 * @param num number to format 2393 */ 2394 String formatRomanNumerals(int num) { 2395 return formatRomanNumerals(0, num); 2396 } 2397 2398 /** 2399 * Converts the item number into a roman numeral 2400 * 2401 * @param num number to format 2402 */ 2403 String formatRomanNumerals(int level, int num) { 2404 if (num < 10) { 2405 return formatRomanDigit(level, num); 2406 } else { 2407 return formatRomanNumerals(level + 1, num / 10) + 2408 formatRomanDigit(level, num % 10); 2409 } 2410 } 2411 2412 2413 /** 2414 * Converts the item number into a roman numeral 2415 * 2416 * @param level position 2417 * @param digit digit to format 2418 */ 2419 String formatRomanDigit(int level, int digit) { 2420 String result = ""; 2421 if (digit == 9) { 2422 result = result + romanChars[level][0]; 2423 result = result + romanChars[level + 1][0]; 2424 return result; 2425 } else if (digit == 4) { 2426 result = result + romanChars[level][0]; 2427 result = result + romanChars[level][1]; 2428 return result; 2429 } else if (digit >= 5) { 2430 result = result + romanChars[level][1]; 2431 digit -= 5; 2432 } 2433 2434 for (int i = 0; i < digit; i++) { 2435 result = result + romanChars[level][0]; 2436 } 2437 2438 return result; 2439 } 2440 2441 private Rectangle paintRect; 2442 private boolean checkedForStart; 2443 private int start; 2444 private CSS.Value type; 2445 URL imageurl; 2446 private StyleSheet ss = null; 2447 Icon img = null; 2448 private int bulletgap = 5; 2449 private boolean isLeftToRight; 2450 } 2451 2452 2453 /** 2454 * Paints the background image. 2455 */ 2456 @SuppressWarnings("serial") // Same-version serialization only 2457 static class BackgroundImagePainter implements Serializable { 2458 ImageIcon backgroundImage; 2459 float hPosition; 2460 float vPosition; 2461 // bit mask: 0 for repeat x, 1 for repeat y, 2 for horiz relative, 2462 // 3 for vert relative 2463 short flags; 2464 // These are used when painting, updatePaintCoordinates updates them. 2465 private int paintX; 2466 private int paintY; 2467 private int paintMaxX; 2468 private int paintMaxY; 2469 2470 BackgroundImagePainter(AttributeSet a, CSS css, StyleSheet ss) { 2471 backgroundImage = ss.getBackgroundImage(a); 2472 // Determine the position. 2473 CSS.BackgroundPosition pos = (CSS.BackgroundPosition)a.getAttribute 2474 (CSS.Attribute.BACKGROUND_POSITION); 2475 if (pos != null) { 2476 hPosition = pos.getHorizontalPosition(); 2477 vPosition = pos.getVerticalPosition(); 2478 if (pos.isHorizontalPositionRelativeToSize()) { 2479 flags |= 4; 2480 } 2481 else if (pos.isHorizontalPositionRelativeToSize()) { 2482 hPosition *= CSS.getFontSize(a, 12, ss); 2483 } 2484 if (pos.isVerticalPositionRelativeToSize()) { 2485 flags |= 8; 2486 } 2487 else if (pos.isVerticalPositionRelativeToFontSize()) { 2488 vPosition *= CSS.getFontSize(a, 12, ss); 2489 } 2490 } 2491 // Determine any repeating values. 2492 CSS.Value repeats = (CSS.Value)a.getAttribute(CSS.Attribute. 2493 BACKGROUND_REPEAT); 2494 if (repeats == null || repeats == CSS.Value.BACKGROUND_REPEAT) { 2495 flags |= 3; 2496 } 2497 else if (repeats == CSS.Value.BACKGROUND_REPEAT_X) { 2498 flags |= 1; 2499 } 2500 else if (repeats == CSS.Value.BACKGROUND_REPEAT_Y) { 2501 flags |= 2; 2502 } 2503 } 2504 2505 void paint(Graphics g, float x, float y, float w, float h, View v) { 2506 Rectangle clip = g.getClipRect(); 2507 if (clip != null) { 2508 // Constrain the clip so that images don't draw outside the 2509 // legal bounds. 2510 g.clipRect((int)x, (int)y, (int)w, (int)h); 2511 } 2512 if ((flags & 3) == 0) { 2513 // no repeating 2514 int width = backgroundImage.getIconWidth(); 2515 int height = backgroundImage.getIconWidth(); 2516 if ((flags & 4) == 4) { 2517 paintX = (int)(x + w * hPosition - 2518 (float)width * hPosition); 2519 } 2520 else { 2521 paintX = (int)x + (int)hPosition; 2522 } 2523 if ((flags & 8) == 8) { 2524 paintY = (int)(y + h * vPosition - 2525 (float)height * vPosition); 2526 } 2527 else { 2528 paintY = (int)y + (int)vPosition; 2529 } 2530 if (clip == null || 2531 !((paintX + width <= clip.x) || 2532 (paintY + height <= clip.y) || 2533 (paintX >= clip.x + clip.width) || 2534 (paintY >= clip.y + clip.height))) { 2535 backgroundImage.paintIcon(null, g, paintX, paintY); 2536 } 2537 } 2538 else { 2539 int width = backgroundImage.getIconWidth(); 2540 int height = backgroundImage.getIconHeight(); 2541 if (width > 0 && height > 0) { 2542 paintX = (int)x; 2543 paintY = (int)y; 2544 paintMaxX = (int)(x + w); 2545 paintMaxY = (int)(y + h); 2546 if (updatePaintCoordinates(clip, width, height)) { 2547 while (paintX < paintMaxX) { 2548 int ySpot = paintY; 2549 while (ySpot < paintMaxY) { 2550 backgroundImage.paintIcon(null, g, paintX, 2551 ySpot); 2552 ySpot += height; 2553 } 2554 paintX += width; 2555 } 2556 } 2557 } 2558 } 2559 if (clip != null) { 2560 // Reset clip. 2561 g.setClip(clip.x, clip.y, clip.width, clip.height); 2562 } 2563 } 2564 2565 private boolean updatePaintCoordinates 2566 (Rectangle clip, int width, int height){ 2567 if ((flags & 3) == 1) { 2568 paintMaxY = paintY + 1; 2569 } 2570 else if ((flags & 3) == 2) { 2571 paintMaxX = paintX + 1; 2572 } 2573 if (clip != null) { 2574 if ((flags & 3) == 1 && ((paintY + height <= clip.y) || 2575 (paintY > clip.y + clip.height))) { 2576 // not visible. 2577 return false; 2578 } 2579 if ((flags & 3) == 2 && ((paintX + width <= clip.x) || 2580 (paintX > clip.x + clip.width))) { 2581 // not visible. 2582 return false; 2583 } 2584 if ((flags & 1) == 1) { 2585 if ((clip.x + clip.width) < paintMaxX) { 2586 if ((clip.x + clip.width - paintX) % width == 0) { 2587 paintMaxX = clip.x + clip.width; 2588 } 2589 else { 2590 paintMaxX = ((clip.x + clip.width - paintX) / 2591 width + 1) * width + paintX; 2592 } 2593 } 2594 if (clip.x > paintX) { 2595 paintX = (clip.x - paintX) / width * width + paintX; 2596 } 2597 } 2598 if ((flags & 2) == 2) { 2599 if ((clip.y + clip.height) < paintMaxY) { 2600 if ((clip.y + clip.height - paintY) % height == 0) { 2601 paintMaxY = clip.y + clip.height; 2602 } 2603 else { 2604 paintMaxY = ((clip.y + clip.height - paintY) / 2605 height + 1) * height + paintY; 2606 } 2607 } 2608 if (clip.y > paintY) { 2609 paintY = (clip.y - paintY) / height * height + paintY; 2610 } 2611 } 2612 } 2613 // Valid 2614 return true; 2615 } 2616 } 2617 2618 2619 /** 2620 * A subclass of MuxingAttributeSet that translates between 2621 * CSS and HTML and StyleConstants. The AttributeSets used are 2622 * the CSS rules that match the Views Elements. 2623 */ 2624 @SuppressWarnings("serial") // Same-version serialization only 2625 class ViewAttributeSet extends MuxingAttributeSet { 2626 ViewAttributeSet(View v) { 2627 host = v; 2628 2629 // PENDING(prinz) fix this up to be a more realistic 2630 // implementation. 2631 Document doc = v.getDocument(); 2632 SearchBuffer sb = SearchBuffer.obtainSearchBuffer(); 2633 Vector<AttributeSet> muxList = sb.getVector(); 2634 try { 2635 if (doc instanceof HTMLDocument) { 2636 StyleSheet styles = StyleSheet.this; 2637 Element elem = v.getElement(); 2638 AttributeSet a = elem.getAttributes(); 2639 AttributeSet htmlAttr = styles.translateHTMLToCSS(a); 2640 2641 if (htmlAttr.getAttributeCount() != 0) { 2642 muxList.addElement(htmlAttr); 2643 } 2644 if (elem.isLeaf()) { 2645 Enumeration keys = a.getAttributeNames(); 2646 while (keys.hasMoreElements()) { 2647 Object key = keys.nextElement(); 2648 if (key instanceof HTML.Tag) { 2649 if (key == HTML.Tag.A) { 2650 Object o = a.getAttribute(key); 2651 /** 2652 In the case of an A tag, the css rules 2653 apply only for tags that have their 2654 href attribute defined and not for 2655 anchors that only have their name attributes 2656 defined, i.e anchors that function as 2657 destinations. Hence we do not add the 2658 attributes for that latter kind of 2659 anchors. When CSS2 support is added, 2660 it will be possible to specificity this 2661 kind of conditional behaviour in the 2662 stylesheet. 2663 **/ 2664 if (o != null && o instanceof AttributeSet) { 2665 AttributeSet attr = (AttributeSet)o; 2666 if (attr.getAttribute(HTML.Attribute.HREF) == null) { 2667 continue; 2668 } 2669 } 2670 } 2671 AttributeSet cssRule = styles.getRule((HTML.Tag) key, elem); 2672 if (cssRule != null) { 2673 muxList.addElement(cssRule); 2674 } 2675 } 2676 } 2677 } else { 2678 HTML.Tag t = (HTML.Tag) a.getAttribute 2679 (StyleConstants.NameAttribute); 2680 AttributeSet cssRule = styles.getRule(t, elem); 2681 if (cssRule != null) { 2682 muxList.addElement(cssRule); 2683 } 2684 } 2685 } 2686 AttributeSet[] attrs = new AttributeSet[muxList.size()]; 2687 muxList.copyInto(attrs); 2688 setAttributes(attrs); 2689 } 2690 finally { 2691 SearchBuffer.releaseSearchBuffer(sb); 2692 } 2693 } 2694 2695 // --- AttributeSet methods ---------------------------- 2696 2697 /** 2698 * Checks whether a given attribute is defined. 2699 * This will convert the key over to CSS if the 2700 * key is a StyleConstants key that has a CSS 2701 * mapping. 2702 * 2703 * @param key the attribute key 2704 * @return true if the attribute is defined 2705 * @see AttributeSet#isDefined 2706 */ 2707 public boolean isDefined(Object key) { 2708 if (key instanceof StyleConstants) { 2709 Object cssKey = css.styleConstantsKeyToCSSKey 2710 ((StyleConstants)key); 2711 if (cssKey != null) { 2712 key = cssKey; 2713 } 2714 } 2715 return super.isDefined(key); 2716 } 2717 2718 /** 2719 * Gets the value of an attribute. If the requested 2720 * attribute is a StyleConstants attribute that has 2721 * a CSS mapping, the request will be converted. 2722 * 2723 * @param key the attribute name 2724 * @return the attribute value 2725 * @see AttributeSet#getAttribute 2726 */ 2727 public Object getAttribute(Object key) { 2728 if (key instanceof StyleConstants) { 2729 Object cssKey = css.styleConstantsKeyToCSSKey 2730 ((StyleConstants)key); 2731 if (cssKey != null) { 2732 Object value = doGetAttribute(cssKey); 2733 if (value instanceof CSS.CssValue) { 2734 return ((CSS.CssValue)value).toStyleConstants 2735 ((StyleConstants)key, host); 2736 } 2737 } 2738 } 2739 return doGetAttribute(key); 2740 } 2741 2742 Object doGetAttribute(Object key) { 2743 Object retValue = super.getAttribute(key); 2744 if (retValue != null) { 2745 return retValue; 2746 } 2747 // didn't find it... try parent if it's a css attribute 2748 // that is inherited. 2749 if (key instanceof CSS.Attribute) { 2750 CSS.Attribute css = (CSS.Attribute) key; 2751 if (css.isInherited()) { 2752 AttributeSet parent = getResolveParent(); 2753 if (parent != null) 2754 return parent.getAttribute(key); 2755 } 2756 } 2757 return null; 2758 } 2759 2760 /** 2761 * If not overriden, the resolving parent defaults to 2762 * the parent element. 2763 * 2764 * @return the attributes from the parent 2765 * @see AttributeSet#getResolveParent 2766 */ 2767 public AttributeSet getResolveParent() { 2768 if (host == null) { 2769 return null; 2770 } 2771 View parent = host.getParent(); 2772 return (parent != null) ? parent.getAttributes() : null; 2773 } 2774 2775 /** View created for. */ 2776 View host; 2777 } 2778 2779 2780 /** 2781 * A subclass of MuxingAttributeSet that implements Style. Currently 2782 * the MutableAttributeSet methods are unimplemented, that is they 2783 * do nothing. 2784 */ 2785 // PENDING(sky): Decide what to do with this. Either make it 2786 // contain a SimpleAttributeSet that modify methods are delegated to, 2787 // or change getRule to return an AttributeSet and then don't make this 2788 // implement Style. 2789 @SuppressWarnings("serial") // Same-version serialization only 2790 static class ResolvedStyle extends MuxingAttributeSet implements 2791 Serializable, Style { 2792 ResolvedStyle(String name, AttributeSet[] attrs, int extendedIndex) { 2793 super(attrs); 2794 this.name = name; 2795 this.extendedIndex = extendedIndex; 2796 } 2797 2798 /** 2799 * Inserts a Style into the receiver so that the styles the 2800 * receiver represents are still ordered by specificity. 2801 * <code>style</code> will be added before any extended styles, that 2802 * is before extendedIndex. 2803 */ 2804 synchronized void insertStyle(Style style, int specificity) { 2805 AttributeSet[] attrs = getAttributes(); 2806 int maxCounter = attrs.length; 2807 int counter = 0; 2808 for (;counter < extendedIndex; counter++) { 2809 if (specificity > getSpecificity(((Style)attrs[counter]). 2810 getName())) { 2811 break; 2812 } 2813 } 2814 insertAttributeSetAt(style, counter); 2815 extendedIndex++; 2816 } 2817 2818 /** 2819 * Removes a previously added style. This will do nothing if 2820 * <code>style</code> is not referenced by the receiver. 2821 */ 2822 synchronized void removeStyle(Style style) { 2823 AttributeSet[] attrs = getAttributes(); 2824 2825 for (int counter = attrs.length - 1; counter >= 0; counter--) { 2826 if (attrs[counter] == style) { 2827 removeAttributeSetAt(counter); 2828 if (counter < extendedIndex) { 2829 extendedIndex--; 2830 } 2831 break; 2832 } 2833 } 2834 } 2835 2836 /** 2837 * Adds <code>s</code> as one of the Attributesets to look up 2838 * attributes in. 2839 */ 2840 synchronized void insertExtendedStyleAt(Style attr, int index) { 2841 insertAttributeSetAt(attr, extendedIndex + index); 2842 } 2843 2844 /** 2845 * Adds <code>s</code> as one of the AttributeSets to look up 2846 * attributes in. It will be the AttributeSet last checked. 2847 */ 2848 synchronized void addExtendedStyle(Style attr) { 2849 insertAttributeSetAt(attr, getAttributes().length); 2850 } 2851 2852 /** 2853 * Removes the style at <code>index</code> + 2854 * <code>extendedIndex</code>. 2855 */ 2856 synchronized void removeExtendedStyleAt(int index) { 2857 removeAttributeSetAt(extendedIndex + index); 2858 } 2859 2860 /** 2861 * Returns true if the receiver matches <code>selector</code>, where 2862 * a match is defined by the CSS rule matching. 2863 * Each simple selector must be separated by a single space. 2864 */ 2865 protected boolean matches(String selector) { 2866 int sLast = selector.length(); 2867 2868 if (sLast == 0) { 2869 return false; 2870 } 2871 int thisLast = name.length(); 2872 int sCurrent = selector.lastIndexOf(' '); 2873 int thisCurrent = name.lastIndexOf(' '); 2874 if (sCurrent >= 0) { 2875 sCurrent++; 2876 } 2877 if (thisCurrent >= 0) { 2878 thisCurrent++; 2879 } 2880 if (!matches(selector, sCurrent, sLast, thisCurrent, thisLast)) { 2881 return false; 2882 } 2883 while (sCurrent != -1) { 2884 sLast = sCurrent - 1; 2885 sCurrent = selector.lastIndexOf(' ', sLast - 1); 2886 if (sCurrent >= 0) { 2887 sCurrent++; 2888 } 2889 boolean match = false; 2890 while (!match && thisCurrent != -1) { 2891 thisLast = thisCurrent - 1; 2892 thisCurrent = name.lastIndexOf(' ', thisLast - 1); 2893 if (thisCurrent >= 0) { 2894 thisCurrent++; 2895 } 2896 match = matches(selector, sCurrent, sLast, thisCurrent, 2897 thisLast); 2898 } 2899 if (!match) { 2900 return false; 2901 } 2902 } 2903 return true; 2904 } 2905 2906 /** 2907 * Returns true if the substring of the receiver, in the range 2908 * thisCurrent, thisLast matches the substring of selector in 2909 * the ranme sCurrent to sLast based on CSS selector matching. 2910 */ 2911 boolean matches(String selector, int sCurrent, int sLast, 2912 int thisCurrent, int thisLast) { 2913 sCurrent = Math.max(sCurrent, 0); 2914 thisCurrent = Math.max(thisCurrent, 0); 2915 int thisDotIndex = boundedIndexOf(name, '.', thisCurrent, 2916 thisLast); 2917 int thisPoundIndex = boundedIndexOf(name, '#', thisCurrent, 2918 thisLast); 2919 int sDotIndex = boundedIndexOf(selector, '.', sCurrent, sLast); 2920 int sPoundIndex = boundedIndexOf(selector, '#', sCurrent, sLast); 2921 if (sDotIndex != -1) { 2922 // Selector has a '.', which indicates name must match it, 2923 // or if the '.' starts the selector than name must have 2924 // the same class (doesn't matter what element name). 2925 if (thisDotIndex == -1) { 2926 return false; 2927 } 2928 if (sCurrent == sDotIndex) { 2929 if ((thisLast - thisDotIndex) != (sLast - sDotIndex) || 2930 !selector.regionMatches(sCurrent, name, thisDotIndex, 2931 (thisLast - thisDotIndex))) { 2932 return false; 2933 } 2934 } 2935 else { 2936 // Has to fully match. 2937 if ((sLast - sCurrent) != (thisLast - thisCurrent) || 2938 !selector.regionMatches(sCurrent, name, thisCurrent, 2939 (thisLast - thisCurrent))) { 2940 return false; 2941 } 2942 } 2943 return true; 2944 } 2945 if (sPoundIndex != -1) { 2946 // Selector has a '#', which indicates name must match it, 2947 // or if the '#' starts the selector than name must have 2948 // the same id (doesn't matter what element name). 2949 if (thisPoundIndex == -1) { 2950 return false; 2951 } 2952 if (sCurrent == sPoundIndex) { 2953 if ((thisLast - thisPoundIndex) !=(sLast - sPoundIndex) || 2954 !selector.regionMatches(sCurrent, name, thisPoundIndex, 2955 (thisLast - thisPoundIndex))) { 2956 return false; 2957 } 2958 } 2959 else { 2960 // Has to fully match. 2961 if ((sLast - sCurrent) != (thisLast - thisCurrent) || 2962 !selector.regionMatches(sCurrent, name, thisCurrent, 2963 (thisLast - thisCurrent))) { 2964 return false; 2965 } 2966 } 2967 return true; 2968 } 2969 if (thisDotIndex != -1) { 2970 // Receiver references a class, just check element name. 2971 return (((thisDotIndex - thisCurrent) == (sLast - sCurrent)) && 2972 selector.regionMatches(sCurrent, name, thisCurrent, 2973 thisDotIndex - thisCurrent)); 2974 } 2975 if (thisPoundIndex != -1) { 2976 // Receiver references an id, just check element name. 2977 return (((thisPoundIndex - thisCurrent) ==(sLast - sCurrent))&& 2978 selector.regionMatches(sCurrent, name, thisCurrent, 2979 thisPoundIndex - thisCurrent)); 2980 } 2981 // Fail through, no classes or ides, just check string. 2982 return (((thisLast - thisCurrent) == (sLast - sCurrent)) && 2983 selector.regionMatches(sCurrent, name, thisCurrent, 2984 thisLast - thisCurrent)); 2985 } 2986 2987 /** 2988 * Similar to String.indexOf, but allows an upper bound 2989 * (this is slower in that it will still check string starting at 2990 * start. 2991 */ 2992 int boundedIndexOf(String string, char search, int start, 2993 int end) { 2994 int retValue = string.indexOf(search, start); 2995 if (retValue >= end) { 2996 return -1; 2997 } 2998 return retValue; 2999 } 3000 3001 public void addAttribute(Object name, Object value) {} 3002 public void addAttributes(AttributeSet attributes) {} 3003 public void removeAttribute(Object name) {} 3004 public void removeAttributes(Enumeration<?> names) {} 3005 public void removeAttributes(AttributeSet attributes) {} 3006 public void setResolveParent(AttributeSet parent) {} 3007 public String getName() {return name;} 3008 public void addChangeListener(ChangeListener l) {} 3009 public void removeChangeListener(ChangeListener l) {} 3010 public ChangeListener[] getChangeListeners() { 3011 return new ChangeListener[0]; 3012 } 3013 3014 /** The name of the Style, which is the selector. 3015 * This will NEVER change! 3016 */ 3017 String name; 3018 /** Start index of styles coming from other StyleSheets. */ 3019 private int extendedIndex; 3020 } 3021 3022 3023 /** 3024 * SelectorMapping contains a specifitiy, as an integer, and an associated 3025 * Style. It can also reference children <code>SelectorMapping</code>s, 3026 * so that it behaves like a tree. 3027 * <p> 3028 * This is not thread safe, it is assumed the caller will take the 3029 * necessary precations if this is to be used in a threaded environment. 3030 */ 3031 @SuppressWarnings("serial") // Same-version serialization only 3032 static class SelectorMapping implements Serializable { 3033 public SelectorMapping(int specificity) { 3034 this.specificity = specificity; 3035 } 3036 3037 /** 3038 * Returns the specificity this mapping represents. 3039 */ 3040 public int getSpecificity() { 3041 return specificity; 3042 } 3043 3044 /** 3045 * Sets the Style associated with this mapping. 3046 */ 3047 public void setStyle(Style style) { 3048 this.style = style; 3049 } 3050 3051 /** 3052 * Returns the Style associated with this mapping. 3053 */ 3054 public Style getStyle() { 3055 return style; 3056 } 3057 3058 /** 3059 * Returns the child mapping identified by the simple selector 3060 * <code>selector</code>. If a child mapping does not exist for 3061 *<code>selector</code>, and <code>create</code> is true, a new 3062 * one will be created. 3063 */ 3064 public SelectorMapping getChildSelectorMapping(String selector, 3065 boolean create) { 3066 SelectorMapping retValue = null; 3067 3068 if (children != null) { 3069 retValue = children.get(selector); 3070 } 3071 else if (create) { 3072 children = new HashMap<String, SelectorMapping>(7); 3073 } 3074 if (retValue == null && create) { 3075 int specificity = getChildSpecificity(selector); 3076 3077 retValue = createChildSelectorMapping(specificity); 3078 children.put(selector, retValue); 3079 } 3080 return retValue; 3081 } 3082 3083 /** 3084 * Creates a child <code>SelectorMapping</code> with the specified 3085 * <code>specificity</code>. 3086 */ 3087 protected SelectorMapping createChildSelectorMapping(int specificity) { 3088 return new SelectorMapping(specificity); 3089 } 3090 3091 /** 3092 * Returns the specificity for the child selector 3093 * <code>selector</code>. 3094 */ 3095 protected int getChildSpecificity(String selector) { 3096 // class (.) 100 3097 // id (#) 10000 3098 char firstChar = selector.charAt(0); 3099 int specificity = getSpecificity(); 3100 3101 if (firstChar == '.') { 3102 specificity += 100; 3103 } 3104 else if (firstChar == '#') { 3105 specificity += 10000; 3106 } 3107 else { 3108 specificity += 1; 3109 if (selector.indexOf('.') != -1) { 3110 specificity += 100; 3111 } 3112 if (selector.indexOf('#') != -1) { 3113 specificity += 10000; 3114 } 3115 } 3116 return specificity; 3117 } 3118 3119 /** 3120 * The specificity for this selector. 3121 */ 3122 private int specificity; 3123 /** 3124 * Style for this selector. 3125 */ 3126 private Style style; 3127 /** 3128 * Any sub selectors. Key will be String, and value will be 3129 * another SelectorMapping. 3130 */ 3131 private HashMap<String, SelectorMapping> children; 3132 } 3133 3134 3135 // ---- Variables --------------------------------------------- 3136 3137 final static int DEFAULT_FONT_SIZE = 3; 3138 3139 private CSS css; 3140 3141 /** 3142 * An inverted graph of the selectors. 3143 */ 3144 private SelectorMapping selectorMapping; 3145 3146 /** Maps from selector (as a string) to Style that includes all 3147 * relevant styles. */ 3148 private Hashtable<String, ResolvedStyle> resolvedStyles; 3149 3150 /** Vector of StyleSheets that the rules are to reference. 3151 */ 3152 private Vector<StyleSheet> linkedStyleSheets; 3153 3154 /** Where the style sheet was found. Used for relative imports. */ 3155 private URL base; 3156 3157 3158 /** 3159 * Default parser for CSS specifications that get loaded into 3160 * the StyleSheet.<p> 3161 * This class is NOT thread safe, do not ask it to parse while it is 3162 * in the middle of parsing. 3163 */ 3164 class CssParser implements CSSParser.CSSParserCallback { 3165 3166 /** 3167 * Parses the passed in CSS declaration into an AttributeSet. 3168 */ 3169 public AttributeSet parseDeclaration(String string) { 3170 try { 3171 return parseDeclaration(new StringReader(string)); 3172 } catch (IOException ioe) {} 3173 return null; 3174 } 3175 3176 /** 3177 * Parses the passed in CSS declaration into an AttributeSet. 3178 */ 3179 public AttributeSet parseDeclaration(Reader r) throws IOException { 3180 parse(base, r, true, false); 3181 return declaration.copyAttributes(); 3182 } 3183 3184 /** 3185 * Parse the given CSS stream 3186 */ 3187 public void parse(URL base, Reader r, boolean parseDeclaration, 3188 boolean isLink) throws IOException { 3189 this.base = base; 3190 this.isLink = isLink; 3191 this.parsingDeclaration = parseDeclaration; 3192 declaration.removeAttributes(declaration); 3193 selectorTokens.removeAllElements(); 3194 selectors.removeAllElements(); 3195 propertyName = null; 3196 parser.parse(r, this, parseDeclaration); 3197 } 3198 3199 // 3200 // CSSParserCallback methods, public to implement the interface. 3201 // 3202 3203 /** 3204 * Invoked when a valid @import is encountered, will call 3205 * <code>importStyleSheet</code> if a 3206 * <code>MalformedURLException</code> is not thrown in creating 3207 * the URL. 3208 */ 3209 public void handleImport(String importString) { 3210 URL url = CSS.getURL(base, importString); 3211 if (url != null) { 3212 importStyleSheet(url); 3213 } 3214 } 3215 3216 /** 3217 * A selector has been encountered. 3218 */ 3219 public void handleSelector(String selector) { 3220 //class and index selectors are case sensitive 3221 if (!(selector.startsWith(".") 3222 || selector.startsWith("#"))) { 3223 selector = selector.toLowerCase(); 3224 } 3225 int length = selector.length(); 3226 3227 if (selector.endsWith(",")) { 3228 if (length > 1) { 3229 selector = selector.substring(0, length - 1); 3230 selectorTokens.addElement(selector); 3231 } 3232 addSelector(); 3233 } 3234 else if (length > 0) { 3235 selectorTokens.addElement(selector); 3236 } 3237 } 3238 3239 /** 3240 * Invoked when the start of a rule is encountered. 3241 */ 3242 public void startRule() { 3243 if (selectorTokens.size() > 0) { 3244 addSelector(); 3245 } 3246 propertyName = null; 3247 } 3248 3249 /** 3250 * Invoked when a property name is encountered. 3251 */ 3252 public void handleProperty(String property) { 3253 propertyName = property; 3254 } 3255 3256 /** 3257 * Invoked when a property value is encountered. 3258 */ 3259 public void handleValue(String value) { 3260 if (propertyName != null && value != null && value.length() > 0) { 3261 CSS.Attribute cssKey = CSS.getAttribute(propertyName); 3262 if (cssKey != null) { 3263 // There is currently no mechanism to determine real 3264 // base that style sheet was loaded from. For the time 3265 // being, this maps for LIST_STYLE_IMAGE, which appear 3266 // to be the only one that currently matters. A more 3267 // general mechanism is definately needed. 3268 if (cssKey == CSS.Attribute.LIST_STYLE_IMAGE) { 3269 if (value != null && !value.equals("none")) { 3270 URL url = CSS.getURL(base, value); 3271 3272 if (url != null) { 3273 value = url.toString(); 3274 } 3275 } 3276 } 3277 addCSSAttribute(declaration, cssKey, value); 3278 } 3279 propertyName = null; 3280 } 3281 } 3282 3283 /** 3284 * Invoked when the end of a rule is encountered. 3285 */ 3286 public void endRule() { 3287 int n = selectors.size(); 3288 for (int i = 0; i < n; i++) { 3289 String[] selector = selectors.elementAt(i); 3290 if (selector.length > 0) { 3291 StyleSheet.this.addRule(selector, declaration, isLink); 3292 } 3293 } 3294 declaration.removeAttributes(declaration); 3295 selectors.removeAllElements(); 3296 } 3297 3298 private void addSelector() { 3299 String[] selector = new String[selectorTokens.size()]; 3300 selectorTokens.copyInto(selector); 3301 selectors.addElement(selector); 3302 selectorTokens.removeAllElements(); 3303 } 3304 3305 3306 Vector<String[]> selectors = new Vector<String[]>(); 3307 Vector<String> selectorTokens = new Vector<String>(); 3308 /** Name of the current property. */ 3309 String propertyName; 3310 MutableAttributeSet declaration = new SimpleAttributeSet(); 3311 /** True if parsing a declaration, that is the Reader will not 3312 * contain a selector. */ 3313 boolean parsingDeclaration; 3314 /** True if the attributes are coming from a linked/imported style. */ 3315 boolean isLink; 3316 /** Where the CSS stylesheet lives. */ 3317 URL base; 3318 CSSParser parser = new CSSParser(); 3319 } 3320 3321 void rebaseSizeMap(int base) { 3322 final int minimalFontSize = 4; 3323 sizeMap = new int[sizeMapDefault.length]; 3324 for (int i = 0; i < sizeMapDefault.length; i++) { 3325 sizeMap[i] = Math.max(base * sizeMapDefault[i] / 3326 sizeMapDefault[CSS.baseFontSizeIndex], 3327 minimalFontSize); 3328 } 3329 3330 } 3331 3332 int[] getSizeMap() { 3333 return sizeMap; 3334 } 3335 boolean isW3CLengthUnits() { 3336 return w3cLengthUnits; 3337 } 3338 3339 /** 3340 * The HTML/CSS size model has seven slots 3341 * that one can assign sizes to. 3342 */ 3343 static final int sizeMapDefault[] = { 8, 10, 12, 14, 18, 24, 36 }; 3344 3345 private int sizeMap[] = sizeMapDefault; 3346 private boolean w3cLengthUnits = false; 3347 }