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