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