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