Source for javax.swing.text.html.StyleSheet

   1: /* StyleSheet.java -- 
   2:    Copyright (C) 2005 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.text.html;
  40: 
  41: import gnu.javax.swing.text.html.css.BorderWidth;
  42: import gnu.javax.swing.text.html.css.CSSColor;
  43: import gnu.javax.swing.text.html.css.CSSParser;
  44: import gnu.javax.swing.text.html.css.CSSParserCallback;
  45: import gnu.javax.swing.text.html.css.FontSize;
  46: import gnu.javax.swing.text.html.css.FontStyle;
  47: import gnu.javax.swing.text.html.css.FontWeight;
  48: import gnu.javax.swing.text.html.css.Length;
  49: import gnu.javax.swing.text.html.css.Selector;
  50: 
  51: import java.awt.Color;
  52: import java.awt.Font;
  53: import java.awt.Graphics;
  54: import java.awt.Rectangle;
  55: import java.awt.Shape;
  56: import java.awt.font.FontRenderContext;
  57: import java.awt.geom.Rectangle2D;
  58: import java.io.BufferedReader;
  59: import java.io.IOException;
  60: import java.io.InputStream;
  61: import java.io.InputStreamReader;
  62: import java.io.Reader;
  63: import java.io.Serializable;
  64: import java.io.StringReader;
  65: import java.net.URL;
  66: import java.util.ArrayList;
  67: import java.util.Collections;
  68: import java.util.Enumeration;
  69: import java.util.HashMap;
  70: import java.util.Iterator;
  71: import java.util.List;
  72: import java.util.Map;
  73: 
  74: import javax.swing.border.Border;
  75: import javax.swing.event.ChangeListener;
  76: import javax.swing.text.AttributeSet;
  77: import javax.swing.text.Element;
  78: import javax.swing.text.MutableAttributeSet;
  79: import javax.swing.text.SimpleAttributeSet;
  80: import javax.swing.text.Style;
  81: import javax.swing.text.StyleConstants;
  82: import javax.swing.text.StyleContext;
  83: import javax.swing.text.View;
  84: 
  85: 
  86: /**
  87:  * This class adds support for defining the visual characteristics of HTML views
  88:  * being rendered. This enables views to be customized by a look-and-feel, mulitple
  89:  * views over the same model can be rendered differently. Each EditorPane has its 
  90:  * own StyleSheet, but by default one sheet will be shared by all of the HTMLEditorKit
  91:  * instances. An HTMLDocument can also have a StyleSheet, which holds specific CSS
  92:  * specs. 
  93:  * 
  94:  *  In order for Views to store less state and therefore be more lightweight, 
  95:  *  the StyleSheet can act as a factory for painters that handle some of the 
  96:  *  rendering tasks. Since the StyleSheet may be used by views over multiple
  97:  *  documents the HTML attributes don't effect the selector being used.
  98:  *  
  99:  *  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: }