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