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