001 /* StyleSheet.java -- 002 Copyright (C) 2005 Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package javax.swing.text.html; 040 041 import gnu.javax.swing.text.html.css.BorderWidth; 042 import gnu.javax.swing.text.html.css.CSSColor; 043 import gnu.javax.swing.text.html.css.CSSParser; 044 import gnu.javax.swing.text.html.css.CSSParserCallback; 045 import gnu.javax.swing.text.html.css.FontSize; 046 import gnu.javax.swing.text.html.css.FontStyle; 047 import gnu.javax.swing.text.html.css.FontWeight; 048 import gnu.javax.swing.text.html.css.Length; 049 import gnu.javax.swing.text.html.css.Selector; 050 051 import java.awt.Color; 052 import java.awt.Font; 053 import java.awt.Graphics; 054 import java.awt.Rectangle; 055 import java.awt.Shape; 056 import java.awt.font.FontRenderContext; 057 import java.awt.geom.Rectangle2D; 058 import java.io.BufferedReader; 059 import java.io.IOException; 060 import java.io.InputStream; 061 import java.io.InputStreamReader; 062 import java.io.Reader; 063 import java.io.Serializable; 064 import java.io.StringReader; 065 import java.net.URL; 066 import java.util.ArrayList; 067 import java.util.Collections; 068 import java.util.Enumeration; 069 import java.util.HashMap; 070 import java.util.Iterator; 071 import java.util.List; 072 import java.util.Map; 073 074 import javax.swing.border.Border; 075 import javax.swing.event.ChangeListener; 076 import javax.swing.text.AttributeSet; 077 import javax.swing.text.Element; 078 import javax.swing.text.MutableAttributeSet; 079 import javax.swing.text.SimpleAttributeSet; 080 import javax.swing.text.Style; 081 import javax.swing.text.StyleConstants; 082 import javax.swing.text.StyleContext; 083 import javax.swing.text.View; 084 085 086 /** 087 * This class adds support for defining the visual characteristics of HTML views 088 * being rendered. This enables views to be customized by a look-and-feel, mulitple 089 * views over the same model can be rendered differently. Each EditorPane has its 090 * own StyleSheet, but by default one sheet will be shared by all of the HTMLEditorKit 091 * instances. An HTMLDocument can also have a StyleSheet, which holds specific CSS 092 * specs. 093 * 094 * In order for Views to store less state and therefore be more lightweight, 095 * the StyleSheet can act as a factory for painters that handle some of the 096 * rendering tasks. Since the StyleSheet may be used by views over multiple 097 * documents the HTML attributes don't effect the selector being used. 098 * 099 * The rules are stored as named styles, and other information is stored to 100 * translate the context of an element to a rule. 101 * 102 * @author Lillian Angel (langel@redhat.com) 103 */ 104 public class StyleSheet extends StyleContext 105 { 106 107 /** 108 * Parses CSS stylesheets using the parser in gnu/javax/swing/html/css. 109 * 110 * This is package private to avoid accessor methods. 111 */ 112 class CSSStyleSheetParserCallback 113 implements CSSParserCallback 114 { 115 /** 116 * The current styles. 117 */ 118 private CSSStyle[] styles; 119 120 /** 121 * The precedence of the stylesheet to be parsed. 122 */ 123 private int precedence; 124 125 /** 126 * Creates a new CSS parser. This parser parses a CSS stylesheet with 127 * the specified precedence. 128 * 129 * @param prec the precedence, according to the constants defined in 130 * CSSStyle 131 */ 132 CSSStyleSheetParserCallback(int prec) 133 { 134 precedence = prec; 135 } 136 137 /** 138 * Called at the beginning of a statement. 139 * 140 * @param sel the selector 141 */ 142 public void startStatement(Selector[] sel) 143 { 144 styles = new CSSStyle[sel.length]; 145 for (int i = 0; i < sel.length; i++) 146 styles[i] = new CSSStyle(precedence, sel[i]); 147 } 148 149 /** 150 * Called at the end of a statement. 151 */ 152 public void endStatement() 153 { 154 for (int i = 0; i < styles.length; i++) 155 css.add(styles[i]); 156 styles = null; 157 } 158 159 /** 160 * Called when a declaration is parsed. 161 * 162 * @param property the property 163 * @param value the value 164 */ 165 public void declaration(String property, String value) 166 { 167 CSS.Attribute cssAtt = CSS.getAttribute(property); 168 Object val = CSS.getValue(cssAtt, value); 169 for (int i = 0; i < styles.length; i++) 170 { 171 CSSStyle style = styles[i]; 172 CSS.addInternal(style, cssAtt, value); 173 if (cssAtt != null) 174 style.addAttribute(cssAtt, val); 175 } 176 } 177 178 } 179 180 /** 181 * Represents a style that is defined by a CSS rule. 182 */ 183 private class CSSStyle 184 extends SimpleAttributeSet 185 implements Style, Comparable 186 { 187 188 static final int PREC_UA = 0; 189 static final int PREC_NORM = 100000; 190 static final int PREC_AUTHOR_NORMAL = 200000; 191 static final int PREC_AUTHOR_IMPORTANT = 300000; 192 static final int PREC_USER_IMPORTANT = 400000; 193 194 /** 195 * The priority of this style when matching CSS selectors. 196 */ 197 private int precedence; 198 199 /** 200 * The selector for this rule. 201 * 202 * This is package private to avoid accessor methods. 203 */ 204 Selector selector; 205 206 CSSStyle(int prec, Selector sel) 207 { 208 precedence = prec; 209 selector = sel; 210 } 211 212 public String getName() 213 { 214 // TODO: Implement this for correctness. 215 return null; 216 } 217 218 public void addChangeListener(ChangeListener listener) 219 { 220 // TODO: Implement this for correctness. 221 } 222 223 public void removeChangeListener(ChangeListener listener) 224 { 225 // TODO: Implement this for correctness. 226 } 227 228 /** 229 * Sorts the rule according to the style's precedence and the 230 * selectors specificity. 231 */ 232 public int compareTo(Object o) 233 { 234 CSSStyle other = (CSSStyle) o; 235 return other.precedence + other.selector.getSpecificity() 236 - precedence - selector.getSpecificity(); 237 } 238 239 } 240 241 /** The base URL */ 242 URL base; 243 244 /** Base font size (int) */ 245 int baseFontSize; 246 247 /** 248 * The linked style sheets stored. 249 */ 250 private ArrayList linked; 251 252 /** 253 * Maps element names (selectors) to AttributSet (the corresponding style 254 * information). 255 */ 256 ArrayList css = new ArrayList(); 257 258 /** 259 * Maps selectors to their resolved styles. 260 */ 261 private HashMap resolvedStyles; 262 263 /** 264 * Constructs a StyleSheet. 265 */ 266 public StyleSheet() 267 { 268 super(); 269 baseFontSize = 4; // Default font size from CSS 270 resolvedStyles = new HashMap(); 271 } 272 273 /** 274 * Gets the style used to render the given tag. The element represents the tag 275 * and can be used to determine the nesting, where the attributes will differ 276 * if there is nesting inside of elements. 277 * 278 * @param t - the tag to translate to visual attributes 279 * @param e - the element representing the tag 280 * @return the set of CSS attributes to use to render the tag. 281 */ 282 public Style getRule(HTML.Tag t, Element e) 283 { 284 // Create list of the element and all of its parents, starting 285 // with the bottommost element. 286 ArrayList path = new ArrayList(); 287 Element el; 288 AttributeSet atts; 289 for (el = e; el != null; el = el.getParentElement()) 290 path.add(el); 291 292 // Create fully qualified selector. 293 StringBuilder selector = new StringBuilder(); 294 int count = path.size(); 295 // We append the actual element after this loop. 296 for (int i = count - 1; i > 0; i--) 297 { 298 el = (Element) path.get(i); 299 atts = el.getAttributes(); 300 Object name = atts.getAttribute(StyleConstants.NameAttribute); 301 selector.append(name.toString()); 302 if (atts.isDefined(HTML.Attribute.ID)) 303 { 304 selector.append('#'); 305 selector.append(atts.getAttribute(HTML.Attribute.ID)); 306 } 307 if (atts.isDefined(HTML.Attribute.CLASS)) 308 { 309 selector.append('.'); 310 selector.append(atts.getAttribute(HTML.Attribute.CLASS)); 311 } 312 if (atts.isDefined(HTML.Attribute.DYNAMIC_CLASS)) 313 { 314 selector.append(':'); 315 selector.append(atts.getAttribute(HTML.Attribute.DYNAMIC_CLASS)); 316 } 317 if (atts.isDefined(HTML.Attribute.PSEUDO_CLASS)) 318 { 319 selector.append(':'); 320 selector.append(atts.getAttribute(HTML.Attribute.PSEUDO_CLASS)); 321 } 322 selector.append(' '); 323 } 324 selector.append(t.toString()); 325 el = (Element) path.get(0); 326 atts = el.getAttributes(); 327 // For leaf elements, we have to fetch the tag specific attributes. 328 if (el.isLeaf()) 329 { 330 Object o = atts.getAttribute(t); 331 if (o instanceof AttributeSet) 332 atts = (AttributeSet) o; 333 else 334 atts = null; 335 } 336 if (atts != null) 337 { 338 if (atts.isDefined(HTML.Attribute.ID)) 339 { 340 selector.append('#'); 341 selector.append(atts.getAttribute(HTML.Attribute.ID)); 342 } 343 if (atts.isDefined(HTML.Attribute.CLASS)) 344 { 345 selector.append('.'); 346 selector.append(atts.getAttribute(HTML.Attribute.CLASS)); 347 } 348 if (atts.isDefined(HTML.Attribute.DYNAMIC_CLASS)) 349 { 350 selector.append(':'); 351 selector.append(atts.getAttribute(HTML.Attribute.DYNAMIC_CLASS)); 352 } 353 if (atts.isDefined(HTML.Attribute.PSEUDO_CLASS)) 354 { 355 selector.append(':'); 356 selector.append(atts.getAttribute(HTML.Attribute.PSEUDO_CLASS)); 357 } 358 } 359 return getResolvedStyle(selector.toString(), path, t); 360 } 361 362 /** 363 * Fetches a resolved style. If there is no resolved style for the 364 * specified selector, the resolve the style using 365 * {@link #resolveStyle(String, List, HTML.Tag)}. 366 * 367 * @param selector the selector for which to resolve the style 368 * @param path the Element path, used in the resolving algorithm 369 * @param tag the tag for which to resolve 370 * 371 * @return the resolved style 372 */ 373 private Style getResolvedStyle(String selector, List path, HTML.Tag tag) 374 { 375 Style style = (Style) resolvedStyles.get(selector); 376 if (style == null) 377 style = resolveStyle(selector, path, tag); 378 return style; 379 } 380 381 /** 382 * Resolves a style. This creates arrays that hold the tag names, 383 * class and id attributes and delegates the work to 384 * {@link #resolveStyle(String, String[], Map[])}. 385 * 386 * @param selector the selector 387 * @param path the Element path 388 * @param tag the tag 389 * 390 * @return the resolved style 391 */ 392 private Style resolveStyle(String selector, List path, HTML.Tag tag) 393 { 394 int count = path.size(); 395 String[] tags = new String[count]; 396 Map[] attributes = new Map[count]; 397 for (int i = 0; i < count; i++) 398 { 399 Element el = (Element) path.get(i); 400 AttributeSet atts = el.getAttributes(); 401 if (i == 0 && el.isLeaf()) 402 { 403 Object o = atts.getAttribute(tag); 404 if (o instanceof AttributeSet) 405 atts = (AttributeSet) o; 406 else 407 atts = null; 408 } 409 if (atts != null) 410 { 411 HTML.Tag t = 412 (HTML.Tag) atts.getAttribute(StyleConstants.NameAttribute); 413 if (t != null) 414 tags[i] = t.toString(); 415 else 416 tags[i] = null; 417 attributes[i] = attributeSetToMap(atts); 418 } 419 else 420 { 421 tags[i] = null; 422 attributes[i] = null; 423 } 424 } 425 tags[0] = tag.toString(); 426 return resolveStyle(selector, tags, attributes); 427 } 428 429 /** 430 * Performs style resolving. 431 * 432 * @param selector the selector 433 * @param tags the tags 434 * @param attributes the attributes of the tags 435 * 436 * @return the resolved style 437 */ 438 private Style resolveStyle(String selector, String[] tags, Map[] attributes) 439 { 440 // FIXME: This style resolver is not correct. But it works good enough for 441 // the default.css. 442 int count = tags.length; 443 ArrayList styles = new ArrayList(); 444 for (Iterator i = css.iterator(); i.hasNext();) 445 { 446 CSSStyle style = (CSSStyle) i.next(); 447 if (style.selector.matches(tags, attributes)) 448 styles.add(style); 449 } 450 451 // Add styles from linked stylesheets. 452 if (linked != null) 453 { 454 for (int i = linked.size() - 1; i >= 0; i--) 455 { 456 StyleSheet ss = (StyleSheet) linked.get(i); 457 for (int j = ss.css.size() - 1; j >= 0; j--) 458 { 459 CSSStyle style = (CSSStyle) ss.css.get(j); 460 if (style.selector.matches(tags, attributes)) 461 styles.add(style); 462 } 463 } 464 } 465 466 // Sort selectors. 467 Collections.sort(styles); 468 Style[] styleArray = new Style[styles.size()]; 469 styleArray = (Style[]) styles.toArray(styleArray); 470 Style resolved = new MultiStyle(selector, 471 (Style[]) styles.toArray(styleArray)); 472 resolvedStyles.put(selector, resolved); 473 return resolved; 474 } 475 476 /** 477 * Gets the rule that best matches the selector. selector is a space 478 * separated String of element names. The attributes of the returned 479 * Style will change as rules are added and removed. 480 * 481 * @param selector - the element names separated by spaces 482 * @return the set of CSS attributes to use to render 483 */ 484 public Style getRule(String selector) 485 { 486 CSSStyle best = null; 487 for (Iterator i = css.iterator(); i.hasNext();) 488 { 489 CSSStyle style = (CSSStyle) i.next(); 490 if (style.compareTo(best) < 0) 491 best = style; 492 } 493 return best; 494 } 495 496 /** 497 * Adds a set of rules to the sheet. The rules are expected to be in valid 498 * CSS format. This is called as a result of parsing a <style> tag 499 * 500 * @param rule - the rule to add to the sheet 501 */ 502 public void addRule(String rule) 503 { 504 CSSStyleSheetParserCallback cb = 505 new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL); 506 // FIXME: Handle ref. 507 StringReader in = new StringReader(rule); 508 CSSParser parser = new CSSParser(in, cb); 509 try 510 { 511 parser.parse(); 512 } 513 catch (IOException ex) 514 { 515 // Shouldn't happen. And if, then don't let it bork the outside code. 516 } 517 // Clean up resolved styles cache so that the new styles are recognized 518 // on next stylesheet request. 519 resolvedStyles.clear(); 520 } 521 522 /** 523 * Translates a CSS declaration into an AttributeSet. This is called 524 * as a result of encountering an HTML style attribute. 525 * 526 * @param decl - the declaration to get 527 * @return the AttributeSet representing the declaration 528 */ 529 public AttributeSet getDeclaration(String decl) 530 { 531 if (decl == null) 532 return SimpleAttributeSet.EMPTY; 533 // FIXME: Not implemented. 534 return null; 535 } 536 537 /** 538 * Loads a set of rules that have been specified in terms of CSS grammar. 539 * If there are any conflicts with existing rules, the new rule is added. 540 * 541 * @param in - the stream to read the CSS grammar from. 542 * @param ref - the reference URL. It is the location of the stream, it may 543 * be null. All relative URLs specified in the stream will be based upon this 544 * parameter. 545 * @throws IOException - For any IO error while reading 546 */ 547 public void loadRules(Reader in, URL ref) 548 throws IOException 549 { 550 CSSStyleSheetParserCallback cb = 551 new CSSStyleSheetParserCallback(CSSStyle.PREC_UA); 552 // FIXME: Handle ref. 553 CSSParser parser = new CSSParser(in, cb); 554 parser.parse(); 555 } 556 557 /** 558 * Gets a set of attributes to use in the view. This is a set of 559 * attributes that can be used for View.getAttributes 560 * 561 * @param v - the view to get the set for 562 * @return the AttributeSet to use in the view. 563 */ 564 public AttributeSet getViewAttributes(View v) 565 { 566 return new ViewAttributeSet(v, this); 567 } 568 569 /** 570 * Removes a style previously added. 571 * 572 * @param nm - the name of the style to remove 573 */ 574 public void removeStyle(String nm) 575 { 576 // FIXME: Not implemented. 577 super.removeStyle(nm); 578 } 579 580 /** 581 * Adds the rules from ss to those of the receiver. ss's rules will 582 * override the old rules. An added StyleSheet will never override the rules 583 * of the receiving style sheet. 584 * 585 * @param ss - the new StyleSheet. 586 */ 587 public void addStyleSheet(StyleSheet ss) 588 { 589 if (linked == null) 590 linked = new ArrayList(); 591 linked.add(ss); 592 } 593 594 /** 595 * Removes ss from those of the receiver 596 * 597 * @param ss - the StyleSheet to remove. 598 */ 599 public void removeStyleSheet(StyleSheet ss) 600 { 601 if (linked != null) 602 { 603 linked.remove(ss); 604 } 605 } 606 607 /** 608 * Returns an array of the linked StyleSheets. May return null. 609 * 610 * @return - An array of the linked StyleSheets. 611 */ 612 public StyleSheet[] getStyleSheets() 613 { 614 StyleSheet[] linkedSS; 615 if (linked != null) 616 { 617 linkedSS = new StyleSheet[linked.size()]; 618 linkedSS = (StyleSheet[]) linked.toArray(linkedSS); 619 } 620 else 621 { 622 linkedSS = null; 623 } 624 return linkedSS; 625 } 626 627 /** 628 * Imports a style sheet from the url. The rules are directly added to the 629 * receiver. This is usually called when a <link> tag is resolved in an 630 * HTML document. 631 * 632 * @param url the URL to import the StyleSheet from 633 */ 634 public void importStyleSheet(URL url) 635 { 636 try 637 { 638 InputStream in = url.openStream(); 639 Reader r = new BufferedReader(new InputStreamReader(in)); 640 CSSStyleSheetParserCallback cb = 641 new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL); 642 CSSParser parser = new CSSParser(r, cb); 643 parser.parse(); 644 } 645 catch (IOException ex) 646 { 647 // We can't do anything about it I guess. 648 } 649 } 650 651 /** 652 * Sets the base url. All import statements that are relative, will be 653 * relative to base. 654 * 655 * @param base - 656 * the base URL. 657 */ 658 public void setBase(URL base) 659 { 660 this.base = base; 661 } 662 663 /** 664 * Gets the base url. 665 * 666 * @return - the base 667 */ 668 public URL getBase() 669 { 670 return base; 671 } 672 673 /** 674 * Adds a CSS attribute to the given set. 675 * 676 * @param attr - the attribute set 677 * @param key - the attribute to add 678 * @param value - the value of the key 679 */ 680 public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key, 681 String value) 682 { 683 Object val = CSS.getValue(key, value); 684 CSS.addInternal(attr, key, value); 685 attr.addAttribute(key, val); 686 } 687 688 /** 689 * Adds a CSS attribute to the given set. 690 * This method parses the value argument from HTML based on key. 691 * Returns true if it finds a valid value for the given key, 692 * and false otherwise. 693 * 694 * @param attr - the attribute set 695 * @param key - the attribute to add 696 * @param value - the value of the key 697 * @return true if a valid value was found. 698 */ 699 public boolean addCSSAttributeFromHTML(MutableAttributeSet attr, CSS.Attribute key, 700 String value) 701 { 702 // FIXME: Need to parse value from HTML based on key. 703 attr.addAttribute(key, value); 704 return attr.containsAttribute(key, value); 705 } 706 707 /** 708 * Converts a set of HTML attributes to an equivalent set of CSS attributes. 709 * 710 * @param htmlAttrSet - the set containing the HTML attributes. 711 * @return the set of CSS attributes 712 */ 713 public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) 714 { 715 AttributeSet cssAttr = htmlAttrSet.copyAttributes(); 716 717 // The HTML align attribute maps directly to the CSS text-align attribute. 718 Object o = htmlAttrSet.getAttribute(HTML.Attribute.ALIGN); 719 if (o != null) 720 cssAttr = addAttribute(cssAttr, CSS.Attribute.TEXT_ALIGN, o); 721 722 // The HTML width attribute maps directly to CSS width. 723 o = htmlAttrSet.getAttribute(HTML.Attribute.WIDTH); 724 if (o != null) 725 cssAttr = addAttribute(cssAttr, CSS.Attribute.WIDTH, 726 new Length(o.toString())); 727 728 // The HTML height attribute maps directly to CSS height. 729 o = htmlAttrSet.getAttribute(HTML.Attribute.HEIGHT); 730 if (o != null) 731 cssAttr = addAttribute(cssAttr, CSS.Attribute.HEIGHT, 732 new Length(o.toString())); 733 734 o = htmlAttrSet.getAttribute(HTML.Attribute.NOWRAP); 735 if (o != null) 736 cssAttr = addAttribute(cssAttr, CSS.Attribute.WHITE_SPACE, "nowrap"); 737 738 // Map cellspacing attr of tables to CSS border-spacing. 739 o = htmlAttrSet.getAttribute(HTML.Attribute.CELLSPACING); 740 if (o != null) 741 cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_SPACING, 742 new Length(o.toString())); 743 744 // For table cells and headers, fetch the cellpadding value from the 745 // parent table and set it as CSS padding attribute. 746 HTML.Tag tag = (HTML.Tag) 747 htmlAttrSet.getAttribute(StyleConstants.NameAttribute); 748 if ((tag == HTML.Tag.TD || tag == HTML.Tag.TH) 749 && htmlAttrSet instanceof Element) 750 { 751 Element el = (Element) htmlAttrSet; 752 AttributeSet tableAttrs = el.getParentElement().getParentElement() 753 .getAttributes(); 754 o = tableAttrs.getAttribute(HTML.Attribute.CELLPADDING); 755 if (o != null) 756 { 757 Length l = new Length(o.toString()); 758 cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_BOTTOM, l); 759 cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_LEFT, l); 760 cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_RIGHT, l); 761 cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_TOP, l); 762 } 763 o = tableAttrs.getAttribute(HTML.Attribute.BORDER); 764 cssAttr = translateBorder(cssAttr, o); 765 } 766 767 // Translate border attribute. 768 o = cssAttr.getAttribute(HTML.Attribute.BORDER); 769 cssAttr = translateBorder(cssAttr, o); 770 771 // TODO: Add more mappings. 772 return cssAttr; 773 } 774 775 /** 776 * Translates a HTML border attribute to a corresponding set of CSS 777 * attributes. 778 * 779 * @param cssAttr the original set of CSS attributes to add to 780 * @param o the value of the border attribute 781 * 782 * @return the new set of CSS attributes 783 */ 784 private AttributeSet translateBorder(AttributeSet cssAttr, Object o) 785 { 786 if (o != null) 787 { 788 BorderWidth l = new BorderWidth(o.toString()); 789 if (l.getValue() > 0) 790 { 791 cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_WIDTH, l); 792 cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_STYLE, 793 "solid"); 794 cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_COLOR, 795 new CSSColor("black")); 796 } 797 } 798 return cssAttr; 799 } 800 801 /** 802 * Adds an attribute to the given set and returns a new set. This is implemented 803 * to convert StyleConstants attributes to CSS before forwarding them to the superclass. 804 * The StyleConstants attribute do not have corresponding CSS entry, the attribute 805 * is stored (but will likely not be used). 806 * 807 * @param old - the old set 808 * @param key - the non-null attribute key 809 * @param value - the attribute value 810 * @return the updated set 811 */ 812 public AttributeSet addAttribute(AttributeSet old, Object key, 813 Object value) 814 { 815 // FIXME: Not implemented. 816 return super.addAttribute(old, key, value); 817 } 818 819 /** 820 * Adds a set of attributes to the element. If any of these attributes are 821 * StyleConstants, they will be converted to CSS before forwarding to the 822 * superclass. 823 * 824 * @param old - the old set 825 * @param attr - the attributes to add 826 * @return the updated attribute set 827 */ 828 public AttributeSet addAttributes(AttributeSet old, AttributeSet attr) 829 { 830 // FIXME: Not implemented. 831 return super.addAttributes(old, attr); 832 } 833 834 /** 835 * Removes an attribute from the set. If the attribute is a 836 * StyleConstants, it will be converted to CSS before forwarding to the 837 * superclass. 838 * 839 * @param old - the old set 840 * @param key - the non-null attribute key 841 * @return the updated set 842 */ 843 public AttributeSet removeAttribute(AttributeSet old, Object key) 844 { 845 // FIXME: Not implemented. 846 return super.removeAttribute(old, key); 847 } 848 849 /** 850 * Removes an attribute from the set. If any of the attributes are 851 * StyleConstants, they will be converted to CSS before forwarding to the 852 * superclass. 853 * 854 * @param old - the old set 855 * @param attrs - the attributes to remove 856 * @return the updated set 857 */ 858 public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs) 859 { 860 // FIXME: Not implemented. 861 return super.removeAttributes(old, attrs); 862 } 863 864 /** 865 * Removes a set of attributes for the element. If any of the attributes is a 866 * StyleConstants, they will be converted to CSS before forwarding to the 867 * superclass. 868 * 869 * @param old - the old attribute set 870 * @param names - the attribute names 871 * @return the update attribute set 872 */ 873 public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) 874 { 875 // FIXME: Not implemented. 876 return super.removeAttributes(old, names); 877 } 878 879 /** 880 * Creates a compact set of attributes that might be shared. This is a hook 881 * for subclasses that want to change the behaviour of SmallAttributeSet. 882 * 883 * @param a - the set of attributes to be represented in the compact form. 884 * @return the set of attributes created 885 */ 886 protected StyleContext.SmallAttributeSet createSmallAttributeSet(AttributeSet a) 887 { 888 return super.createSmallAttributeSet(a); 889 } 890 891 /** 892 * Creates a large set of attributes. This set is not shared. This is a hook 893 * for subclasses that want to change the behaviour of the larger attribute 894 * storage format. 895 * 896 * @param a - the set of attributes to be represented in the larger form. 897 * @return the large set of attributes. 898 */ 899 protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) 900 { 901 return super.createLargeAttributeSet(a); 902 } 903 904 /** 905 * Gets the font to use for the given set. 906 * 907 * @param a - the set to get the font for. 908 * @return the font for the set 909 */ 910 public Font getFont(AttributeSet a) 911 { 912 int realSize = getFontSize(a); 913 914 // Decrement size for subscript and superscript. 915 Object valign = a.getAttribute(CSS.Attribute.VERTICAL_ALIGN); 916 if (valign != null) 917 { 918 String v = valign.toString(); 919 if (v.contains("sup") || v.contains("sub")) 920 realSize -= 2; 921 } 922 923 // TODO: Convert font family. 924 String family = "SansSerif"; 925 926 int style = Font.PLAIN; 927 FontWeight weight = (FontWeight) a.getAttribute(CSS.Attribute.FONT_WEIGHT); 928 if (weight != null) 929 style |= weight.getValue(); 930 FontStyle fStyle = (FontStyle) a.getAttribute(CSS.Attribute.FONT_STYLE); 931 if (fStyle != null) 932 style |= fStyle.getValue(); 933 return new Font(family, style, realSize); 934 } 935 936 /** 937 * Determines the EM base value based on the specified attributes. 938 * 939 * @param atts the attibutes 940 * 941 * @return the EM base value 942 */ 943 float getEMBase(AttributeSet atts) 944 { 945 Font font = getFont(atts); 946 FontRenderContext ctx = new FontRenderContext(null, false, false); 947 Rectangle2D bounds = font.getStringBounds("M", ctx); 948 return (float) bounds.getWidth(); 949 } 950 951 /** 952 * Determines the EX base value based on the specified attributes. 953 * 954 * @param atts the attibutes 955 * 956 * @return the EX base value 957 */ 958 float getEXBase(AttributeSet atts) 959 { 960 Font font = getFont(atts); 961 FontRenderContext ctx = new FontRenderContext(null, false, false); 962 Rectangle2D bounds = font.getStringBounds("x", ctx); 963 return (float) bounds.getHeight(); 964 } 965 966 /** 967 * Resolves the fontsize for a given set of attributes. 968 * 969 * @param atts the attributes 970 * 971 * @return the resolved font size 972 */ 973 private int getFontSize(AttributeSet atts) 974 { 975 int size = 12; 976 if (atts.isDefined(CSS.Attribute.FONT_SIZE)) 977 { 978 FontSize fs = (FontSize) atts.getAttribute(CSS.Attribute.FONT_SIZE); 979 if (fs.isRelative()) 980 { 981 int parSize = 12; 982 AttributeSet resolver = atts.getResolveParent(); 983 if (resolver != null) 984 parSize = getFontSize(resolver); 985 size = fs.getValue(parSize); 986 } 987 else 988 { 989 size = fs.getValue(); 990 } 991 } 992 else 993 { 994 AttributeSet resolver = atts.getResolveParent(); 995 if (resolver != null) 996 size = getFontSize(resolver); 997 } 998 return size; 999 } 1000 1001 /** 1002 * Takes a set of attributes and turns it into a foreground 1003 * color specification. This is used to specify things like, brigher, more hue 1004 * etc. 1005 * 1006 * @param a - the set to get the foreground color for 1007 * @return the foreground color for the set 1008 */ 1009 public Color getForeground(AttributeSet a) 1010 { 1011 CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.COLOR); 1012 Color color = null; 1013 if (c != null) 1014 color = c.getValue(); 1015 return color; 1016 } 1017 1018 /** 1019 * Takes a set of attributes and turns it into a background 1020 * color specification. This is used to specify things like, brigher, more hue 1021 * etc. 1022 * 1023 * @param a - the set to get the background color for 1024 * @return the background color for the set 1025 */ 1026 public Color getBackground(AttributeSet a) 1027 { 1028 CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.BACKGROUND_COLOR); 1029 Color color = null; 1030 if (c != null) 1031 color = c.getValue(); 1032 return color; 1033 } 1034 1035 /** 1036 * Gets the box formatter to use for the given set of CSS attributes. 1037 * 1038 * @param a - the given set 1039 * @return the box formatter 1040 */ 1041 public BoxPainter getBoxPainter(AttributeSet a) 1042 { 1043 return new BoxPainter(a, this); 1044 } 1045 1046 /** 1047 * Gets the list formatter to use for the given set of CSS attributes. 1048 * 1049 * @param a - the given set 1050 * @return the list formatter 1051 */ 1052 public ListPainter getListPainter(AttributeSet a) 1053 { 1054 return new ListPainter(a, this); 1055 } 1056 1057 /** 1058 * Sets the base font size between 1 and 7. 1059 * 1060 * @param sz - the new font size for the base. 1061 */ 1062 public void setBaseFontSize(int sz) 1063 { 1064 if (sz <= 7 && sz >= 1) 1065 baseFontSize = sz; 1066 } 1067 1068 /** 1069 * Sets the base font size from the String. It can either identify 1070 * a specific font size (between 1 and 7) or identify a relative 1071 * font size such as +1 or -2. 1072 * 1073 * @param size - the new font size as a String. 1074 */ 1075 public void setBaseFontSize(String size) 1076 { 1077 size.trim(); 1078 int temp = 0; 1079 try 1080 { 1081 if (size.length() == 2) 1082 { 1083 int i = new Integer(size.substring(1)).intValue(); 1084 if (size.startsWith("+")) 1085 temp = baseFontSize + i; 1086 else if (size.startsWith("-")) 1087 temp = baseFontSize - i; 1088 } 1089 else if (size.length() == 1) 1090 temp = new Integer(size.substring(0)).intValue(); 1091 1092 if (temp <= 7 && temp >= 1) 1093 baseFontSize = temp; 1094 } 1095 catch (NumberFormatException nfe) 1096 { 1097 // Do nothing here 1098 } 1099 } 1100 1101 /** 1102 * TODO 1103 * 1104 * @param pt - TODO 1105 * @return TODO 1106 */ 1107 public static int getIndexOfSize(float pt) 1108 { 1109 // FIXME: Not implemented. 1110 return 0; 1111 } 1112 1113 /** 1114 * Gets the point size, given a size index. 1115 * 1116 * @param index - the size index 1117 * @return the point size. 1118 */ 1119 public float getPointSize(int index) 1120 { 1121 // FIXME: Not implemented. 1122 return 0; 1123 } 1124 1125 /** 1126 * Given the string of the size, returns the point size value. 1127 * 1128 * @param size - the string representation of the size. 1129 * @return - the point size value. 1130 */ 1131 public float getPointSize(String size) 1132 { 1133 // FIXME: Not implemented. 1134 return 0; 1135 } 1136 1137 /** 1138 * Convert the color string represenation into java.awt.Color. The valid 1139 * values are like "aqua" , "#00FFFF" or "rgb(1,6,44)". 1140 * 1141 * @param colorName the color to convert. 1142 * @return the matching java.awt.color 1143 */ 1144 public Color stringToColor(String colorName) 1145 { 1146 return CSSColor.convertValue(colorName); 1147 } 1148 1149 /** 1150 * This class carries out some of the duties of CSS formatting. This enables views 1151 * to present the CSS formatting while not knowing how the CSS values are cached. 1152 * 1153 * This object is reponsible for the insets of a View and making sure 1154 * the background is maintained according to the CSS attributes. 1155 * 1156 * @author Lillian Angel (langel@redhat.com) 1157 */ 1158 public static class BoxPainter extends Object implements Serializable 1159 { 1160 1161 /** 1162 * The left inset. 1163 */ 1164 private float leftInset; 1165 1166 /** 1167 * The right inset. 1168 */ 1169 private float rightInset; 1170 1171 /** 1172 * The top inset. 1173 */ 1174 private float topInset; 1175 1176 /** 1177 * The bottom inset. 1178 */ 1179 private float bottomInset; 1180 1181 /** 1182 * The border of the box. 1183 */ 1184 private Border border; 1185 1186 private float leftPadding; 1187 private float rightPadding; 1188 private float topPadding; 1189 private float bottomPadding; 1190 1191 /** 1192 * The background color. 1193 */ 1194 private Color background; 1195 1196 /** 1197 * Package-private constructor. 1198 * 1199 * @param as - AttributeSet for painter 1200 */ 1201 BoxPainter(AttributeSet as, StyleSheet ss) 1202 { 1203 float emBase = ss.getEMBase(as); 1204 float exBase = ss.getEXBase(as); 1205 // Fetch margins. 1206 Length l = (Length) as.getAttribute(CSS.Attribute.MARGIN_LEFT); 1207 if (l != null) 1208 { 1209 l.setFontBases(emBase, exBase); 1210 leftInset = l.getValue(); 1211 } 1212 l = (Length) as.getAttribute(CSS.Attribute.MARGIN_RIGHT); 1213 if (l != null) 1214 { 1215 l.setFontBases(emBase, exBase); 1216 rightInset = l.getValue(); 1217 } 1218 l = (Length) as.getAttribute(CSS.Attribute.MARGIN_TOP); 1219 if (l != null) 1220 { 1221 l.setFontBases(emBase, exBase); 1222 topInset = l.getValue(); 1223 } 1224 l = (Length) as.getAttribute(CSS.Attribute.MARGIN_BOTTOM); 1225 if (l != null) 1226 { 1227 l.setFontBases(emBase, exBase); 1228 bottomInset = l.getValue(); 1229 } 1230 1231 // Fetch padding. 1232 l = (Length) as.getAttribute(CSS.Attribute.PADDING_LEFT); 1233 if (l != null) 1234 { 1235 l.setFontBases(emBase, exBase); 1236 leftPadding = l.getValue(); 1237 } 1238 l = (Length) as.getAttribute(CSS.Attribute.PADDING_RIGHT); 1239 if (l != null) 1240 { 1241 l.setFontBases(emBase, exBase); 1242 rightPadding = l.getValue(); 1243 } 1244 l = (Length) as.getAttribute(CSS.Attribute.PADDING_TOP); 1245 if (l != null) 1246 { 1247 l.setFontBases(emBase, exBase); 1248 topPadding = l.getValue(); 1249 } 1250 l = (Length) as.getAttribute(CSS.Attribute.PADDING_BOTTOM); 1251 if (l != null) 1252 { 1253 l.setFontBases(emBase, exBase); 1254 bottomPadding = l.getValue(); 1255 } 1256 1257 // Determine border. 1258 border = new CSSBorder(as, ss); 1259 1260 // Determine background. 1261 background = ss.getBackground(as); 1262 1263 } 1264 1265 1266 /** 1267 * Gets the inset needed on a given side to account for the margin, border 1268 * and padding. 1269 * 1270 * @param size - the size of the box to get the inset for. View.TOP, View.LEFT, 1271 * View.BOTTOM or View.RIGHT. 1272 * @param v - the view making the request. This is used to get the AttributeSet, 1273 * amd may be used to resolve percentage arguments. 1274 * @return the inset 1275 * @throws IllegalArgumentException - for an invalid direction. 1276 */ 1277 public float getInset(int size, View v) 1278 { 1279 float inset; 1280 switch (size) 1281 { 1282 case View.TOP: 1283 inset = topInset; 1284 if (border != null) 1285 inset += border.getBorderInsets(null).top; 1286 inset += topPadding; 1287 break; 1288 case View.BOTTOM: 1289 inset = bottomInset; 1290 if (border != null) 1291 inset += border.getBorderInsets(null).bottom; 1292 inset += bottomPadding; 1293 break; 1294 case View.LEFT: 1295 inset = leftInset; 1296 if (border != null) 1297 inset += border.getBorderInsets(null).left; 1298 inset += leftPadding; 1299 break; 1300 case View.RIGHT: 1301 inset = rightInset; 1302 if (border != null) 1303 inset += border.getBorderInsets(null).right; 1304 inset += rightPadding; 1305 break; 1306 default: 1307 inset = 0.0F; 1308 } 1309 return inset; 1310 } 1311 1312 /** 1313 * Paints the CSS box according to the attributes given. This should 1314 * paint the border, padding and background. 1315 * 1316 * @param g - the graphics configuration 1317 * @param x - the x coordinate 1318 * @param y - the y coordinate 1319 * @param w - the width of the allocated area 1320 * @param h - the height of the allocated area 1321 * @param v - the view making the request 1322 */ 1323 public void paint(Graphics g, float x, float y, float w, float h, View v) 1324 { 1325 int inX = (int) (x + leftInset); 1326 int inY = (int) (y + topInset); 1327 int inW = (int) (w - leftInset - rightInset); 1328 int inH = (int) (h - topInset - bottomInset); 1329 if (background != null) 1330 { 1331 g.setColor(background); 1332 g.fillRect(inX, inY, inW, inH); 1333 } 1334 if (border != null) 1335 { 1336 border.paintBorder(null, g, inX, inY, inW, inH); 1337 } 1338 } 1339 } 1340 1341 /** 1342 * This class carries out some of the CSS list formatting duties. Implementations 1343 * of this class enable views to present the CSS formatting while not knowing anything 1344 * about how the CSS values are being cached. 1345 * 1346 * @author Lillian Angel (langel@redhat.com) 1347 */ 1348 public static class ListPainter implements Serializable 1349 { 1350 1351 /** 1352 * Attribute set for painter 1353 */ 1354 private AttributeSet attributes; 1355 1356 /** 1357 * The associated style sheet. 1358 */ 1359 private StyleSheet styleSheet; 1360 1361 /** 1362 * The bullet type. 1363 */ 1364 private String type; 1365 1366 /** 1367 * Package-private constructor. 1368 * 1369 * @param as - AttributeSet for painter 1370 */ 1371 ListPainter(AttributeSet as, StyleSheet ss) 1372 { 1373 attributes = as; 1374 styleSheet = ss; 1375 type = (String) as.getAttribute(CSS.Attribute.LIST_STYLE_TYPE); 1376 } 1377 1378 /** 1379 * Cached rectangle re-used in the paint method below. 1380 */ 1381 private final Rectangle tmpRect = new Rectangle(); 1382 1383 /** 1384 * Paints the CSS list decoration according to the attributes given. 1385 * 1386 * @param g - the graphics configuration 1387 * @param x - the x coordinate 1388 * @param y - the y coordinate 1389 * @param w - the width of the allocated area 1390 * @param h - the height of the allocated area 1391 * @param v - the view making the request 1392 * @param item - the list item to be painted >=0. 1393 */ 1394 public void paint(Graphics g, float x, float y, float w, float h, View v, 1395 int item) 1396 { 1397 // FIXME: This is a very simplistic list rendering. We still need 1398 // to implement different bullet types (see type field) and custom 1399 // bullets via images. 1400 View itemView = v.getView(item); 1401 AttributeSet viewAtts = itemView.getAttributes(); 1402 Object tag = viewAtts.getAttribute(StyleConstants.NameAttribute); 1403 // Only paint something here when the child view is an LI tag 1404 // and the calling view is some of the list tags then). 1405 if (tag != null && tag == HTML.Tag.LI) 1406 { 1407 g.setColor(Color.BLACK); 1408 int centerX = (int) (x - 12); 1409 int centerY = -1; 1410 // For paragraphs (almost all cases) center bullet vertically 1411 // in the middle of the first line. 1412 tmpRect.setBounds((int) x, (int) y, (int) w, (int) h); 1413 if (itemView.getViewCount() > 0) 1414 { 1415 View v1 = itemView.getView(0); 1416 if (v1 instanceof ParagraphView && v1.getViewCount() > 0) 1417 { 1418 Shape a1 = itemView.getChildAllocation(0, tmpRect); 1419 Rectangle r1 = a1 instanceof Rectangle ? (Rectangle) a1 1420 : a1.getBounds(); 1421 ParagraphView par = (ParagraphView) v1; 1422 Shape a = par.getChildAllocation(0, r1); 1423 if (a != null) 1424 { 1425 Rectangle r = a instanceof Rectangle ? (Rectangle) a 1426 : a.getBounds(); 1427 centerY = (int) (r.height / 2 + r.y); 1428 } 1429 } 1430 } 1431 if (centerY == -1) 1432 { 1433 centerY =(int) (h / 2 + y); 1434 } 1435 g.fillOval(centerX - 3, centerY - 3, 6, 6); 1436 } 1437 } 1438 } 1439 1440 /** 1441 * Converts an AttributeSet to a Map. This is used for CSS resolving. 1442 * 1443 * @param atts the attributes to convert 1444 * 1445 * @return the converted map 1446 */ 1447 private Map attributeSetToMap(AttributeSet atts) 1448 { 1449 HashMap map = new HashMap(); 1450 Enumeration keys = atts.getAttributeNames(); 1451 while (keys.hasMoreElements()) 1452 { 1453 Object key = keys.nextElement(); 1454 Object value = atts.getAttribute(key); 1455 map.put(key.toString(), value.toString()); 1456 } 1457 return map; 1458 } 1459 }