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<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 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[], Map[])}. 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 path, HTML.Tag tag) 392 { 393 int count = path.size(); 394 String[] tags = new String[count]; 395 Map[] attributes = new Map[count]; 396 for (int i = 0; i < count; i++) 397 { 398 Element el = (Element) path.get(i); 399 AttributeSet atts = el.getAttributes(); 400 if (i == 0 && el.isLeaf()) 401 { 402 Object o = atts.getAttribute(tag); 403 if (o instanceof AttributeSet) 404 atts = (AttributeSet) o; 405 else 406 atts = null; 407 } 408 if (atts != null) 409 { 410 HTML.Tag t = 411 (HTML.Tag) atts.getAttribute(StyleConstants.NameAttribute); 412 if (t != null) 413 tags[i] = t.toString(); 414 else 415 tags[i] = null; 416 attributes[i] = attributeSetToMap(atts); 417 } 418 else 419 { 420 tags[i] = null; 421 attributes[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, Map[] attributes) 438 { 439 // FIXME: This style resolver is not correct. But it works good enough for 440 // the default.css. 441 ArrayList<CSSStyle> styles = new ArrayList<CSSStyle>(); 442 for (CSSStyle style : css) 443 { 444 if (style.selector.matches(tags, attributes)) 445 styles.add(style); 446 } 447 448 // Add styles from linked stylesheets. 449 if (linked != null) 450 { 451 for (int i = linked.size() - 1; i >= 0; i--) 452 { 453 StyleSheet ss = linked.get(i); 454 for (int j = ss.css.size() - 1; j >= 0; j--) 455 { 456 CSSStyle style = ss.css.get(j); 457 if (style.selector.matches(tags, attributes)) 458 styles.add(style); 459 } 460 } 461 } 462 463 // Sort selectors. 464 Collections.sort(styles); 465 Style[] styleArray = new Style[styles.size()]; 466 styleArray = (Style[]) styles.toArray(styleArray); 467 Style resolved = new MultiStyle(selector, 468 (Style[]) styles.toArray(styleArray)); 469 resolvedStyles.put(selector, resolved); 470 return resolved; 471 } 472 473 /** 474 * Gets the rule that best matches the selector. selector is a space 475 * separated String of element names. The attributes of the returned 476 * Style will change as rules are added and removed. 477 * 478 * @param selector - the element names separated by spaces 479 * @return the set of CSS attributes to use to render 480 */ 481 public Style getRule(String selector) 482 { 483 CSSStyle best = null; 484 for (Iterator i = css.iterator(); i.hasNext();) 485 { 486 CSSStyle style = (CSSStyle) i.next(); 487 if (style.compareTo(best) < 0) 488 best = style; 489 } 490 return best; 491 } 492 493 /** 494 * Adds a set of rules to the sheet. The rules are expected to be in valid 495 * CSS format. This is called as a result of parsing a <style> tag 496 * 497 * @param rule - the rule to add to the sheet 498 */ 499 public void addRule(String rule) 500 { 501 CSSStyleSheetParserCallback cb = 502 new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL); 503 // FIXME: Handle ref. 504 StringReader in = new StringReader(rule); 505 CSSParser parser = new CSSParser(in, cb); 506 try 507 { 508 parser.parse(); 509 } 510 catch (IOException ex) 511 { 512 // Shouldn't happen. And if, then don't let it bork the outside code. 513 } 514 // Clean up resolved styles cache so that the new styles are recognized 515 // on next stylesheet request. 516 resolvedStyles.clear(); 517 } 518 519 /** 520 * Translates a CSS declaration into an AttributeSet. This is called 521 * as a result of encountering an HTML style attribute. 522 * 523 * @param decl - the declaration to get 524 * @return the AttributeSet representing the declaration 525 */ 526 public AttributeSet getDeclaration(String decl) 527 { 528 if (decl == null) 529 return SimpleAttributeSet.EMPTY; 530 // FIXME: Not implemented. 531 return null; 532 } 533 534 /** 535 * Loads a set of rules that have been specified in terms of CSS grammar. 536 * If there are any conflicts with existing rules, the new rule is added. 537 * 538 * @param in - the stream to read the CSS grammar from. 539 * @param ref - the reference URL. It is the location of the stream, it may 540 * be null. All relative URLs specified in the stream will be based upon this 541 * parameter. 542 * @throws IOException - For any IO error while reading 543 */ 544 public void loadRules(Reader in, URL ref) 545 throws IOException 546 { 547 CSSStyleSheetParserCallback cb = 548 new CSSStyleSheetParserCallback(CSSStyle.PREC_UA); 549 // FIXME: Handle ref. 550 CSSParser parser = new CSSParser(in, cb); 551 parser.parse(); 552 } 553 554 /** 555 * Gets a set of attributes to use in the view. This is a set of 556 * attributes that can be used for View.getAttributes 557 * 558 * @param v - the view to get the set for 559 * @return the AttributeSet to use in the view. 560 */ 561 public AttributeSet getViewAttributes(View v) 562 { 563 return new ViewAttributeSet(v, this); 564 } 565 566 /** 567 * Removes a style previously added. 568 * 569 * @param nm - the name of the style to remove 570 */ 571 public void removeStyle(String nm) 572 { 573 // FIXME: Not implemented. 574 super.removeStyle(nm); 575 } 576 577 /** 578 * Adds the rules from ss to those of the receiver. ss's rules will 579 * override the old rules. An added StyleSheet will never override the rules 580 * of the receiving style sheet. 581 * 582 * @param ss - the new StyleSheet. 583 */ 584 public void addStyleSheet(StyleSheet ss) 585 { 586 if (linked == null) 587 linked = new ArrayList(); 588 linked.add(ss); 589 } 590 591 /** 592 * Removes ss from those of the receiver 593 * 594 * @param ss - the StyleSheet to remove. 595 */ 596 public void removeStyleSheet(StyleSheet ss) 597 { 598 if (linked != null) 599 { 600 linked.remove(ss); 601 } 602 } 603 604 /** 605 * Returns an array of the linked StyleSheets. May return null. 606 * 607 * @return - An array of the linked StyleSheets. 608 */ 609 public StyleSheet[] getStyleSheets() 610 { 611 StyleSheet[] linkedSS; 612 if (linked != null) 613 { 614 linkedSS = new StyleSheet[linked.size()]; 615 linkedSS = linked.toArray(linkedSS); 616 } 617 else 618 { 619 linkedSS = null; 620 } 621 return linkedSS; 622 } 623 624 /** 625 * Imports a style sheet from the url. The rules are directly added to the 626 * receiver. This is usually called when a <link> tag is resolved in an 627 * HTML document. 628 * 629 * @param url the URL to import the StyleSheet from 630 */ 631 public void importStyleSheet(URL url) 632 { 633 try 634 { 635 InputStream in = url.openStream(); 636 Reader r = new BufferedReader(new InputStreamReader(in)); 637 CSSStyleSheetParserCallback cb = 638 new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL); 639 CSSParser parser = new CSSParser(r, cb); 640 parser.parse(); 641 } 642 catch (IOException ex) 643 { 644 // We can't do anything about it I guess. 645 } 646 } 647 648 /** 649 * Sets the base url. All import statements that are relative, will be 650 * relative to base. 651 * 652 * @param base - 653 * the base URL. 654 */ 655 public void setBase(URL base) 656 { 657 this.base = base; 658 } 659 660 /** 661 * Gets the base url. 662 * 663 * @return - the base 664 */ 665 public URL getBase() 666 { 667 return base; 668 } 669 670 /** 671 * Adds a CSS attribute to the given set. 672 * 673 * @param attr - the attribute set 674 * @param key - the attribute to add 675 * @param value - the value of the key 676 */ 677 public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key, 678 String value) 679 { 680 Object val = CSS.getValue(key, value); 681 CSS.addInternal(attr, key, value); 682 attr.addAttribute(key, val); 683 } 684 685 /** 686 * Adds a CSS attribute to the given set. 687 * This method parses the value argument from HTML based on key. 688 * Returns true if it finds a valid value for the given key, 689 * and false otherwise. 690 * 691 * @param attr - the attribute set 692 * @param key - the attribute to add 693 * @param value - the value of the key 694 * @return true if a valid value was found. 695 */ 696 public boolean addCSSAttributeFromHTML(MutableAttributeSet attr, CSS.Attribute key, 697 String value) 698 { 699 // FIXME: Need to parse value from HTML based on key. 700 attr.addAttribute(key, value); 701 return attr.containsAttribute(key, value); 702 } 703 704 /** 705 * Converts a set of HTML attributes to an equivalent set of CSS attributes. 706 * 707 * @param htmlAttrSet - the set containing the HTML attributes. 708 * @return the set of CSS attributes 709 */ 710 public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) 711 { 712 AttributeSet cssAttr = htmlAttrSet.copyAttributes(); 713 714 // The HTML align attribute maps directly to the CSS text-align attribute. 715 Object o = htmlAttrSet.getAttribute(HTML.Attribute.ALIGN); 716 if (o != null) 717 cssAttr = addAttribute(cssAttr, CSS.Attribute.TEXT_ALIGN, o); 718 719 // The HTML width attribute maps directly to CSS width. 720 o = htmlAttrSet.getAttribute(HTML.Attribute.WIDTH); 721 if (o != null) 722 cssAttr = addAttribute(cssAttr, CSS.Attribute.WIDTH, 723 new Length(o.toString())); 724 725 // The HTML height attribute maps directly to CSS height. 726 o = htmlAttrSet.getAttribute(HTML.Attribute.HEIGHT); 727 if (o != null) 728 cssAttr = addAttribute(cssAttr, CSS.Attribute.HEIGHT, 729 new Length(o.toString())); 730 731 o = htmlAttrSet.getAttribute(HTML.Attribute.NOWRAP); 732 if (o != null) 733 cssAttr = addAttribute(cssAttr, CSS.Attribute.WHITE_SPACE, "nowrap"); 734 735 // Map cellspacing attr of tables to CSS border-spacing. 736 o = htmlAttrSet.getAttribute(HTML.Attribute.CELLSPACING); 737 if (o != null) 738 cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_SPACING, 739 new Length(o.toString())); 740 741 // For table cells and headers, fetch the cellpadding value from the 742 // parent table and set it as CSS padding attribute. 743 HTML.Tag tag = (HTML.Tag) 744 htmlAttrSet.getAttribute(StyleConstants.NameAttribute); 745 if ((tag == HTML.Tag.TD || tag == HTML.Tag.TH) 746 && htmlAttrSet instanceof Element) 747 { 748 Element el = (Element) htmlAttrSet; 749 AttributeSet tableAttrs = el.getParentElement().getParentElement() 750 .getAttributes(); 751 o = tableAttrs.getAttribute(HTML.Attribute.CELLPADDING); 752 if (o != null) 753 { 754 Length l = new Length(o.toString()); 755 cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_BOTTOM, l); 756 cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_LEFT, l); 757 cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_RIGHT, l); 758 cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_TOP, l); 759 } 760 o = tableAttrs.getAttribute(HTML.Attribute.BORDER); 761 cssAttr = translateBorder(cssAttr, o); 762 } 763 764 // Translate border attribute. 765 o = cssAttr.getAttribute(HTML.Attribute.BORDER); 766 cssAttr = translateBorder(cssAttr, o); 767 768 // TODO: Add more mappings. 769 return cssAttr; 770 } 771 772 /** 773 * Translates a HTML border attribute to a corresponding set of CSS 774 * attributes. 775 * 776 * @param cssAttr the original set of CSS attributes to add to 777 * @param o the value of the border attribute 778 * 779 * @return the new set of CSS attributes 780 */ 781 private AttributeSet translateBorder(AttributeSet cssAttr, Object o) 782 { 783 if (o != null) 784 { 785 BorderWidth l = new BorderWidth(o.toString()); 786 if (l.getValue() > 0) 787 { 788 cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_WIDTH, l); 789 cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_STYLE, 790 "solid"); 791 cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_COLOR, 792 new CSSColor("black")); 793 } 794 } 795 return cssAttr; 796 } 797 798 /** 799 * Adds an attribute to the given set and returns a new set. This is implemented 800 * to convert StyleConstants attributes to CSS before forwarding them to the superclass. 801 * The StyleConstants attribute do not have corresponding CSS entry, the attribute 802 * is stored (but will likely not be used). 803 * 804 * @param old - the old set 805 * @param key - the non-null attribute key 806 * @param value - the attribute value 807 * @return the updated set 808 */ 809 public AttributeSet addAttribute(AttributeSet old, Object key, 810 Object value) 811 { 812 // FIXME: Not implemented. 813 return super.addAttribute(old, key, value); 814 } 815 816 /** 817 * Adds a set of attributes to the element. If any of these attributes are 818 * StyleConstants, they will be converted to CSS before forwarding to the 819 * superclass. 820 * 821 * @param old - the old set 822 * @param attr - the attributes to add 823 * @return the updated attribute set 824 */ 825 public AttributeSet addAttributes(AttributeSet old, AttributeSet attr) 826 { 827 // FIXME: Not implemented. 828 return super.addAttributes(old, attr); 829 } 830 831 /** 832 * Removes an attribute from the set. If the attribute is a 833 * StyleConstants, it will be converted to CSS before forwarding to the 834 * superclass. 835 * 836 * @param old - the old set 837 * @param key - the non-null attribute key 838 * @return the updated set 839 */ 840 public AttributeSet removeAttribute(AttributeSet old, Object key) 841 { 842 // FIXME: Not implemented. 843 return super.removeAttribute(old, key); 844 } 845 846 /** 847 * Removes an attribute from the set. If any of the attributes are 848 * StyleConstants, they will be converted to CSS before forwarding to the 849 * superclass. 850 * 851 * @param old - the old set 852 * @param attrs - the attributes to remove 853 * @return the updated set 854 */ 855 public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs) 856 { 857 // FIXME: Not implemented. 858 return super.removeAttributes(old, attrs); 859 } 860 861 /** 862 * Removes a set of attributes for the element. If any of the attributes is a 863 * StyleConstants, they will be converted to CSS before forwarding to the 864 * superclass. 865 * 866 * @param old - the old attribute set 867 * @param names - the attribute names 868 * @return the update attribute set 869 */ 870 public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) 871 { 872 // FIXME: Not implemented. 873 return super.removeAttributes(old, names); 874 } 875 876 /** 877 * Creates a compact set of attributes that might be shared. This is a hook 878 * for subclasses that want to change the behaviour of SmallAttributeSet. 879 * 880 * @param a - the set of attributes to be represented in the compact form. 881 * @return the set of attributes created 882 */ 883 protected StyleContext.SmallAttributeSet createSmallAttributeSet(AttributeSet a) 884 { 885 return super.createSmallAttributeSet(a); 886 } 887 888 /** 889 * Creates a large set of attributes. This set is not shared. This is a hook 890 * for subclasses that want to change the behaviour of the larger attribute 891 * storage format. 892 * 893 * @param a - the set of attributes to be represented in the larger form. 894 * @return the large set of attributes. 895 */ 896 protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) 897 { 898 return super.createLargeAttributeSet(a); 899 } 900 901 /** 902 * Gets the font to use for the given set. 903 * 904 * @param a - the set to get the font for. 905 * @return the font for the set 906 */ 907 public Font getFont(AttributeSet a) 908 { 909 int realSize = getFontSize(a); 910 911 // Decrement size for subscript and superscript. 912 Object valign = a.getAttribute(CSS.Attribute.VERTICAL_ALIGN); 913 if (valign != null) 914 { 915 String v = valign.toString(); 916 if (v.contains("sup") || v.contains("sub")) 917 realSize -= 2; 918 } 919 920 // TODO: Convert font family. 921 String family = "SansSerif"; 922 923 int style = Font.PLAIN; 924 FontWeight weight = (FontWeight) a.getAttribute(CSS.Attribute.FONT_WEIGHT); 925 if (weight != null) 926 style |= weight.getValue(); 927 FontStyle fStyle = (FontStyle) a.getAttribute(CSS.Attribute.FONT_STYLE); 928 if (fStyle != null) 929 style |= fStyle.getValue(); 930 return new Font(family, style, realSize); 931 } 932 933 /** 934 * Determines the EM base value based on the specified attributes. 935 * 936 * @param atts the attibutes 937 * 938 * @return the EM base value 939 */ 940 float getEMBase(AttributeSet atts) 941 { 942 Font font = getFont(atts); 943 FontRenderContext ctx = new FontRenderContext(null, false, false); 944 Rectangle2D bounds = font.getStringBounds("M", ctx); 945 return (float) bounds.getWidth(); 946 } 947 948 /** 949 * Determines the EX base value based on the specified attributes. 950 * 951 * @param atts the attibutes 952 * 953 * @return the EX base value 954 */ 955 float getEXBase(AttributeSet atts) 956 { 957 Font font = getFont(atts); 958 FontRenderContext ctx = new FontRenderContext(null, false, false); 959 Rectangle2D bounds = font.getStringBounds("x", ctx); 960 return (float) bounds.getHeight(); 961 } 962 963 /** 964 * Resolves the fontsize for a given set of attributes. 965 * 966 * @param atts the attributes 967 * 968 * @return the resolved font size 969 */ 970 private int getFontSize(AttributeSet atts) 971 { 972 int size = 12; 973 if (atts.isDefined(CSS.Attribute.FONT_SIZE)) 974 { 975 FontSize fs = (FontSize) atts.getAttribute(CSS.Attribute.FONT_SIZE); 976 if (fs.isRelative()) 977 { 978 int parSize = 12; 979 AttributeSet resolver = atts.getResolveParent(); 980 if (resolver != null) 981 parSize = getFontSize(resolver); 982 size = fs.getValue(parSize); 983 } 984 else 985 { 986 size = fs.getValue(); 987 } 988 } 989 else 990 { 991 AttributeSet resolver = atts.getResolveParent(); 992 if (resolver != null) 993 size = getFontSize(resolver); 994 } 995 return size; 996 } 997 998 /** 999 * Takes a set of attributes and turns it into a foreground 1000 * color specification. This is used to specify things like, brigher, more hue 1001 * etc. 1002 * 1003 * @param a - the set to get the foreground color for 1004 * @return the foreground color for the set 1005 */ 1006 public Color getForeground(AttributeSet a) 1007 { 1008 CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.COLOR); 1009 Color color = null; 1010 if (c != null) 1011 color = c.getValue(); 1012 return color; 1013 } 1014 1015 /** 1016 * Takes a set of attributes and turns it into a background 1017 * color specification. This is used to specify things like, brigher, more hue 1018 * etc. 1019 * 1020 * @param a - the set to get the background color for 1021 * @return the background color for the set 1022 */ 1023 public Color getBackground(AttributeSet a) 1024 { 1025 CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.BACKGROUND_COLOR); 1026 Color color = null; 1027 if (c != null) 1028 color = c.getValue(); 1029 return color; 1030 } 1031 1032 /** 1033 * Gets the box formatter to use for the given set of CSS attributes. 1034 * 1035 * @param a - the given set 1036 * @return the box formatter 1037 */ 1038 public BoxPainter getBoxPainter(AttributeSet a) 1039 { 1040 return new BoxPainter(a, this); 1041 } 1042 1043 /** 1044 * Gets the list formatter to use for the given set of CSS attributes. 1045 * 1046 * @param a - the given set 1047 * @return the list formatter 1048 */ 1049 public ListPainter getListPainter(AttributeSet a) 1050 { 1051 return new ListPainter(a, this); 1052 } 1053 1054 /** 1055 * Sets the base font size between 1 and 7. 1056 * 1057 * @param sz - the new font size for the base. 1058 */ 1059 public void setBaseFontSize(int sz) 1060 { 1061 if (sz <= 7 && sz >= 1) 1062 baseFontSize = sz; 1063 } 1064 1065 /** 1066 * Sets the base font size from the String. It can either identify 1067 * a specific font size (between 1 and 7) or identify a relative 1068 * font size such as +1 or -2. 1069 * 1070 * @param size - the new font size as a String. 1071 */ 1072 public void setBaseFontSize(String size) 1073 { 1074 size = size.trim(); 1075 int temp = 0; 1076 try 1077 { 1078 if (size.length() == 2) 1079 { 1080 int i = new Integer(size.substring(1)).intValue(); 1081 if (size.startsWith("+")) 1082 temp = baseFontSize + i; 1083 else if (size.startsWith("-")) 1084 temp = baseFontSize - i; 1085 } 1086 else if (size.length() == 1) 1087 temp = new Integer(size.substring(0)).intValue(); 1088 1089 if (temp <= 7 && temp >= 1) 1090 baseFontSize = temp; 1091 } 1092 catch (NumberFormatException nfe) 1093 { 1094 // Do nothing here 1095 } 1096 } 1097 1098 /** 1099 * TODO 1100 * 1101 * @param pt - TODO 1102 * @return TODO 1103 */ 1104 public static int getIndexOfSize(float pt) 1105 { 1106 // FIXME: Not implemented. 1107 return 0; 1108 } 1109 1110 /** 1111 * Gets the point size, given a size index. 1112 * 1113 * @param index - the size index 1114 * @return the point size. 1115 */ 1116 public float getPointSize(int index) 1117 { 1118 // FIXME: Not implemented. 1119 return 0; 1120 } 1121 1122 /** 1123 * Given the string of the size, returns the point size value. 1124 * 1125 * @param size - the string representation of the size. 1126 * @return - the point size value. 1127 */ 1128 public float getPointSize(String size) 1129 { 1130 // FIXME: Not implemented. 1131 return 0; 1132 } 1133 1134 /** 1135 * Convert the color string represenation into java.awt.Color. The valid 1136 * values are like "aqua" , "#00FFFF" or "rgb(1,6,44)". 1137 * 1138 * @param colorName the color to convert. 1139 * @return the matching java.awt.color 1140 */ 1141 public Color stringToColor(String colorName) 1142 { 1143 return CSSColor.convertValue(colorName); 1144 } 1145 1146 /** 1147 * This class carries out some of the duties of CSS formatting. This enables views 1148 * to present the CSS formatting while not knowing how the CSS values are cached. 1149 * 1150 * This object is reponsible for the insets of a View and making sure 1151 * the background is maintained according to the CSS attributes. 1152 * 1153 * @author Lillian Angel (langel@redhat.com) 1154 */ 1155 public static class BoxPainter extends Object implements Serializable 1156 { 1157 1158 /** 1159 * The left inset. 1160 */ 1161 private float leftInset; 1162 1163 /** 1164 * The right inset. 1165 */ 1166 private float rightInset; 1167 1168 /** 1169 * The top inset. 1170 */ 1171 private float topInset; 1172 1173 /** 1174 * The bottom inset. 1175 */ 1176 private float bottomInset; 1177 1178 /** 1179 * The border of the box. 1180 */ 1181 private Border border; 1182 1183 private float leftPadding; 1184 private float rightPadding; 1185 private float topPadding; 1186 private float bottomPadding; 1187 1188 /** 1189 * The background color. 1190 */ 1191 private Color background; 1192 1193 /** 1194 * Package-private constructor. 1195 * 1196 * @param as - AttributeSet for painter 1197 */ 1198 BoxPainter(AttributeSet as, StyleSheet ss) 1199 { 1200 float emBase = ss.getEMBase(as); 1201 float exBase = ss.getEXBase(as); 1202 // Fetch margins. 1203 Length l = (Length) as.getAttribute(CSS.Attribute.MARGIN_LEFT); 1204 if (l != null) 1205 { 1206 l.setFontBases(emBase, exBase); 1207 leftInset = l.getValue(); 1208 } 1209 l = (Length) as.getAttribute(CSS.Attribute.MARGIN_RIGHT); 1210 if (l != null) 1211 { 1212 l.setFontBases(emBase, exBase); 1213 rightInset = l.getValue(); 1214 } 1215 l = (Length) as.getAttribute(CSS.Attribute.MARGIN_TOP); 1216 if (l != null) 1217 { 1218 l.setFontBases(emBase, exBase); 1219 topInset = l.getValue(); 1220 } 1221 l = (Length) as.getAttribute(CSS.Attribute.MARGIN_BOTTOM); 1222 if (l != null) 1223 { 1224 l.setFontBases(emBase, exBase); 1225 bottomInset = l.getValue(); 1226 } 1227 1228 // Fetch padding. 1229 l = (Length) as.getAttribute(CSS.Attribute.PADDING_LEFT); 1230 if (l != null) 1231 { 1232 l.setFontBases(emBase, exBase); 1233 leftPadding = l.getValue(); 1234 } 1235 l = (Length) as.getAttribute(CSS.Attribute.PADDING_RIGHT); 1236 if (l != null) 1237 { 1238 l.setFontBases(emBase, exBase); 1239 rightPadding = l.getValue(); 1240 } 1241 l = (Length) as.getAttribute(CSS.Attribute.PADDING_TOP); 1242 if (l != null) 1243 { 1244 l.setFontBases(emBase, exBase); 1245 topPadding = l.getValue(); 1246 } 1247 l = (Length) as.getAttribute(CSS.Attribute.PADDING_BOTTOM); 1248 if (l != null) 1249 { 1250 l.setFontBases(emBase, exBase); 1251 bottomPadding = l.getValue(); 1252 } 1253 1254 // Determine border. 1255 border = new CSSBorder(as, ss); 1256 1257 // Determine background. 1258 background = ss.getBackground(as); 1259 1260 } 1261 1262 1263 /** 1264 * Gets the inset needed on a given side to account for the margin, border 1265 * and padding. 1266 * 1267 * @param size - the size of the box to get the inset for. View.TOP, View.LEFT, 1268 * View.BOTTOM or View.RIGHT. 1269 * @param v - the view making the request. This is used to get the AttributeSet, 1270 * amd may be used to resolve percentage arguments. 1271 * @return the inset 1272 * @throws IllegalArgumentException - for an invalid direction. 1273 */ 1274 public float getInset(int size, View v) 1275 { 1276 float inset; 1277 switch (size) 1278 { 1279 case View.TOP: 1280 inset = topInset; 1281 if (border != null) 1282 inset += border.getBorderInsets(null).top; 1283 inset += topPadding; 1284 break; 1285 case View.BOTTOM: 1286 inset = bottomInset; 1287 if (border != null) 1288 inset += border.getBorderInsets(null).bottom; 1289 inset += bottomPadding; 1290 break; 1291 case View.LEFT: 1292 inset = leftInset; 1293 if (border != null) 1294 inset += border.getBorderInsets(null).left; 1295 inset += leftPadding; 1296 break; 1297 case View.RIGHT: 1298 inset = rightInset; 1299 if (border != null) 1300 inset += border.getBorderInsets(null).right; 1301 inset += rightPadding; 1302 break; 1303 default: 1304 inset = 0.0F; 1305 } 1306 return inset; 1307 } 1308 1309 /** 1310 * Paints the CSS box according to the attributes given. This should 1311 * paint the border, padding and background. 1312 * 1313 * @param g - the graphics configuration 1314 * @param x - the x coordinate 1315 * @param y - the y coordinate 1316 * @param w - the width of the allocated area 1317 * @param h - the height of the allocated area 1318 * @param v - the view making the request 1319 */ 1320 public void paint(Graphics g, float x, float y, float w, float h, View v) 1321 { 1322 int inX = (int) (x + leftInset); 1323 int inY = (int) (y + topInset); 1324 int inW = (int) (w - leftInset - rightInset); 1325 int inH = (int) (h - topInset - bottomInset); 1326 if (background != null) 1327 { 1328 g.setColor(background); 1329 g.fillRect(inX, inY, inW, inH); 1330 } 1331 if (border != null) 1332 { 1333 border.paintBorder(null, g, inX, inY, inW, inH); 1334 } 1335 } 1336 } 1337 1338 /** 1339 * This class carries out some of the CSS list formatting duties. Implementations 1340 * of this class enable views to present the CSS formatting while not knowing anything 1341 * about how the CSS values are being cached. 1342 * 1343 * @author Lillian Angel (langel@redhat.com) 1344 */ 1345 public static class ListPainter implements Serializable 1346 { 1347 1348 /** 1349 * Attribute set for painter 1350 */ 1351 private AttributeSet attributes; 1352 1353 /** 1354 * The associated style sheet. 1355 */ 1356 private StyleSheet styleSheet; 1357 1358 /** 1359 * The bullet type. 1360 */ 1361 private String type; 1362 1363 /** 1364 * Package-private constructor. 1365 * 1366 * @param as - AttributeSet for painter 1367 */ 1368 ListPainter(AttributeSet as, StyleSheet ss) 1369 { 1370 attributes = as; 1371 styleSheet = ss; 1372 type = (String) as.getAttribute(CSS.Attribute.LIST_STYLE_TYPE); 1373 } 1374 1375 /** 1376 * Cached rectangle re-used in the paint method below. 1377 */ 1378 private final Rectangle tmpRect = new Rectangle(); 1379 1380 /** 1381 * Paints the CSS list decoration according to the attributes given. 1382 * 1383 * @param g - the graphics configuration 1384 * @param x - the x coordinate 1385 * @param y - the y coordinate 1386 * @param w - the width of the allocated area 1387 * @param h - the height of the allocated area 1388 * @param v - the view making the request 1389 * @param item - the list item to be painted >=0. 1390 */ 1391 public void paint(Graphics g, float x, float y, float w, float h, View v, 1392 int item) 1393 { 1394 // FIXME: This is a very simplistic list rendering. We still need 1395 // to implement different bullet types (see type field) and custom 1396 // bullets via images. 1397 View itemView = v.getView(item); 1398 AttributeSet viewAtts = itemView.getAttributes(); 1399 Object tag = viewAtts.getAttribute(StyleConstants.NameAttribute); 1400 // Only paint something here when the child view is an LI tag 1401 // and the calling view is some of the list tags then). 1402 if (tag != null && tag == HTML.Tag.LI) 1403 { 1404 g.setColor(Color.BLACK); 1405 int centerX = (int) (x - 12); 1406 int centerY = -1; 1407 // For paragraphs (almost all cases) center bullet vertically 1408 // in the middle of the first line. 1409 tmpRect.setBounds((int) x, (int) y, (int) w, (int) h); 1410 if (itemView.getViewCount() > 0) 1411 { 1412 View v1 = itemView.getView(0); 1413 if (v1 instanceof ParagraphView && v1.getViewCount() > 0) 1414 { 1415 Shape a1 = itemView.getChildAllocation(0, tmpRect); 1416 Rectangle r1 = a1 instanceof Rectangle ? (Rectangle) a1 1417 : a1.getBounds(); 1418 ParagraphView par = (ParagraphView) v1; 1419 Shape a = par.getChildAllocation(0, r1); 1420 if (a != null) 1421 { 1422 Rectangle r = a instanceof Rectangle ? (Rectangle) a 1423 : a.getBounds(); 1424 centerY = (int) (r.height / 2 + r.y); 1425 } 1426 } 1427 } 1428 if (centerY == -1) 1429 { 1430 centerY =(int) (h / 2 + y); 1431 } 1432 g.fillOval(centerX - 3, centerY - 3, 6, 6); 1433 } 1434 } 1435 } 1436 1437 /** 1438 * Converts an AttributeSet to a Map. This is used for CSS resolving. 1439 * 1440 * @param atts the attributes to convert 1441 * 1442 * @return the converted map 1443 */ 1444 private Map attributeSetToMap(AttributeSet atts) 1445 { 1446 HashMap<String,String> map = new HashMap<String,String>(); 1447 Enumeration<?> keys = atts.getAttributeNames(); 1448 while (keys.hasMoreElements()) 1449 { 1450 Object key = keys.nextElement(); 1451 Object value = atts.getAttribute(key); 1452 map.put(key.toString(), value.toString()); 1453 } 1454 return map; 1455 } 1456 }