1 /* 2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing.text.html; 26 27 import sun.swing.SwingUtilities2; 28 import java.util.*; 29 import java.awt.*; 30 import java.io.*; 31 import java.net.*; 32 import javax.swing.Icon; 33 import javax.swing.ImageIcon; 34 import javax.swing.UIManager; 35 import javax.swing.border.*; 36 import javax.swing.event.ChangeListener; 37 import javax.swing.text.*; 38 39 /** 40 * Support for defining the visual characteristics of 41 * HTML views being rendered. The StyleSheet is used to 42 * translate the HTML model into visual characteristics. 43 * This enables views to be customized by a look-and-feel, 44 * multiple views over the same model can be rendered 45 * differently, etc. This can be thought of as a CSS 46 * rule repository. The key for CSS attributes is an 47 * object of type CSS.Attribute. The type of the value 48 * is up to the StyleSheet implementation, but the 49 * <code>toString</code> method is required 50 * to return a string representation of CSS value. 51 * <p> 52 * The primary entry point for HTML View implementations 53 * to get their attributes is the 54 * {@link #getViewAttributes getViewAttributes} 55 * method. This should be implemented to establish the 56 * desired policy used to associate attributes with the view. 57 * Each HTMLEditorKit (i.e. and therefore each associated 58 * JEditorPane) can have its own StyleSheet, but by default one 59 * sheet will be shared by all of the HTMLEditorKit instances. 60 * HTMLDocument instance can also have a StyleSheet, which 61 * holds the document-specific CSS specifications. 62 * <p> 63 * In order for Views to store less state and therefore be 64 * more lightweight, the StyleSheet can act as a factory for 65 * painters that handle some of the rendering tasks. This allows 66 * implementations to determine what they want to cache 67 * and have the sharing potentially at the level that a 68 * selector is common to multiple views. Since the StyleSheet 69 * may be used by views over multiple documents and typically 70 * the HTML attributes don't effect the selector being used, 71 * the potential for sharing is significant. 72 * <p> 73 * The rules are stored as named styles, and other information 74 * is stored to translate the context of an element to a 75 * rule quickly. The following code fragment will display 76 * the named styles, and therefore the CSS rules contained. 77 * <pre><code> 78 * 79 * import java.util.*; 80 * import javax.swing.text.*; 81 * import javax.swing.text.html.*; 82 * 83 * public class ShowStyles { 84 * 85 * public static void main(String[] args) { 86 * HTMLEditorKit kit = new HTMLEditorKit(); 87 * HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument(); 88 * StyleSheet styles = doc.getStyleSheet(); 89 * 90 * Enumeration rules = styles.getStyleNames(); 91 * while (rules.hasMoreElements()) { 92 * String name = (String) rules.nextElement(); 93 * Style rule = styles.getStyle(name); 94 * System.out.println(rule.toString()); 95 * } 96 * System.exit(0); 97 * } 98 * } 99 * 100 * </code></pre> 101 * <p> 102 * The semantics for when a CSS style should overide visual attributes 103 * defined by an element are not well defined. For example, the html 104 * <code><body bgcolor=red></code> makes the body have a red 105 * background. But if the html file also contains the CSS rule 106 * <code>body { background: blue }</code> it becomes less clear as to 107 * what color the background of the body should be. The current 108 * implementation gives visual attributes defined in the element the 109 * highest precedence, that is they are always checked before any styles. 110 * Therefore, in the previous example the background would have a 111 * red color as the body element defines the background color to be red. 112 * <p> 113 * As already mentioned this supports CSS. We don't support the full CSS 114 * spec. Refer to the javadoc of the CSS class to see what properties 115 * we support. The two major CSS parsing related 116 * concepts we do not currently 117 * support are pseudo selectors, such as <code>A:link { color: red }</code>, 118 * and the <code>important</code> modifier. 119 * 120 * @implNote This implementation is currently 121 * incomplete. It can be replaced with alternative implementations 122 * that are complete. Future versions of this class will provide 123 * better CSS support. 124 * 125 * @author Timothy Prinzing 126 * @author Sunita Mani 127 * @author Sara Swanson 128 * @author Jill Nakata 129 */ 130 @SuppressWarnings("serial") // Superclass is not serializable across versions 131 public class StyleSheet extends StyleContext { 132 // As the javadoc states, this class maintains a mapping between 133 // a CSS selector (such as p.bar) and a Style. 134 // This consists of a number of parts: 135 // . Each selector is broken down into its constituent simple selectors, 136 // and stored in an inverted graph, for example: 137 // p { color: red } ol p { font-size: 10pt } ul p { font-size: 12pt } 138 // results in the graph: 139 // root 140 // | 141 // p 142 // / \ 143 // ol ul 144 // each node (an instance of SelectorMapping) has an associated 145 // specificity and potentially a Style. 146 // . Every rule that is asked for (either by way of getRule(String) or 147 // getRule(HTML.Tag, Element)) results in a unique instance of 148 // ResolvedStyle. ResolvedStyles contain the AttributeSets from the 149 // SelectorMapping. 150 // . When a new rule is created it is inserted into the graph, and 151 // the AttributeSets of each ResolvedStyles are updated appropriately. 152 // . This class creates special AttributeSets, LargeConversionSet and 153 // SmallConversionSet, that maintain a mapping between StyleConstants 154 // and CSS so that developers that wish to use the StyleConstants 155 // methods can do so. 156 // . When one of the AttributeSets is mutated by way of a 157 // StyleConstants key, all the associated CSS keys are removed. This is 158 // done so that the two representations don't get out of sync. For 159 // example, if the developer adds 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 // Align to bottom of shape. 2343 int gap = isLeftToRight ? - (bulletgap + 8) : (aw + bulletgap); 2344 int x = ax + gap; 2345 int y = Math.max(ay, ay + (int)(align * ah) - 8); 2346 2347 if (type == CSS.Value.SQUARE) { 2348 g.drawRect(x, y, 8, 8); 2349 } else if (type == CSS.Value.CIRCLE) { 2350 g.drawOval(x, y, 8, 8); 2351 } else { 2352 g.fillOval(x, y, 8, 8); 2353 } 2354 } 2355 2356 /** 2357 * Draws the letter or number for an ordered list. 2358 * 2359 * @param g the graphics context 2360 * @param letter type of ordered list to draw 2361 * @param ax x coordinate to place the bullet 2362 * @param ay y coordinate to place the bullet 2363 * @param aw width of the container the bullet is placed in 2364 * @param ah height of the container the bullet is placed in 2365 * @param index position of the list item in the list 2366 */ 2367 void drawLetter(Graphics g, char letter, int ax, int ay, int aw, 2368 int ah, float align, int index) { 2369 String str = formatItemNum(index, letter); 2370 str = isLeftToRight ? str + "." : "." + str; 2371 FontMetrics fm = SwingUtilities2.getFontMetrics(null, g); 2372 int stringwidth = SwingUtilities2.stringWidth(null, fm, str); 2373 int gap = isLeftToRight ? - (stringwidth + bulletgap) : 2374 (aw + bulletgap); 2375 int x = ax + gap; 2376 int y = Math.max(ay + fm.getAscent(), ay + (int)(ah * align)); 2377 SwingUtilities2.drawString(null, g, str, x, y); 2378 } 2379 2380 /** 2381 * Converts the item number into the ordered list number 2382 * (i.e. 1 2 3, i ii iii, a b c, etc. 2383 * 2384 * @param itemNum number to format 2385 * @param type type of ordered list 2386 */ 2387 @SuppressWarnings("fallthrough") 2388 String formatItemNum(int itemNum, char type) { 2389 String numStyle = "1"; 2390 2391 boolean uppercase = false; 2392 2393 String formattedNum; 2394 2395 switch (type) { 2396 case '1': 2397 default: 2398 formattedNum = String.valueOf(itemNum); 2399 break; 2400 2401 case 'A': 2402 uppercase = true; 2403 // fall through 2404 case 'a': 2405 formattedNum = formatAlphaNumerals(itemNum); 2406 break; 2407 2408 case 'I': 2409 uppercase = true; 2410 // fall through 2411 case 'i': 2412 formattedNum = formatRomanNumerals(itemNum); 2413 } 2414 2415 if (uppercase) { 2416 formattedNum = formattedNum.toUpperCase(); 2417 } 2418 2419 return formattedNum; 2420 } 2421 2422 /** 2423 * Converts the item number into an alphabetic character 2424 * 2425 * @param itemNum number to format 2426 */ 2427 String formatAlphaNumerals(int itemNum) { 2428 String result; 2429 2430 if (itemNum > 26) { 2431 result = formatAlphaNumerals(itemNum / 26) + 2432 formatAlphaNumerals(itemNum % 26); 2433 } else { 2434 // -1 because item is 1 based. 2435 result = String.valueOf((char)('a' + itemNum - 1)); 2436 } 2437 2438 return result; 2439 } 2440 2441 /* list of roman numerals */ 2442 static final char romanChars[][] = { 2443 {'i', 'v'}, 2444 {'x', 'l' }, 2445 {'c', 'd' }, 2446 {'m', '?' }, 2447 }; 2448 2449 /** 2450 * Converts the item number into a roman numeral 2451 * 2452 * @param num number to format 2453 */ 2454 String formatRomanNumerals(int num) { 2455 return formatRomanNumerals(0, num); 2456 } 2457 2458 /** 2459 * Converts the item number into a roman numeral 2460 * 2461 * @param num number to format 2462 */ 2463 String formatRomanNumerals(int level, int num) { 2464 if (num < 10) { 2465 return formatRomanDigit(level, num); 2466 } else { 2467 return formatRomanNumerals(level + 1, num / 10) + 2468 formatRomanDigit(level, num % 10); 2469 } 2470 } 2471 2472 2473 /** 2474 * Converts the item number into a roman numeral 2475 * 2476 * @param level position 2477 * @param digit digit to format 2478 */ 2479 String formatRomanDigit(int level, int digit) { 2480 String result = ""; 2481 if (digit == 9) { 2482 result = result + romanChars[level][0]; 2483 result = result + romanChars[level + 1][0]; 2484 return result; 2485 } else if (digit == 4) { 2486 result = result + romanChars[level][0]; 2487 result = result + romanChars[level][1]; 2488 return result; 2489 } else if (digit >= 5) { 2490 result = result + romanChars[level][1]; 2491 digit -= 5; 2492 } 2493 2494 for (int i = 0; i < digit; i++) { 2495 result = result + romanChars[level][0]; 2496 } 2497 2498 return result; 2499 } 2500 2501 private Rectangle paintRect; 2502 private boolean checkedForStart; 2503 private int start; 2504 private CSS.Value type; 2505 URL imageurl; 2506 private StyleSheet ss = null; 2507 Icon img = null; 2508 private int bulletgap = 5; 2509 private boolean isLeftToRight; 2510 } 2511 2512 2513 /** 2514 * Paints the background image. 2515 */ 2516 @SuppressWarnings("serial") // Same-version serialization only 2517 static class BackgroundImagePainter implements Serializable { 2518 ImageIcon backgroundImage; 2519 float hPosition; 2520 float vPosition; 2521 // bit mask: 0 for repeat x, 1 for repeat y, 2 for horiz relative, 2522 // 3 for vert relative 2523 short flags; 2524 // These are used when painting, updatePaintCoordinates updates them. 2525 private int paintX; 2526 private int paintY; 2527 private int paintMaxX; 2528 private int paintMaxY; 2529 2530 BackgroundImagePainter(AttributeSet a, CSS css, StyleSheet ss) { 2531 backgroundImage = ss.getBackgroundImage(a); 2532 // Determine the position. 2533 CSS.BackgroundPosition pos = (CSS.BackgroundPosition)a.getAttribute 2534 (CSS.Attribute.BACKGROUND_POSITION); 2535 if (pos != null) { 2536 hPosition = pos.getHorizontalPosition(); 2537 vPosition = pos.getVerticalPosition(); 2538 if (pos.isHorizontalPositionRelativeToSize()) { 2539 flags |= 4; 2540 } 2541 else if (pos.isHorizontalPositionRelativeToSize()) { 2542 hPosition *= CSS.getFontSize(a, 12, ss); 2543 } 2544 if (pos.isVerticalPositionRelativeToSize()) { 2545 flags |= 8; 2546 } 2547 else if (pos.isVerticalPositionRelativeToFontSize()) { 2548 vPosition *= CSS.getFontSize(a, 12, ss); 2549 } 2550 } 2551 // Determine any repeating values. 2552 CSS.Value repeats = (CSS.Value)a.getAttribute(CSS.Attribute. 2553 BACKGROUND_REPEAT); 2554 if (repeats == null || repeats == CSS.Value.BACKGROUND_REPEAT) { 2555 flags |= 3; 2556 } 2557 else if (repeats == CSS.Value.BACKGROUND_REPEAT_X) { 2558 flags |= 1; 2559 } 2560 else if (repeats == CSS.Value.BACKGROUND_REPEAT_Y) { 2561 flags |= 2; 2562 } 2563 } 2564 2565 @SuppressWarnings("deprecation") 2566 void paint(Graphics g, float x, float y, float w, float h, View v) { 2567 Rectangle clip = g.getClipRect(); 2568 if (clip != null) { 2569 // Constrain the clip so that images don't draw outside the 2570 // legal bounds. 2571 g.clipRect((int)x, (int)y, (int)w, (int)h); 2572 } 2573 if ((flags & 3) == 0) { 2574 // no repeating 2575 int width = backgroundImage.getIconWidth(); 2576 int height = backgroundImage.getIconWidth(); 2577 if ((flags & 4) == 4) { 2578 paintX = (int)(x + w * hPosition - 2579 (float)width * hPosition); 2580 } 2581 else { 2582 paintX = (int)x + (int)hPosition; 2583 } 2584 if ((flags & 8) == 8) { 2585 paintY = (int)(y + h * vPosition - 2586 (float)height * vPosition); 2587 } 2588 else { 2589 paintY = (int)y + (int)vPosition; 2590 } 2591 if (clip == null || 2592 !((paintX + width <= clip.x) || 2593 (paintY + height <= clip.y) || 2594 (paintX >= clip.x + clip.width) || 2595 (paintY >= clip.y + clip.height))) { 2596 backgroundImage.paintIcon(null, g, paintX, paintY); 2597 } 2598 } 2599 else { 2600 int width = backgroundImage.getIconWidth(); 2601 int height = backgroundImage.getIconHeight(); 2602 if (width > 0 && height > 0) { 2603 paintX = (int)x; 2604 paintY = (int)y; 2605 paintMaxX = (int)(x + w); 2606 paintMaxY = (int)(y + h); 2607 if (updatePaintCoordinates(clip, width, height)) { 2608 while (paintX < paintMaxX) { 2609 int ySpot = paintY; 2610 while (ySpot < paintMaxY) { 2611 backgroundImage.paintIcon(null, g, paintX, 2612 ySpot); 2613 ySpot += height; 2614 } 2615 paintX += width; 2616 } 2617 } 2618 } 2619 } 2620 if (clip != null) { 2621 // Reset clip. 2622 g.setClip(clip.x, clip.y, clip.width, clip.height); 2623 } 2624 } 2625 2626 private boolean updatePaintCoordinates 2627 (Rectangle clip, int width, int height){ 2628 if ((flags & 3) == 1) { 2629 paintMaxY = paintY + 1; 2630 } 2631 else if ((flags & 3) == 2) { 2632 paintMaxX = paintX + 1; 2633 } 2634 if (clip != null) { 2635 if ((flags & 3) == 1 && ((paintY + height <= clip.y) || 2636 (paintY > clip.y + clip.height))) { 2637 // not visible. 2638 return false; 2639 } 2640 if ((flags & 3) == 2 && ((paintX + width <= clip.x) || 2641 (paintX > clip.x + clip.width))) { 2642 // not visible. 2643 return false; 2644 } 2645 if ((flags & 1) == 1) { 2646 if ((clip.x + clip.width) < paintMaxX) { 2647 if ((clip.x + clip.width - paintX) % width == 0) { 2648 paintMaxX = clip.x + clip.width; 2649 } 2650 else { 2651 paintMaxX = ((clip.x + clip.width - paintX) / 2652 width + 1) * width + paintX; 2653 } 2654 } 2655 if (clip.x > paintX) { 2656 paintX = (clip.x - paintX) / width * width + paintX; 2657 } 2658 } 2659 if ((flags & 2) == 2) { 2660 if ((clip.y + clip.height) < paintMaxY) { 2661 if ((clip.y + clip.height - paintY) % height == 0) { 2662 paintMaxY = clip.y + clip.height; 2663 } 2664 else { 2665 paintMaxY = ((clip.y + clip.height - paintY) / 2666 height + 1) * height + paintY; 2667 } 2668 } 2669 if (clip.y > paintY) { 2670 paintY = (clip.y - paintY) / height * height + paintY; 2671 } 2672 } 2673 } 2674 // Valid 2675 return true; 2676 } 2677 } 2678 2679 2680 /** 2681 * A subclass of MuxingAttributeSet that translates between 2682 * CSS and HTML and StyleConstants. The AttributeSets used are 2683 * the CSS rules that match the Views Elements. 2684 */ 2685 @SuppressWarnings("serial") // Same-version serialization only 2686 class ViewAttributeSet extends MuxingAttributeSet { 2687 ViewAttributeSet(View v) { 2688 host = v; 2689 2690 // PENDING(prinz) fix this up to be a more realistic 2691 // implementation. 2692 Document doc = v.getDocument(); 2693 SearchBuffer sb = SearchBuffer.obtainSearchBuffer(); 2694 @SuppressWarnings("unchecked") 2695 Vector<AttributeSet> muxList = sb.getVector(); 2696 try { 2697 if (doc instanceof HTMLDocument) { 2698 StyleSheet styles = StyleSheet.this; 2699 Element elem = v.getElement(); 2700 AttributeSet a = elem.getAttributes(); 2701 AttributeSet htmlAttr = styles.translateHTMLToCSS(a); 2702 2703 if (htmlAttr.getAttributeCount() != 0) { 2704 muxList.addElement(htmlAttr); 2705 } 2706 if (elem.isLeaf()) { 2707 Enumeration<?> keys = a.getAttributeNames(); 2708 while (keys.hasMoreElements()) { 2709 Object key = keys.nextElement(); 2710 if (key instanceof HTML.Tag) { 2711 if (key == HTML.Tag.A) { 2712 Object o = a.getAttribute(key); 2713 /** 2714 In the case of an A tag, the css rules 2715 apply only for tags that have their 2716 href attribute defined and not for 2717 anchors that only have their name attributes 2718 defined, i.e anchors that function as 2719 destinations. Hence we do not add the 2720 attributes for that latter kind of 2721 anchors. When CSS2 support is added, 2722 it will be possible to specificity this 2723 kind of conditional behaviour in the 2724 stylesheet. 2725 **/ 2726 if (o != null && o instanceof AttributeSet) { 2727 AttributeSet attr = (AttributeSet)o; 2728 if (attr.getAttribute(HTML.Attribute.HREF) == null) { 2729 continue; 2730 } 2731 } 2732 } 2733 AttributeSet cssRule = styles.getRule((HTML.Tag) key, elem); 2734 if (cssRule != null) { 2735 muxList.addElement(cssRule); 2736 } 2737 } 2738 } 2739 } else { 2740 HTML.Tag t = (HTML.Tag) a.getAttribute 2741 (StyleConstants.NameAttribute); 2742 AttributeSet cssRule = styles.getRule(t, elem); 2743 if (cssRule != null) { 2744 muxList.addElement(cssRule); 2745 } 2746 } 2747 } 2748 AttributeSet[] attrs = new AttributeSet[muxList.size()]; 2749 muxList.copyInto(attrs); 2750 setAttributes(attrs); 2751 } 2752 finally { 2753 SearchBuffer.releaseSearchBuffer(sb); 2754 } 2755 } 2756 2757 // --- AttributeSet methods ---------------------------- 2758 2759 /** 2760 * Checks whether a given attribute is defined. 2761 * This will convert the key over to CSS if the 2762 * key is a StyleConstants key that has a CSS 2763 * mapping. 2764 * 2765 * @param key the attribute key 2766 * @return true if the attribute is defined 2767 * @see AttributeSet#isDefined 2768 */ 2769 public boolean isDefined(Object key) { 2770 if (key instanceof StyleConstants) { 2771 Object cssKey = css.styleConstantsKeyToCSSKey 2772 ((StyleConstants)key); 2773 if (cssKey != null) { 2774 key = cssKey; 2775 } 2776 } 2777 return super.isDefined(key); 2778 } 2779 2780 /** 2781 * Gets the value of an attribute. If the requested 2782 * attribute is a StyleConstants attribute that has 2783 * a CSS mapping, the request will be converted. 2784 * 2785 * @param key the attribute name 2786 * @return the attribute value 2787 * @see AttributeSet#getAttribute 2788 */ 2789 public Object getAttribute(Object key) { 2790 if (key instanceof StyleConstants) { 2791 Object cssKey = css.styleConstantsKeyToCSSKey 2792 ((StyleConstants)key); 2793 if (cssKey != null) { 2794 Object value = doGetAttribute(cssKey); 2795 if (value instanceof CSS.CssValue) { 2796 return ((CSS.CssValue)value).toStyleConstants 2797 ((StyleConstants)key, host); 2798 } 2799 } 2800 } 2801 return doGetAttribute(key); 2802 } 2803 2804 Object doGetAttribute(Object key) { 2805 Object retValue = super.getAttribute(key); 2806 if (retValue != null) { 2807 return retValue; 2808 } 2809 // didn't find it... try parent if it's a css attribute 2810 // that is inherited. 2811 if (key instanceof CSS.Attribute) { 2812 CSS.Attribute css = (CSS.Attribute) key; 2813 if (css.isInherited()) { 2814 AttributeSet parent = getResolveParent(); 2815 if (parent != null) 2816 return parent.getAttribute(key); 2817 } 2818 } 2819 return null; 2820 } 2821 2822 /** 2823 * If not overriden, the resolving parent defaults to 2824 * the parent element. 2825 * 2826 * @return the attributes from the parent 2827 * @see AttributeSet#getResolveParent 2828 */ 2829 public AttributeSet getResolveParent() { 2830 if (host == null) { 2831 return null; 2832 } 2833 View parent = host.getParent(); 2834 return (parent != null) ? parent.getAttributes() : null; 2835 } 2836 2837 /** View created for. */ 2838 View host; 2839 } 2840 2841 2842 /** 2843 * A subclass of MuxingAttributeSet that implements Style. Currently 2844 * the MutableAttributeSet methods are unimplemented, that is they 2845 * do nothing. 2846 */ 2847 // PENDING(sky): Decide what to do with this. Either make it 2848 // contain a SimpleAttributeSet that modify methods are delegated to, 2849 // or change getRule to return an AttributeSet and then don't make this 2850 // implement Style. 2851 @SuppressWarnings("serial") // Same-version serialization only 2852 static class ResolvedStyle extends MuxingAttributeSet implements 2853 Serializable, Style { 2854 ResolvedStyle(String name, AttributeSet[] attrs, int extendedIndex) { 2855 super(attrs); 2856 this.name = name; 2857 this.extendedIndex = extendedIndex; 2858 } 2859 2860 /** 2861 * Inserts a Style into the receiver so that the styles the 2862 * receiver represents are still ordered by specificity. 2863 * <code>style</code> will be added before any extended styles, that 2864 * is before extendedIndex. 2865 */ 2866 synchronized void insertStyle(Style style, int specificity) { 2867 AttributeSet[] attrs = getAttributes(); 2868 int maxCounter = attrs.length; 2869 int counter = 0; 2870 for (;counter < extendedIndex; counter++) { 2871 if (specificity > getSpecificity(((Style)attrs[counter]). 2872 getName())) { 2873 break; 2874 } 2875 } 2876 insertAttributeSetAt(style, counter); 2877 extendedIndex++; 2878 } 2879 2880 /** 2881 * Removes a previously added style. This will do nothing if 2882 * <code>style</code> is not referenced by the receiver. 2883 */ 2884 synchronized void removeStyle(Style style) { 2885 AttributeSet[] attrs = getAttributes(); 2886 2887 for (int counter = attrs.length - 1; counter >= 0; counter--) { 2888 if (attrs[counter] == style) { 2889 removeAttributeSetAt(counter); 2890 if (counter < extendedIndex) { 2891 extendedIndex--; 2892 } 2893 break; 2894 } 2895 } 2896 } 2897 2898 /** 2899 * Adds <code>s</code> as one of the Attributesets to look up 2900 * attributes in. 2901 */ 2902 synchronized void insertExtendedStyleAt(Style attr, int index) { 2903 insertAttributeSetAt(attr, extendedIndex + index); 2904 } 2905 2906 /** 2907 * Adds <code>s</code> as one of the AttributeSets to look up 2908 * attributes in. It will be the AttributeSet last checked. 2909 */ 2910 synchronized void addExtendedStyle(Style attr) { 2911 insertAttributeSetAt(attr, getAttributes().length); 2912 } 2913 2914 /** 2915 * Removes the style at <code>index</code> + 2916 * <code>extendedIndex</code>. 2917 */ 2918 synchronized void removeExtendedStyleAt(int index) { 2919 removeAttributeSetAt(extendedIndex + index); 2920 } 2921 2922 /** 2923 * Returns true if the receiver matches <code>selector</code>, where 2924 * a match is defined by the CSS rule matching. 2925 * Each simple selector must be separated by a single space. 2926 */ 2927 protected boolean matches(String selector) { 2928 int sLast = selector.length(); 2929 2930 if (sLast == 0) { 2931 return false; 2932 } 2933 int thisLast = name.length(); 2934 int sCurrent = selector.lastIndexOf(' '); 2935 int thisCurrent = name.lastIndexOf(' '); 2936 if (sCurrent >= 0) { 2937 sCurrent++; 2938 } 2939 if (thisCurrent >= 0) { 2940 thisCurrent++; 2941 } 2942 if (!matches(selector, sCurrent, sLast, thisCurrent, thisLast)) { 2943 return false; 2944 } 2945 while (sCurrent != -1) { 2946 sLast = sCurrent - 1; 2947 sCurrent = selector.lastIndexOf(' ', sLast - 1); 2948 if (sCurrent >= 0) { 2949 sCurrent++; 2950 } 2951 boolean match = false; 2952 while (!match && thisCurrent != -1) { 2953 thisLast = thisCurrent - 1; 2954 thisCurrent = name.lastIndexOf(' ', thisLast - 1); 2955 if (thisCurrent >= 0) { 2956 thisCurrent++; 2957 } 2958 match = matches(selector, sCurrent, sLast, thisCurrent, 2959 thisLast); 2960 } 2961 if (!match) { 2962 return false; 2963 } 2964 } 2965 return true; 2966 } 2967 2968 /** 2969 * Returns true if the substring of the receiver, in the range 2970 * thisCurrent, thisLast matches the substring of selector in 2971 * the ranme sCurrent to sLast based on CSS selector matching. 2972 */ 2973 boolean matches(String selector, int sCurrent, int sLast, 2974 int thisCurrent, int thisLast) { 2975 sCurrent = Math.max(sCurrent, 0); 2976 thisCurrent = Math.max(thisCurrent, 0); 2977 int thisDotIndex = boundedIndexOf(name, '.', thisCurrent, 2978 thisLast); 2979 int thisPoundIndex = boundedIndexOf(name, '#', thisCurrent, 2980 thisLast); 2981 int sDotIndex = boundedIndexOf(selector, '.', sCurrent, sLast); 2982 int sPoundIndex = boundedIndexOf(selector, '#', sCurrent, sLast); 2983 if (sDotIndex != -1) { 2984 // Selector has a '.', which indicates name must match it, 2985 // or if the '.' starts the selector than name must have 2986 // the same class (doesn't matter what element name). 2987 if (thisDotIndex == -1) { 2988 return false; 2989 } 2990 if (sCurrent == sDotIndex) { 2991 if ((thisLast - thisDotIndex) != (sLast - sDotIndex) || 2992 !selector.regionMatches(sCurrent, name, thisDotIndex, 2993 (thisLast - thisDotIndex))) { 2994 return false; 2995 } 2996 } 2997 else { 2998 // Has to fully match. 2999 if ((sLast - sCurrent) != (thisLast - thisCurrent) || 3000 !selector.regionMatches(sCurrent, name, thisCurrent, 3001 (thisLast - thisCurrent))) { 3002 return false; 3003 } 3004 } 3005 return true; 3006 } 3007 if (sPoundIndex != -1) { 3008 // Selector has a '#', which indicates name must match it, 3009 // or if the '#' starts the selector than name must have 3010 // the same id (doesn't matter what element name). 3011 if (thisPoundIndex == -1) { 3012 return false; 3013 } 3014 if (sCurrent == sPoundIndex) { 3015 if ((thisLast - thisPoundIndex) !=(sLast - sPoundIndex) || 3016 !selector.regionMatches(sCurrent, name, thisPoundIndex, 3017 (thisLast - thisPoundIndex))) { 3018 return false; 3019 } 3020 } 3021 else { 3022 // Has to fully match. 3023 if ((sLast - sCurrent) != (thisLast - thisCurrent) || 3024 !selector.regionMatches(sCurrent, name, thisCurrent, 3025 (thisLast - thisCurrent))) { 3026 return false; 3027 } 3028 } 3029 return true; 3030 } 3031 if (thisDotIndex != -1) { 3032 // Receiver references a class, just check element name. 3033 return (((thisDotIndex - thisCurrent) == (sLast - sCurrent)) && 3034 selector.regionMatches(sCurrent, name, thisCurrent, 3035 thisDotIndex - thisCurrent)); 3036 } 3037 if (thisPoundIndex != -1) { 3038 // Receiver references an id, just check element name. 3039 return (((thisPoundIndex - thisCurrent) ==(sLast - sCurrent))&& 3040 selector.regionMatches(sCurrent, name, thisCurrent, 3041 thisPoundIndex - thisCurrent)); 3042 } 3043 // Fail through, no classes or ides, just check string. 3044 return (((thisLast - thisCurrent) == (sLast - sCurrent)) && 3045 selector.regionMatches(sCurrent, name, thisCurrent, 3046 thisLast - thisCurrent)); 3047 } 3048 3049 /** 3050 * Similar to String.indexOf, but allows an upper bound 3051 * (this is slower in that it will still check string starting at 3052 * start. 3053 */ 3054 int boundedIndexOf(String string, char search, int start, 3055 int end) { 3056 int retValue = string.indexOf(search, start); 3057 if (retValue >= end) { 3058 return -1; 3059 } 3060 return retValue; 3061 } 3062 3063 public void addAttribute(Object name, Object value) {} 3064 public void addAttributes(AttributeSet attributes) {} 3065 public void removeAttribute(Object name) {} 3066 public void removeAttributes(Enumeration<?> names) {} 3067 public void removeAttributes(AttributeSet attributes) {} 3068 public void setResolveParent(AttributeSet parent) {} 3069 public String getName() {return name;} 3070 public void addChangeListener(ChangeListener l) {} 3071 public void removeChangeListener(ChangeListener l) {} 3072 public ChangeListener[] getChangeListeners() { 3073 return new ChangeListener[0]; 3074 } 3075 3076 /** The name of the Style, which is the selector. 3077 * This will NEVER change! 3078 */ 3079 String name; 3080 /** Start index of styles coming from other StyleSheets. */ 3081 private int extendedIndex; 3082 } 3083 3084 3085 /** 3086 * SelectorMapping contains a specifitiy, as an integer, and an associated 3087 * Style. It can also reference children <code>SelectorMapping</code>s, 3088 * so that it behaves like a tree. 3089 * <p> 3090 * This is not thread safe, it is assumed the caller will take the 3091 * necessary precations if this is to be used in a threaded environment. 3092 */ 3093 @SuppressWarnings("serial") // Same-version serialization only 3094 static class SelectorMapping implements Serializable { 3095 public SelectorMapping(int specificity) { 3096 this.specificity = specificity; 3097 } 3098 3099 /** 3100 * Returns the specificity this mapping represents. 3101 */ 3102 public int getSpecificity() { 3103 return specificity; 3104 } 3105 3106 /** 3107 * Sets the Style associated with this mapping. 3108 */ 3109 public void setStyle(Style style) { 3110 this.style = style; 3111 } 3112 3113 /** 3114 * Returns the Style associated with this mapping. 3115 */ 3116 public Style getStyle() { 3117 return style; 3118 } 3119 3120 /** 3121 * Returns the child mapping identified by the simple selector 3122 * <code>selector</code>. If a child mapping does not exist for 3123 *<code>selector</code>, and <code>create</code> is true, a new 3124 * one will be created. 3125 */ 3126 public SelectorMapping getChildSelectorMapping(String selector, 3127 boolean create) { 3128 SelectorMapping retValue = null; 3129 3130 if (children != null) { 3131 retValue = children.get(selector); 3132 } 3133 else if (create) { 3134 children = new HashMap<String, SelectorMapping>(7); 3135 } 3136 if (retValue == null && create) { 3137 int specificity = getChildSpecificity(selector); 3138 3139 retValue = createChildSelectorMapping(specificity); 3140 children.put(selector, retValue); 3141 } 3142 return retValue; 3143 } 3144 3145 /** 3146 * Creates a child <code>SelectorMapping</code> with the specified 3147 * <code>specificity</code>. 3148 */ 3149 protected SelectorMapping createChildSelectorMapping(int specificity) { 3150 return new SelectorMapping(specificity); 3151 } 3152 3153 /** 3154 * Returns the specificity for the child selector 3155 * <code>selector</code>. 3156 */ 3157 protected int getChildSpecificity(String selector) { 3158 // class (.) 100 3159 // id (#) 10000 3160 char firstChar = selector.charAt(0); 3161 int specificity = getSpecificity(); 3162 3163 if (firstChar == '.') { 3164 specificity += 100; 3165 } 3166 else if (firstChar == '#') { 3167 specificity += 10000; 3168 } 3169 else { 3170 specificity += 1; 3171 if (selector.indexOf('.') != -1) { 3172 specificity += 100; 3173 } 3174 if (selector.indexOf('#') != -1) { 3175 specificity += 10000; 3176 } 3177 } 3178 return specificity; 3179 } 3180 3181 /** 3182 * The specificity for this selector. 3183 */ 3184 private int specificity; 3185 /** 3186 * Style for this selector. 3187 */ 3188 private Style style; 3189 /** 3190 * Any sub selectors. Key will be String, and value will be 3191 * another SelectorMapping. 3192 */ 3193 private HashMap<String, SelectorMapping> children; 3194 } 3195 3196 3197 // ---- Variables --------------------------------------------- 3198 3199 final static int DEFAULT_FONT_SIZE = 3; 3200 3201 private CSS css; 3202 3203 /** 3204 * An inverted graph of the selectors. 3205 */ 3206 private SelectorMapping selectorMapping; 3207 3208 /** Maps from selector (as a string) to Style that includes all 3209 * relevant styles. */ 3210 private Hashtable<String, ResolvedStyle> resolvedStyles; 3211 3212 /** Vector of StyleSheets that the rules are to reference. 3213 */ 3214 private Vector<StyleSheet> linkedStyleSheets; 3215 3216 /** Where the style sheet was found. Used for relative imports. */ 3217 private URL base; 3218 3219 3220 /** 3221 * Default parser for CSS specifications that get loaded into 3222 * the StyleSheet.<p> 3223 * This class is NOT thread safe, do not ask it to parse while it is 3224 * in the middle of parsing. 3225 */ 3226 class CssParser implements CSSParser.CSSParserCallback { 3227 3228 /** 3229 * Parses the passed in CSS declaration into an AttributeSet. 3230 */ 3231 public AttributeSet parseDeclaration(String string) { 3232 try { 3233 return parseDeclaration(new StringReader(string)); 3234 } catch (IOException ioe) {} 3235 return null; 3236 } 3237 3238 /** 3239 * Parses the passed in CSS declaration into an AttributeSet. 3240 */ 3241 public AttributeSet parseDeclaration(Reader r) throws IOException { 3242 parse(base, r, true, false); 3243 return declaration.copyAttributes(); 3244 } 3245 3246 /** 3247 * Parse the given CSS stream 3248 */ 3249 public void parse(URL base, Reader r, boolean parseDeclaration, 3250 boolean isLink) throws IOException { 3251 this.base = base; 3252 this.isLink = isLink; 3253 this.parsingDeclaration = parseDeclaration; 3254 declaration.removeAttributes(declaration); 3255 selectorTokens.removeAllElements(); 3256 selectors.removeAllElements(); 3257 propertyName = null; 3258 parser.parse(r, this, parseDeclaration); 3259 } 3260 3261 // 3262 // CSSParserCallback methods, public to implement the interface. 3263 // 3264 3265 /** 3266 * Invoked when a valid @import is encountered, will call 3267 * <code>importStyleSheet</code> if a 3268 * <code>MalformedURLException</code> is not thrown in creating 3269 * the URL. 3270 */ 3271 public void handleImport(String importString) { 3272 URL url = CSS.getURL(base, importString); 3273 if (url != null) { 3274 importStyleSheet(url); 3275 } 3276 } 3277 3278 /** 3279 * A selector has been encountered. 3280 */ 3281 public void handleSelector(String selector) { 3282 //class and index selectors are case sensitive 3283 if (!(selector.startsWith(".") 3284 || selector.startsWith("#"))) { 3285 selector = selector.toLowerCase(); 3286 } 3287 int length = selector.length(); 3288 3289 if (selector.endsWith(",")) { 3290 if (length > 1) { 3291 selector = selector.substring(0, length - 1); 3292 selectorTokens.addElement(selector); 3293 } 3294 addSelector(); 3295 } 3296 else if (length > 0) { 3297 selectorTokens.addElement(selector); 3298 } 3299 } 3300 3301 /** 3302 * Invoked when the start of a rule is encountered. 3303 */ 3304 public void startRule() { 3305 if (selectorTokens.size() > 0) { 3306 addSelector(); 3307 } 3308 propertyName = null; 3309 } 3310 3311 /** 3312 * Invoked when a property name is encountered. 3313 */ 3314 public void handleProperty(String property) { 3315 propertyName = property; 3316 } 3317 3318 /** 3319 * Invoked when a property value is encountered. 3320 */ 3321 public void handleValue(String value) { 3322 if (propertyName != null && value != null && value.length() > 0) { 3323 CSS.Attribute cssKey = CSS.getAttribute(propertyName); 3324 if (cssKey != null) { 3325 // There is currently no mechanism to determine real 3326 // base that style sheet was loaded from. For the time 3327 // being, this maps for LIST_STYLE_IMAGE, which appear 3328 // to be the only one that currently matters. A more 3329 // general mechanism is definately needed. 3330 if (cssKey == CSS.Attribute.LIST_STYLE_IMAGE) { 3331 if (value != null && !value.equals("none")) { 3332 URL url = CSS.getURL(base, value); 3333 3334 if (url != null) { 3335 value = url.toString(); 3336 } 3337 } 3338 } 3339 addCSSAttribute(declaration, cssKey, value); 3340 } 3341 propertyName = null; 3342 } 3343 } 3344 3345 /** 3346 * Invoked when the end of a rule is encountered. 3347 */ 3348 public void endRule() { 3349 int n = selectors.size(); 3350 for (int i = 0; i < n; i++) { 3351 String[] selector = selectors.elementAt(i); 3352 if (selector.length > 0) { 3353 StyleSheet.this.addRule(selector, declaration, isLink); 3354 } 3355 } 3356 declaration.removeAttributes(declaration); 3357 selectors.removeAllElements(); 3358 } 3359 3360 private void addSelector() { 3361 String[] selector = new String[selectorTokens.size()]; 3362 selectorTokens.copyInto(selector); 3363 selectors.addElement(selector); 3364 selectorTokens.removeAllElements(); 3365 } 3366 3367 3368 Vector<String[]> selectors = new Vector<String[]>(); 3369 Vector<String> selectorTokens = new Vector<String>(); 3370 /** Name of the current property. */ 3371 String propertyName; 3372 MutableAttributeSet declaration = new SimpleAttributeSet(); 3373 /** True if parsing a declaration, that is the Reader will not 3374 * contain a selector. */ 3375 boolean parsingDeclaration; 3376 /** True if the attributes are coming from a linked/imported style. */ 3377 boolean isLink; 3378 /** Where the CSS stylesheet lives. */ 3379 URL base; 3380 CSSParser parser = new CSSParser(); 3381 } 3382 3383 void rebaseSizeMap(int base) { 3384 final int minimalFontSize = 4; 3385 sizeMap = new int[sizeMapDefault.length]; 3386 for (int i = 0; i < sizeMapDefault.length; i++) { 3387 sizeMap[i] = Math.max(base * sizeMapDefault[i] / 3388 sizeMapDefault[CSS.baseFontSizeIndex], 3389 minimalFontSize); 3390 } 3391 3392 } 3393 3394 int[] getSizeMap() { 3395 return sizeMap; 3396 } 3397 boolean isW3CLengthUnits() { 3398 return w3cLengthUnits; 3399 } 3400 3401 /** 3402 * The HTML/CSS size model has seven slots 3403 * that one can assign sizes to. 3404 */ 3405 static final int sizeMapDefault[] = { 8, 10, 12, 14, 18, 24, 36 }; 3406 3407 private int sizeMap[] = sizeMapDefault; 3408 private boolean w3cLengthUnits = false; 3409 }