1 /*
   2  * Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.text.html;
  26 
  27 import java.awt.Color;
  28 import java.awt.Font;
  29 import java.awt.HeadlessException;
  30 import java.awt.Image;
  31 import java.awt.Toolkit;
  32 import java.io.IOException;
  33 import java.io.ObjectInputStream;
  34 import java.io.Serializable;
  35 import java.net.MalformedURLException;
  36 import java.net.URL;
  37 import java.util.Enumeration;
  38 import java.util.Hashtable;
  39 import java.util.Vector;
  40 
  41 import javax.swing.ImageIcon;
  42 import javax.swing.SizeRequirements;
  43 import javax.swing.text.AttributeSet;
  44 import javax.swing.text.Element;
  45 import javax.swing.text.MutableAttributeSet;
  46 import javax.swing.text.SimpleAttributeSet;
  47 import javax.swing.text.StyleConstants;
  48 import javax.swing.text.StyleContext;
  49 import javax.swing.text.View;
  50 
  51 /**
  52  * Defines a set of
  53  * <a href="http://www.w3.org/TR/REC-CSS1">CSS attributes</a>
  54  * as a typesafe enumeration.  The HTML View implementations use
  55  * CSS attributes to determine how they will render. This also defines
  56  * methods to map between CSS/HTML/StyleConstants. Any shorthand
  57  * properties, such as font, are mapped to the intrinsic properties.
  58  * <p>The following describes the CSS properties that are supported by the
  59  * rendering engine:
  60  * <ul><li>font-family
  61  *   <li>font-style
  62  *   <li>font-size (supports relative units)
  63  *   <li>font-weight
  64  *   <li>font
  65  *   <li>color
  66  *   <li>background-color (with the exception of transparent)
  67  *   <li>background-image
  68  *   <li>background-repeat
  69  *   <li>background-position
  70  *   <li>background
  71  *   <li>text-decoration (with the exception of blink and overline)
  72  *   <li>vertical-align (only sup and super)
  73  *   <li>text-align (justify is treated as center)
  74  *   <li>margin-top
  75  *   <li>margin-right
  76  *   <li>margin-bottom
  77  *   <li>margin-left
  78  *   <li>margin
  79  *   <li>padding-top
  80  *   <li>padding-right
  81  *   <li>padding-bottom
  82  *   <li>padding-left
  83  *   <li>padding
  84  *   <li>border-top-style
  85  *   <li>border-right-style
  86  *   <li>border-bottom-style
  87  *   <li>border-left-style
  88  *   <li>border-style (only supports inset, outset and none)
  89  *   <li>border-top-color
  90  *   <li>border-right-color
  91  *   <li>border-bottom-color
  92  *   <li>border-left-color
  93  *   <li>border-color
  94  *   <li>list-style-image
  95  *   <li>list-style-type
  96  *   <li>list-style-position
  97  * </ul>
  98  * The following are modeled, but currently not rendered.
  99  * <ul><li>font-variant
 100  *   <li>background-attachment (background always treated as scroll)
 101  *   <li>word-spacing
 102  *   <li>letter-spacing
 103  *   <li>text-indent
 104  *   <li>text-transform
 105  *   <li>line-height
 106  *   <li>border-top-width (this is used to indicate if a border should be used)
 107  *   <li>border-right-width
 108  *   <li>border-bottom-width
 109  *   <li>border-left-width
 110  *   <li>border-width
 111  *   <li>border-top
 112  *   <li>border-right
 113  *   <li>border-bottom
 114  *   <li>border-left
 115  *   <li>border
 116  *   <li>width
 117  *   <li>height
 118  *   <li>float
 119  *   <li>clear
 120  *   <li>display
 121  *   <li>white-space
 122  *   <li>list-style
 123  * </ul>
 124  * <p><b>Note: for the time being we do not fully support relative units,
 125  * unless noted, so that
 126  * p { margin-top: 10% } will be treated as if no margin-top was specified.</b>
 127  *
 128  * @author  Timothy Prinzing
 129  * @author  Scott Violet
 130  * @see StyleSheet
 131  */
 132 @SuppressWarnings("serial") // Same-version serialization only
 133 public class CSS implements Serializable {
 134 
 135     /**
 136      * Definitions to be used as a key on AttributeSet's
 137      * that might hold CSS attributes.  Since this is a
 138      * closed set (i.e. defined exactly by the specification),
 139      * it is final and cannot be extended.
 140      */
 141     public static final class Attribute {
 142 
 143         private Attribute(String name, String defaultValue, boolean inherited) {
 144             this.name = name;
 145             this.defaultValue = defaultValue;
 146             this.inherited = inherited;
 147         }
 148 
 149         /**
 150          * The string representation of the attribute.  This
 151          * should exactly match the string specified in the
 152          * CSS specification.
 153          */
 154         public String toString() {
 155             return name;
 156         }
 157 
 158         /**
 159          * Fetch the default value for the attribute.
 160          * If there is no default value (such as for
 161          * composite attributes), null will be returned.
 162          *
 163          * @return default value for the attribute
 164          */
 165         public String getDefaultValue() {
 166             return defaultValue;
 167         }
 168 
 169         /**
 170          * Indicates if the attribute should be inherited
 171          * from the parent or not.
 172          *
 173          * @return true if the attribute should be inherited from the parent
 174          */
 175         public boolean isInherited() {
 176             return inherited;
 177         }
 178 
 179         private String name;
 180         private String defaultValue;
 181         private boolean inherited;
 182 
 183 
 184         /**
 185          * CSS attribute "background".
 186          */
 187         public static final Attribute BACKGROUND =
 188             new Attribute("background", null, false);
 189 
 190         /**
 191          * CSS attribute "background-attachment".
 192          */
 193         public static final Attribute BACKGROUND_ATTACHMENT =
 194             new Attribute("background-attachment", "scroll", false);
 195 
 196         /**
 197          * CSS attribute "background-color".
 198          */
 199         public static final Attribute BACKGROUND_COLOR =
 200             new Attribute("background-color", "transparent", false);
 201 
 202         /**
 203          * CSS attribute "background-image".
 204          */
 205         public static final Attribute BACKGROUND_IMAGE =
 206             new Attribute("background-image", "none", false);
 207 
 208         /**
 209          * CSS attribute "background-position".
 210          */
 211         public static final Attribute BACKGROUND_POSITION =
 212             new Attribute("background-position", null, false);
 213 
 214         /**
 215          * CSS attribute "background-repeat".
 216          */
 217         public static final Attribute BACKGROUND_REPEAT =
 218             new Attribute("background-repeat", "repeat", false);
 219 
 220         /**
 221          * CSS attribute "border".
 222          */
 223         public static final Attribute BORDER =
 224             new Attribute("border", null, false);
 225 
 226         /**
 227          * CSS attribute "border-bottom".
 228          */
 229         public static final Attribute BORDER_BOTTOM =
 230             new Attribute("border-bottom", null, false);
 231 
 232         /**
 233          * CSS attribute "border-bottom-color".
 234          */
 235         public static final Attribute BORDER_BOTTOM_COLOR =
 236             new Attribute("border-bottom-color", null, false);
 237 
 238         /**
 239          * CSS attribute "border-bottom-style".
 240          */
 241         public static final Attribute BORDER_BOTTOM_STYLE =
 242             new Attribute("border-bottom-style", "none", false);
 243 
 244         /**
 245          * CSS attribute "border-bottom-width".
 246          */
 247         public static final Attribute BORDER_BOTTOM_WIDTH =
 248             new Attribute("border-bottom-width", "medium", false);
 249 
 250         /**
 251          * CSS attribute "border-color".
 252          */
 253         public static final Attribute BORDER_COLOR =
 254             new Attribute("border-color", null, false);
 255 
 256         /**
 257          * CSS attribute "border-left".
 258          */
 259         public static final Attribute BORDER_LEFT =
 260             new Attribute("border-left", null, false);
 261 
 262         /**
 263          * CSS attribute "margin-right".
 264          */
 265         public static final Attribute BORDER_LEFT_COLOR =
 266             new Attribute("border-left-color", null, false);
 267 
 268         /**
 269          * CSS attribute "border-left-style".
 270          */
 271         public static final Attribute BORDER_LEFT_STYLE =
 272             new Attribute("border-left-style", "none", false);
 273 
 274         /**
 275          * CSS attribute "border-left-width".
 276          */
 277         public static final Attribute BORDER_LEFT_WIDTH =
 278             new Attribute("border-left-width", "medium", false);
 279 
 280         /**
 281          * CSS attribute "border-right".
 282          */
 283         public static final Attribute BORDER_RIGHT =
 284             new Attribute("border-right", null, false);
 285 
 286         /**
 287          * CSS attribute "border-right-color".
 288          */
 289         public static final Attribute BORDER_RIGHT_COLOR =
 290             new Attribute("border-right-color", null, false);
 291 
 292         /**
 293          * CSS attribute "border-right-style".
 294          */
 295         public static final Attribute BORDER_RIGHT_STYLE =
 296             new Attribute("border-right-style", "none", false);
 297 
 298         /**
 299          * CSS attribute "border-right-width".
 300          */
 301         public static final Attribute BORDER_RIGHT_WIDTH =
 302             new Attribute("border-right-width", "medium", false);
 303 
 304         /**
 305          * CSS attribute "border-style".
 306          */
 307         public static final Attribute BORDER_STYLE =
 308             new Attribute("border-style", "none", false);
 309 
 310         /**
 311          * CSS attribute "border-top".
 312          */
 313         public static final Attribute BORDER_TOP =
 314             new Attribute("border-top", null, false);
 315 
 316         /**
 317          * CSS attribute "border-top-color".
 318          */
 319         public static final Attribute BORDER_TOP_COLOR =
 320             new Attribute("border-top-color", null, false);
 321 
 322         /**
 323          * CSS attribute "border-top-style".
 324          */
 325         public static final Attribute BORDER_TOP_STYLE =
 326             new Attribute("border-top-style", "none", false);
 327 
 328         /**
 329          * CSS attribute "border-top-width".
 330          */
 331         public static final Attribute BORDER_TOP_WIDTH =
 332             new Attribute("border-top-width", "medium", false);
 333 
 334         /**
 335          * CSS attribute "border-width".
 336          */
 337         public static final Attribute BORDER_WIDTH =
 338             new Attribute("border-width", "medium", false);
 339 
 340         /**
 341          * CSS attribute "clear".
 342          */
 343         public static final Attribute CLEAR =
 344             new Attribute("clear", "none", false);
 345 
 346         /**
 347          * CSS attribute "color".
 348          */
 349         public static final Attribute COLOR =
 350             new Attribute("color", "black", true);
 351 
 352         /**
 353          * CSS attribute "display".
 354          */
 355         public static final Attribute DISPLAY =
 356             new Attribute("display", "block", false);
 357 
 358         /**
 359          * CSS attribute "float".
 360          */
 361         public static final Attribute FLOAT =
 362             new Attribute("float", "none", false);
 363 
 364         /**
 365          * CSS attribute "font".
 366          */
 367         public static final Attribute FONT =
 368             new Attribute("font", null, true);
 369 
 370         /**
 371          * CSS attribute "font-family".
 372          */
 373         public static final Attribute FONT_FAMILY =
 374             new Attribute("font-family", null, true);
 375 
 376         /**
 377          * CSS attribute "font-size".
 378          */
 379         public static final Attribute FONT_SIZE =
 380             new Attribute("font-size", "medium", true);
 381 
 382         /**
 383          * CSS attribute "font-style".
 384          */
 385         public static final Attribute FONT_STYLE =
 386             new Attribute("font-style", "normal", true);
 387 
 388         /**
 389          * CSS attribute "font-variant".
 390          */
 391         public static final Attribute FONT_VARIANT =
 392             new Attribute("font-variant", "normal", true);
 393 
 394         /**
 395          * CSS attribute "font-weight".
 396          */
 397         public static final Attribute FONT_WEIGHT =
 398             new Attribute("font-weight", "normal", true);
 399 
 400         /**
 401          * CSS attribute "height".
 402          */
 403         public static final Attribute HEIGHT =
 404             new Attribute("height", "auto", false);
 405 
 406         /**
 407          * CSS attribute "letter-spacing".
 408          */
 409         public static final Attribute LETTER_SPACING =
 410             new Attribute("letter-spacing", "normal", true);
 411 
 412         /**
 413          * CSS attribute "line-height".
 414          */
 415         public static final Attribute LINE_HEIGHT =
 416             new Attribute("line-height", "normal", true);
 417 
 418         /**
 419          * CSS attribute "list-style".
 420          */
 421         public static final Attribute LIST_STYLE =
 422             new Attribute("list-style", null, true);
 423 
 424         /**
 425          * CSS attribute "list-style-image".
 426          */
 427         public static final Attribute LIST_STYLE_IMAGE =
 428             new Attribute("list-style-image", "none", true);
 429 
 430         /**
 431          * CSS attribute "list-style-position".
 432          */
 433         public static final Attribute LIST_STYLE_POSITION =
 434             new Attribute("list-style-position", "outside", true);
 435 
 436         /**
 437          * CSS attribute "list-style-type".
 438          */
 439         public static final Attribute LIST_STYLE_TYPE =
 440             new Attribute("list-style-type", "disc", true);
 441 
 442         /**
 443          * CSS attribute "margin".
 444          */
 445         public static final Attribute MARGIN =
 446             new Attribute("margin", null, false);
 447 
 448         /**
 449          * CSS attribute "margin-bottom".
 450          */
 451         public static final Attribute MARGIN_BOTTOM =
 452             new Attribute("margin-bottom", "0", false);
 453 
 454         /**
 455          * CSS attribute "margin-left".
 456          */
 457         public static final Attribute MARGIN_LEFT =
 458             new Attribute("margin-left", "0", false);
 459 
 460         /**
 461          * CSS attribute "margin-right".
 462          */
 463         public static final Attribute MARGIN_RIGHT =
 464             new Attribute("margin-right", "0", false);
 465 
 466         /*
 467          * made up css attributes to describe orientation depended
 468          * margins. used for <dir>, <menu>, <ul> etc. see
 469          * 5088268 for more details
 470          */
 471         static final Attribute MARGIN_LEFT_LTR =
 472             new Attribute("margin-left-ltr",
 473                           Integer.toString(Integer.MIN_VALUE), false);
 474 
 475         static final Attribute MARGIN_LEFT_RTL =
 476             new Attribute("margin-left-rtl",
 477                           Integer.toString(Integer.MIN_VALUE), false);
 478 
 479         static final Attribute MARGIN_RIGHT_LTR =
 480             new Attribute("margin-right-ltr",
 481                           Integer.toString(Integer.MIN_VALUE), false);
 482 
 483         static final Attribute MARGIN_RIGHT_RTL =
 484             new Attribute("margin-right-rtl",
 485                           Integer.toString(Integer.MIN_VALUE), false);
 486 
 487 
 488         /**
 489          * CSS attribute "margin-top".
 490          */
 491         public static final Attribute MARGIN_TOP =
 492             new Attribute("margin-top", "0", false);
 493 
 494         /**
 495          * CSS attribute "padding".
 496          */
 497         public static final Attribute PADDING =
 498             new Attribute("padding", null, false);
 499 
 500         /**
 501          * CSS attribute "padding-bottom".
 502          */
 503         public static final Attribute PADDING_BOTTOM =
 504             new Attribute("padding-bottom", "0", false);
 505 
 506         /**
 507          * CSS attribute "padding-left".
 508          */
 509         public static final Attribute PADDING_LEFT =
 510             new Attribute("padding-left", "0", false);
 511 
 512         /**
 513          * CSS attribute "padding-right".
 514          */
 515         public static final Attribute PADDING_RIGHT =
 516             new Attribute("padding-right", "0", false);
 517 
 518         /**
 519          * CSS attribute "padding-top".
 520          */
 521         public static final Attribute PADDING_TOP =
 522             new Attribute("padding-top", "0", false);
 523 
 524         /**
 525          * CSS attribute "text-align".
 526          */
 527         public static final Attribute TEXT_ALIGN =
 528             new Attribute("text-align", null, true);
 529 
 530         /**
 531          * CSS attribute "text-decoration".
 532          */
 533         public static final Attribute TEXT_DECORATION =
 534             new Attribute("text-decoration", "none", true);
 535 
 536         /**
 537          * CSS attribute "text-indent".
 538          */
 539         public static final Attribute TEXT_INDENT =
 540             new Attribute("text-indent", "0", true);
 541 
 542         /**
 543          * CSS attribute "text-transform".
 544          */
 545         public static final Attribute TEXT_TRANSFORM =
 546             new Attribute("text-transform", "none", true);
 547 
 548         /**
 549          * CSS attribute "vertical-align".
 550          */
 551         public static final Attribute VERTICAL_ALIGN =
 552             new Attribute("vertical-align", "baseline", false);
 553 
 554         /**
 555          * CSS attribute "word-spacing".
 556          */
 557         public static final Attribute WORD_SPACING =
 558             new Attribute("word-spacing", "normal", true);
 559 
 560         /**
 561          * CSS attribute "white-space".
 562          */
 563         public static final Attribute WHITE_SPACE =
 564             new Attribute("white-space", "normal", true);
 565 
 566         /**
 567          * CSS attribute "width".
 568          */
 569         public static final Attribute WIDTH =
 570             new Attribute("width", "auto", false);
 571 
 572         /*public*/ static final Attribute BORDER_SPACING =
 573             new Attribute("border-spacing", "0", true);
 574 
 575         /*public*/ static final Attribute CAPTION_SIDE =
 576             new Attribute("caption-side", "left", true);
 577 
 578         // All possible CSS attribute keys.
 579         static final Attribute[] allAttributes = {
 580             BACKGROUND, BACKGROUND_ATTACHMENT, BACKGROUND_COLOR,
 581             BACKGROUND_IMAGE, BACKGROUND_POSITION, BACKGROUND_REPEAT,
 582             BORDER, BORDER_BOTTOM, BORDER_BOTTOM_WIDTH, BORDER_COLOR,
 583             BORDER_LEFT, BORDER_LEFT_WIDTH, BORDER_RIGHT, BORDER_RIGHT_WIDTH,
 584             BORDER_STYLE, BORDER_TOP, BORDER_TOP_WIDTH, BORDER_WIDTH,
 585             BORDER_TOP_STYLE, BORDER_RIGHT_STYLE, BORDER_BOTTOM_STYLE,
 586             BORDER_LEFT_STYLE,
 587             BORDER_TOP_COLOR, BORDER_RIGHT_COLOR, BORDER_BOTTOM_COLOR,
 588             BORDER_LEFT_COLOR,
 589             CLEAR, COLOR, DISPLAY, FLOAT, FONT, FONT_FAMILY, FONT_SIZE,
 590             FONT_STYLE, FONT_VARIANT, FONT_WEIGHT, HEIGHT, LETTER_SPACING,
 591             LINE_HEIGHT, LIST_STYLE, LIST_STYLE_IMAGE, LIST_STYLE_POSITION,
 592             LIST_STYLE_TYPE, MARGIN, MARGIN_BOTTOM, MARGIN_LEFT, MARGIN_RIGHT,
 593             MARGIN_TOP, PADDING, PADDING_BOTTOM, PADDING_LEFT, PADDING_RIGHT,
 594             PADDING_TOP, TEXT_ALIGN, TEXT_DECORATION, TEXT_INDENT, TEXT_TRANSFORM,
 595             VERTICAL_ALIGN, WORD_SPACING, WHITE_SPACE, WIDTH,
 596             BORDER_SPACING, CAPTION_SIDE,
 597             MARGIN_LEFT_LTR, MARGIN_LEFT_RTL, MARGIN_RIGHT_LTR, MARGIN_RIGHT_RTL
 598         };
 599 
 600         private static final Attribute[] ALL_MARGINS =
 601                 { MARGIN_TOP, MARGIN_RIGHT, MARGIN_BOTTOM, MARGIN_LEFT };
 602         private static final Attribute[] ALL_PADDING =
 603                 { PADDING_TOP, PADDING_RIGHT, PADDING_BOTTOM, PADDING_LEFT };
 604         private static final Attribute[] ALL_BORDER_WIDTHS =
 605                 { BORDER_TOP_WIDTH, BORDER_RIGHT_WIDTH, BORDER_BOTTOM_WIDTH,
 606                   BORDER_LEFT_WIDTH };
 607         private static final Attribute[] ALL_BORDER_STYLES =
 608                 { BORDER_TOP_STYLE, BORDER_RIGHT_STYLE, BORDER_BOTTOM_STYLE,
 609                   BORDER_LEFT_STYLE };
 610         private static final Attribute[] ALL_BORDER_COLORS =
 611                 { BORDER_TOP_COLOR, BORDER_RIGHT_COLOR, BORDER_BOTTOM_COLOR,
 612                   BORDER_LEFT_COLOR };
 613 
 614     }
 615 
 616     static final class Value {
 617 
 618         private Value(String name) {
 619             this.name = name;
 620         }
 621 
 622         /**
 623          * The string representation of the attribute.  This
 624          * should exactly match the string specified in the
 625          * CSS specification.
 626          */
 627         public String toString() {
 628             return name;
 629         }
 630 
 631         static final Value INHERITED = new Value("inherited");
 632         static final Value NONE = new Value("none");
 633         static final Value HIDDEN = new Value("hidden");
 634         static final Value DOTTED = new Value("dotted");
 635         static final Value DASHED = new Value("dashed");
 636         static final Value SOLID = new Value("solid");
 637         static final Value DOUBLE = new Value("double");
 638         static final Value GROOVE = new Value("groove");
 639         static final Value RIDGE = new Value("ridge");
 640         static final Value INSET = new Value("inset");
 641         static final Value OUTSET = new Value("outset");
 642         // Lists.
 643         static final Value DISC = new Value("disc");
 644         static final Value CIRCLE = new Value("circle");
 645         static final Value SQUARE = new Value("square");
 646         static final Value DECIMAL = new Value("decimal");
 647         static final Value LOWER_ROMAN = new Value("lower-roman");
 648         static final Value UPPER_ROMAN = new Value("upper-roman");
 649         static final Value LOWER_ALPHA = new Value("lower-alpha");
 650         static final Value UPPER_ALPHA = new Value("upper-alpha");
 651         // background-repeat
 652         static final Value BACKGROUND_NO_REPEAT = new Value("no-repeat");
 653         static final Value BACKGROUND_REPEAT = new Value("repeat");
 654         static final Value BACKGROUND_REPEAT_X = new Value("repeat-x");
 655         static final Value BACKGROUND_REPEAT_Y = new Value("repeat-y");
 656         // background-attachment
 657         static final Value BACKGROUND_SCROLL = new Value("scroll");
 658         static final Value BACKGROUND_FIXED = new Value("fixed");
 659 
 660         private String name;
 661 
 662         static final Value[] allValues = {
 663             INHERITED, NONE, DOTTED, DASHED, SOLID, DOUBLE, GROOVE,
 664             RIDGE, INSET, OUTSET, DISC, CIRCLE, SQUARE, DECIMAL,
 665             LOWER_ROMAN, UPPER_ROMAN, LOWER_ALPHA, UPPER_ALPHA,
 666             BACKGROUND_NO_REPEAT, BACKGROUND_REPEAT,
 667             BACKGROUND_REPEAT_X, BACKGROUND_REPEAT_Y,
 668             BACKGROUND_FIXED, BACKGROUND_FIXED
 669         };
 670     }
 671 
 672     /**
 673      * Constructs a CSS object.
 674      */
 675     public CSS() {
 676         baseFontSize = baseFontSizeIndex + 1;
 677         // setup the css conversion table
 678         valueConvertor = new Hashtable<Object, Object>();
 679         valueConvertor.put(CSS.Attribute.FONT_SIZE, new FontSize());
 680         valueConvertor.put(CSS.Attribute.FONT_FAMILY, new FontFamily());
 681         valueConvertor.put(CSS.Attribute.FONT_WEIGHT, new FontWeight());
 682         Object bs = new BorderStyle();
 683         valueConvertor.put(CSS.Attribute.BORDER_TOP_STYLE, bs);
 684         valueConvertor.put(CSS.Attribute.BORDER_RIGHT_STYLE, bs);
 685         valueConvertor.put(CSS.Attribute.BORDER_BOTTOM_STYLE, bs);
 686         valueConvertor.put(CSS.Attribute.BORDER_LEFT_STYLE, bs);
 687         Object cv = new ColorValue();
 688         valueConvertor.put(CSS.Attribute.COLOR, cv);
 689         valueConvertor.put(CSS.Attribute.BACKGROUND_COLOR, cv);
 690         valueConvertor.put(CSS.Attribute.BORDER_TOP_COLOR, cv);
 691         valueConvertor.put(CSS.Attribute.BORDER_RIGHT_COLOR, cv);
 692         valueConvertor.put(CSS.Attribute.BORDER_BOTTOM_COLOR, cv);
 693         valueConvertor.put(CSS.Attribute.BORDER_LEFT_COLOR, cv);
 694         Object lv = new LengthValue();
 695         valueConvertor.put(CSS.Attribute.MARGIN_TOP, lv);
 696         valueConvertor.put(CSS.Attribute.MARGIN_BOTTOM, lv);
 697         valueConvertor.put(CSS.Attribute.MARGIN_LEFT, lv);
 698         valueConvertor.put(CSS.Attribute.MARGIN_LEFT_LTR, lv);
 699         valueConvertor.put(CSS.Attribute.MARGIN_LEFT_RTL, lv);
 700         valueConvertor.put(CSS.Attribute.MARGIN_RIGHT, lv);
 701         valueConvertor.put(CSS.Attribute.MARGIN_RIGHT_LTR, lv);
 702         valueConvertor.put(CSS.Attribute.MARGIN_RIGHT_RTL, lv);
 703         valueConvertor.put(CSS.Attribute.PADDING_TOP, lv);
 704         valueConvertor.put(CSS.Attribute.PADDING_BOTTOM, lv);
 705         valueConvertor.put(CSS.Attribute.PADDING_LEFT, lv);
 706         valueConvertor.put(CSS.Attribute.PADDING_RIGHT, lv);
 707         Object bv = new BorderWidthValue(null, 0);
 708         valueConvertor.put(CSS.Attribute.BORDER_TOP_WIDTH, bv);
 709         valueConvertor.put(CSS.Attribute.BORDER_BOTTOM_WIDTH, bv);
 710         valueConvertor.put(CSS.Attribute.BORDER_LEFT_WIDTH, bv);
 711         valueConvertor.put(CSS.Attribute.BORDER_RIGHT_WIDTH, bv);
 712         Object nlv = new LengthValue(true);
 713         valueConvertor.put(CSS.Attribute.TEXT_INDENT, nlv);
 714         valueConvertor.put(CSS.Attribute.WIDTH, lv);
 715         valueConvertor.put(CSS.Attribute.HEIGHT, lv);
 716         valueConvertor.put(CSS.Attribute.BORDER_SPACING, lv);
 717         Object sv = new StringValue();
 718         valueConvertor.put(CSS.Attribute.FONT_STYLE, sv);
 719         valueConvertor.put(CSS.Attribute.TEXT_DECORATION, sv);
 720         valueConvertor.put(CSS.Attribute.TEXT_ALIGN, sv);
 721         valueConvertor.put(CSS.Attribute.VERTICAL_ALIGN, sv);
 722         Object valueMapper = new CssValueMapper();
 723         valueConvertor.put(CSS.Attribute.LIST_STYLE_TYPE,
 724                            valueMapper);
 725         valueConvertor.put(CSS.Attribute.BACKGROUND_IMAGE,
 726                            new BackgroundImage());
 727         valueConvertor.put(CSS.Attribute.BACKGROUND_POSITION,
 728                            new BackgroundPosition());
 729         valueConvertor.put(CSS.Attribute.BACKGROUND_REPEAT,
 730                            valueMapper);
 731         valueConvertor.put(CSS.Attribute.BACKGROUND_ATTACHMENT,
 732                            valueMapper);
 733         Object generic = new CssValue();
 734         int n = CSS.Attribute.allAttributes.length;
 735         for (int i = 0; i < n; i++) {
 736             CSS.Attribute key = CSS.Attribute.allAttributes[i];
 737             if (valueConvertor.get(key) == null) {
 738                 valueConvertor.put(key, generic);
 739             }
 740         }
 741     }
 742 
 743     /**
 744      * Sets the base font size. <code>sz</code> is a CSS value, and is
 745      * not necessarily the point size. Use getPointSize to determine the
 746      * point size corresponding to <code>sz</code>.
 747      */
 748     void setBaseFontSize(int sz) {
 749         if (sz < 1)
 750           baseFontSize = 0;
 751         else if (sz > 7)
 752           baseFontSize = 7;
 753         else
 754           baseFontSize = sz;
 755     }
 756 
 757     /**
 758      * Sets the base font size from the passed in string.
 759      */
 760     void setBaseFontSize(String size) {
 761         int relSize, absSize, diff;
 762 
 763         if (size != null) {
 764             if (size.startsWith("+")) {
 765                 relSize = Integer.valueOf(size.substring(1)).intValue();
 766                 setBaseFontSize(baseFontSize + relSize);
 767             } else if (size.startsWith("-")) {
 768                 relSize = -Integer.valueOf(size.substring(1)).intValue();
 769                 setBaseFontSize(baseFontSize + relSize);
 770             } else {
 771                 setBaseFontSize(Integer.valueOf(size).intValue());
 772             }
 773         }
 774     }
 775 
 776     /**
 777      * Returns the base font size.
 778      */
 779     int getBaseFontSize() {
 780         return baseFontSize;
 781     }
 782 
 783     /**
 784      * Parses the CSS property <code>key</code> with value
 785      * <code>value</code> placing the result in <code>att</code>.
 786      */
 787     void addInternalCSSValue(MutableAttributeSet attr,
 788                              CSS.Attribute key, String value) {
 789         if (key == CSS.Attribute.FONT) {
 790             ShorthandFontParser.parseShorthandFont(this, value, attr);
 791         }
 792         else if (key == CSS.Attribute.BACKGROUND) {
 793             ShorthandBackgroundParser.parseShorthandBackground
 794                                (this, value, attr);
 795         }
 796         else if (key == CSS.Attribute.MARGIN) {
 797             ShorthandMarginParser.parseShorthandMargin(this, value, attr,
 798                                            CSS.Attribute.ALL_MARGINS);
 799         }
 800         else if (key == CSS.Attribute.PADDING) {
 801             ShorthandMarginParser.parseShorthandMargin(this, value, attr,
 802                                            CSS.Attribute.ALL_PADDING);
 803         }
 804         else if (key == CSS.Attribute.BORDER_WIDTH) {
 805             ShorthandMarginParser.parseShorthandMargin(this, value, attr,
 806                                            CSS.Attribute.ALL_BORDER_WIDTHS);
 807         }
 808         else if (key == CSS.Attribute.BORDER_COLOR) {
 809             ShorthandMarginParser.parseShorthandMargin(this, value, attr,
 810                                             CSS.Attribute.ALL_BORDER_COLORS);
 811         }
 812         else if (key == CSS.Attribute.BORDER_STYLE) {
 813             ShorthandMarginParser.parseShorthandMargin(this, value, attr,
 814                                             CSS.Attribute.ALL_BORDER_STYLES);
 815         }
 816         else if ((key == CSS.Attribute.BORDER) ||
 817                    (key == CSS.Attribute.BORDER_TOP) ||
 818                    (key == CSS.Attribute.BORDER_RIGHT) ||
 819                    (key == CSS.Attribute.BORDER_BOTTOM) ||
 820                    (key == CSS.Attribute.BORDER_LEFT)) {
 821             ShorthandBorderParser.parseShorthandBorder(attr, key, value);
 822         }
 823         else {
 824             Object iValue = getInternalCSSValue(key, value);
 825             if (iValue != null) {
 826                 attr.addAttribute(key, iValue);
 827             }
 828         }
 829     }
 830 
 831     /**
 832      * Gets the internal CSS representation of <code>value</code> which is
 833      * a CSS value of the CSS attribute named <code>key</code>. The receiver
 834      * should not modify <code>value</code>, and the first <code>count</code>
 835      * strings are valid.
 836      */
 837     Object getInternalCSSValue(CSS.Attribute key, String value) {
 838         CssValue conv = (CssValue) valueConvertor.get(key);
 839         Object r = conv.parseCssValue(value);
 840         return r != null ? r : conv.parseCssValue(key.getDefaultValue());
 841     }
 842 
 843     /**
 844      * Maps from a StyleConstants to a CSS Attribute.
 845      */
 846     Attribute styleConstantsKeyToCSSKey(StyleConstants sc) {
 847         return styleConstantToCssMap.get(sc);
 848     }
 849 
 850     /**
 851      * Maps from a StyleConstants value to a CSS value.
 852      */
 853     Object styleConstantsValueToCSSValue(StyleConstants sc,
 854                                          Object styleValue) {
 855         Attribute cssKey = styleConstantsKeyToCSSKey(sc);
 856         if (cssKey != null) {
 857             CssValue conv = (CssValue)valueConvertor.get(cssKey);
 858             return conv.fromStyleConstants(sc, styleValue);
 859         }
 860         return null;
 861     }
 862 
 863     /**
 864      * Converts the passed in CSS value to a StyleConstants value.
 865      * <code>key</code> identifies the CSS attribute being mapped.
 866      */
 867     Object cssValueToStyleConstantsValue(StyleConstants key, Object value) {
 868         if (value instanceof CssValue) {
 869             return ((CssValue)value).toStyleConstants(key, null);
 870         }
 871         return null;
 872     }
 873 
 874     /**
 875      * Returns the font for the values in the passed in AttributeSet.
 876      * It is assumed the keys will be CSS.Attribute keys.
 877      * <code>sc</code> is the StyleContext that will be messaged to get
 878      * the font once the size, name and style have been determined.
 879      */
 880     Font getFont(StyleContext sc, AttributeSet a, int defaultSize, StyleSheet ss) {
 881         ss = getStyleSheet(ss);
 882         int size = getFontSize(a, defaultSize, ss);
 883 
 884         /*
 885          * If the vertical alignment is set to either superscript or
 886          * subscript we reduce the font size by 2 points.
 887          */
 888         StringValue vAlignV = (StringValue)a.getAttribute
 889                               (CSS.Attribute.VERTICAL_ALIGN);
 890         if ((vAlignV != null)) {
 891             String vAlign = vAlignV.toString();
 892             if ((vAlign.indexOf("sup") >= 0) ||
 893                 (vAlign.indexOf("sub") >= 0)) {
 894                 size -= 2;
 895             }
 896         }
 897 
 898         FontFamily familyValue = (FontFamily)a.getAttribute
 899                                             (CSS.Attribute.FONT_FAMILY);
 900         String family = (familyValue != null) ? familyValue.getValue() :
 901                                   Font.SANS_SERIF;
 902         int style = Font.PLAIN;
 903         FontWeight weightValue = (FontWeight) a.getAttribute
 904                                   (CSS.Attribute.FONT_WEIGHT);
 905         if ((weightValue != null) && (weightValue.getValue() > 400)) {
 906             style |= Font.BOLD;
 907         }
 908         Object fs = a.getAttribute(CSS.Attribute.FONT_STYLE);
 909         if ((fs != null) && (fs.toString().indexOf("italic") >= 0)) {
 910             style |= Font.ITALIC;
 911         }
 912         if (family.equalsIgnoreCase("monospace")) {
 913             family = Font.MONOSPACED;
 914         }
 915         Font f = sc.getFont(family, style, size);
 916         if (f == null
 917             || (f.getFamily().equals(Font.DIALOG)
 918                 && ! family.equalsIgnoreCase(Font.DIALOG))) {
 919             family = Font.SANS_SERIF;
 920             f = sc.getFont(family, style, size);
 921         }
 922         return f;
 923     }
 924 
 925     static int getFontSize(AttributeSet attr, int defaultSize, StyleSheet ss) {
 926         // PENDING(prinz) this is a 1.1 based implementation, need to also
 927         // have a 1.2 version.
 928         FontSize sizeValue = (FontSize)attr.getAttribute(CSS.Attribute.
 929                                                          FONT_SIZE);
 930 
 931         return (sizeValue != null) ? sizeValue.getValue(attr, ss)
 932                                    : defaultSize;
 933     }
 934 
 935     /**
 936      * Takes a set of attributes and turn it into a color
 937      * specification.  This might be used to specify things
 938      * like brighter, more hue, etc.
 939      * This will return null if there is no value for <code>key</code>.
 940      *
 941      * @param key CSS.Attribute identifying where color is stored.
 942      * @param a the set of attributes
 943      * @return the color
 944      */
 945     Color getColor(AttributeSet a, CSS.Attribute key) {
 946         ColorValue cv = (ColorValue) a.getAttribute(key);
 947         if (cv != null) {
 948             return cv.getValue();
 949         }
 950         return null;
 951     }
 952 
 953     /**
 954      * Returns the size of a font from the passed in string.
 955      *
 956      * @param size CSS string describing font size
 957      */
 958     float getPointSize(String size, StyleSheet ss) {
 959         int relSize, absSize, diff, index;
 960         ss = getStyleSheet(ss);
 961         if (size != null) {
 962             if (size.startsWith("+")) {
 963                 relSize = Integer.valueOf(size.substring(1)).intValue();
 964                 return getPointSize(baseFontSize + relSize, ss);
 965             } else if (size.startsWith("-")) {
 966                 relSize = -Integer.valueOf(size.substring(1)).intValue();
 967                 return getPointSize(baseFontSize + relSize, ss);
 968             } else {
 969                 absSize = Integer.valueOf(size).intValue();
 970                 return getPointSize(absSize, ss);
 971             }
 972         }
 973         return 0;
 974     }
 975 
 976     /**
 977      * Returns the length of the attribute in <code>a</code> with
 978      * key <code>key</code>.
 979      */
 980     float getLength(AttributeSet a, CSS.Attribute key, StyleSheet ss) {
 981         ss = getStyleSheet(ss);
 982         LengthValue lv = (LengthValue) a.getAttribute(key);
 983         boolean isW3CLengthUnits = (ss == null) ? false : ss.isW3CLengthUnits();
 984         float len = (lv != null) ? lv.getValue(isW3CLengthUnits) : 0;
 985         return len;
 986     }
 987 
 988     /**
 989      * Convert a set of HTML attributes to an equivalent
 990      * set of CSS attributes.
 991      *
 992      * @param htmlAttrSet AttributeSet containing the HTML attributes.
 993      * @return AttributeSet containing the corresponding CSS attributes.
 994      *        The AttributeSet will be empty if there are no mapping
 995      *        CSS attributes.
 996      */
 997     AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) {
 998         MutableAttributeSet cssAttrSet = new SimpleAttributeSet();
 999         Element elem = (Element)htmlAttrSet;
1000         HTML.Tag tag = getHTMLTag(htmlAttrSet);
1001         if ((tag == HTML.Tag.TD) || (tag == HTML.Tag.TH)) {
1002             // translate border width into the cells, if it has non-zero value.
1003             AttributeSet tableAttr = elem.getParentElement().
1004                                      getParentElement().getAttributes();
1005 
1006             int borderWidth = getTableBorder(tableAttr);
1007             if (borderWidth > 0) {
1008                 // If table contains the BORDER attribute cells should have border width equals 1
1009                 translateAttribute(HTML.Attribute.BORDER, "1", cssAttrSet);
1010             }
1011             String pad = (String)tableAttr.getAttribute(HTML.Attribute.CELLPADDING);
1012             if (pad != null) {
1013                 LengthValue v =
1014                     (LengthValue)getInternalCSSValue(CSS.Attribute.PADDING_TOP, pad);
1015                 v.span = (v.span < 0) ? 0 : v.span;
1016                 cssAttrSet.addAttribute(CSS.Attribute.PADDING_TOP, v);
1017                 cssAttrSet.addAttribute(CSS.Attribute.PADDING_BOTTOM, v);
1018                 cssAttrSet.addAttribute(CSS.Attribute.PADDING_LEFT, v);
1019                 cssAttrSet.addAttribute(CSS.Attribute.PADDING_RIGHT, v);
1020             }
1021         }
1022         if (elem.isLeaf()) {
1023             translateEmbeddedAttributes(htmlAttrSet, cssAttrSet);
1024         } else {
1025             translateAttributes(tag, htmlAttrSet, cssAttrSet);
1026         }
1027         if (tag == HTML.Tag.CAPTION) {
1028             /*
1029              * Navigator uses ALIGN for caption placement and IE uses VALIGN.
1030              */
1031             Object v = htmlAttrSet.getAttribute(HTML.Attribute.ALIGN);
1032             if ((v != null) && (v.equals("top") || v.equals("bottom"))) {
1033                 cssAttrSet.addAttribute(CSS.Attribute.CAPTION_SIDE, v);
1034                 cssAttrSet.removeAttribute(CSS.Attribute.TEXT_ALIGN);
1035             } else {
1036                 v = htmlAttrSet.getAttribute(HTML.Attribute.VALIGN);
1037                 if (v != null) {
1038                     cssAttrSet.addAttribute(CSS.Attribute.CAPTION_SIDE, v);
1039                 }
1040             }
1041         }
1042         return cssAttrSet;
1043     }
1044 
1045     private static int getTableBorder(AttributeSet tableAttr) {
1046         String borderValue = (String) tableAttr.getAttribute(HTML.Attribute.BORDER);
1047 
1048         if (borderValue == HTML.NULL_ATTRIBUTE_VALUE || "".equals(borderValue)) {
1049             // Some browsers accept <TABLE BORDER> and <TABLE BORDER=""> with the same semantics as BORDER=1
1050             return 1;
1051         }
1052 
1053         try {
1054             return Integer.parseInt(borderValue);
1055         } catch (NumberFormatException e) {
1056             return 0;
1057         }
1058     }
1059 
1060     private static final Hashtable<String, Attribute> attributeMap = new Hashtable<String, Attribute>();
1061     private static final Hashtable<String, Value> valueMap = new Hashtable<String, Value>();
1062 
1063     /**
1064      * The hashtable and the static initalization block below,
1065      * set up a mapping from well-known HTML attributes to
1066      * CSS attributes.  For the most part, there is a 1-1 mapping
1067      * between the two.  However in the case of certain HTML
1068      * attributes for example HTML.Attribute.VSPACE or
1069      * HTML.Attribute.HSPACE, end up mapping to two CSS.Attribute's.
1070      * Therefore, the value associated with each HTML.Attribute.
1071      * key ends up being an array of CSS.Attribute.* objects.
1072      */
1073     private static final Hashtable<HTML.Attribute, CSS.Attribute[]> htmlAttrToCssAttrMap = new Hashtable<HTML.Attribute, CSS.Attribute[]>(20);
1074 
1075     /**
1076      * The hashtable and static initialization that follows sets
1077      * up a translation from StyleConstants (i.e. the <em>well known</em>
1078      * attributes) to the associated CSS attributes.
1079      */
1080     private static final Hashtable<Object, Attribute> styleConstantToCssMap = new Hashtable<Object, Attribute>(17);
1081     /** Maps from HTML value to a CSS value. Used in internal mapping. */
1082     private static final Hashtable<String, CSS.Value> htmlValueToCssValueMap = new Hashtable<String, CSS.Value>(8);
1083     /** Maps from CSS value (string) to internal value. */
1084     private static final Hashtable<String, CSS.Value> cssValueToInternalValueMap = new Hashtable<String, CSS.Value>(13);
1085 
1086     static {
1087         // load the attribute map
1088         for (int i = 0; i < Attribute.allAttributes.length; i++ ) {
1089             attributeMap.put(Attribute.allAttributes[i].toString(),
1090                              Attribute.allAttributes[i]);
1091         }
1092         // load the value map
1093         for (int i = 0; i < Value.allValues.length; i++ ) {
1094             valueMap.put(Value.allValues[i].toString(),
1095                              Value.allValues[i]);
1096         }
1097 
1098         htmlAttrToCssAttrMap.put(HTML.Attribute.COLOR,
1099                                  new CSS.Attribute[]{CSS.Attribute.COLOR});
1100         htmlAttrToCssAttrMap.put(HTML.Attribute.TEXT,
1101                                  new CSS.Attribute[]{CSS.Attribute.COLOR});
1102         htmlAttrToCssAttrMap.put(HTML.Attribute.CLEAR,
1103                                  new CSS.Attribute[]{CSS.Attribute.CLEAR});
1104         htmlAttrToCssAttrMap.put(HTML.Attribute.BACKGROUND,
1105                                  new CSS.Attribute[]{CSS.Attribute.BACKGROUND_IMAGE});
1106         htmlAttrToCssAttrMap.put(HTML.Attribute.BGCOLOR,
1107                                  new CSS.Attribute[]{CSS.Attribute.BACKGROUND_COLOR});
1108         htmlAttrToCssAttrMap.put(HTML.Attribute.WIDTH,
1109                                  new CSS.Attribute[]{CSS.Attribute.WIDTH});
1110         htmlAttrToCssAttrMap.put(HTML.Attribute.HEIGHT,
1111                                  new CSS.Attribute[]{CSS.Attribute.HEIGHT});
1112         htmlAttrToCssAttrMap.put(HTML.Attribute.BORDER,
1113                                  new CSS.Attribute[]{CSS.Attribute.BORDER_TOP_WIDTH, CSS.Attribute.BORDER_RIGHT_WIDTH, CSS.Attribute.BORDER_BOTTOM_WIDTH, CSS.Attribute.BORDER_LEFT_WIDTH});
1114         htmlAttrToCssAttrMap.put(HTML.Attribute.CELLPADDING,
1115                                  new CSS.Attribute[]{CSS.Attribute.PADDING});
1116         htmlAttrToCssAttrMap.put(HTML.Attribute.CELLSPACING,
1117                                  new CSS.Attribute[]{CSS.Attribute.BORDER_SPACING});
1118         htmlAttrToCssAttrMap.put(HTML.Attribute.MARGINWIDTH,
1119                                  new CSS.Attribute[]{CSS.Attribute.MARGIN_LEFT,
1120                                                      CSS.Attribute.MARGIN_RIGHT});
1121         htmlAttrToCssAttrMap.put(HTML.Attribute.MARGINHEIGHT,
1122                                  new CSS.Attribute[]{CSS.Attribute.MARGIN_TOP,
1123                                                      CSS.Attribute.MARGIN_BOTTOM});
1124         htmlAttrToCssAttrMap.put(HTML.Attribute.HSPACE,
1125                                  new CSS.Attribute[]{CSS.Attribute.PADDING_LEFT,
1126                                                      CSS.Attribute.PADDING_RIGHT});
1127         htmlAttrToCssAttrMap.put(HTML.Attribute.VSPACE,
1128                                  new CSS.Attribute[]{CSS.Attribute.PADDING_BOTTOM,
1129                                                      CSS.Attribute.PADDING_TOP});
1130         htmlAttrToCssAttrMap.put(HTML.Attribute.FACE,
1131                                  new CSS.Attribute[]{CSS.Attribute.FONT_FAMILY});
1132         htmlAttrToCssAttrMap.put(HTML.Attribute.SIZE,
1133                                  new CSS.Attribute[]{CSS.Attribute.FONT_SIZE});
1134         htmlAttrToCssAttrMap.put(HTML.Attribute.VALIGN,
1135                                  new CSS.Attribute[]{CSS.Attribute.VERTICAL_ALIGN});
1136         htmlAttrToCssAttrMap.put(HTML.Attribute.ALIGN,
1137                                  new CSS.Attribute[]{CSS.Attribute.VERTICAL_ALIGN,
1138                                                      CSS.Attribute.TEXT_ALIGN,
1139                                                      CSS.Attribute.FLOAT});
1140         htmlAttrToCssAttrMap.put(HTML.Attribute.TYPE,
1141                                  new CSS.Attribute[]{CSS.Attribute.LIST_STYLE_TYPE});
1142         htmlAttrToCssAttrMap.put(HTML.Attribute.NOWRAP,
1143                                  new CSS.Attribute[]{CSS.Attribute.WHITE_SPACE});
1144 
1145         // initialize StyleConstants mapping
1146         styleConstantToCssMap.put(StyleConstants.FontFamily,
1147                                   CSS.Attribute.FONT_FAMILY);
1148         styleConstantToCssMap.put(StyleConstants.FontSize,
1149                                   CSS.Attribute.FONT_SIZE);
1150         styleConstantToCssMap.put(StyleConstants.Bold,
1151                                   CSS.Attribute.FONT_WEIGHT);
1152         styleConstantToCssMap.put(StyleConstants.Italic,
1153                                   CSS.Attribute.FONT_STYLE);
1154         styleConstantToCssMap.put(StyleConstants.Underline,
1155                                   CSS.Attribute.TEXT_DECORATION);
1156         styleConstantToCssMap.put(StyleConstants.StrikeThrough,
1157                                   CSS.Attribute.TEXT_DECORATION);
1158         styleConstantToCssMap.put(StyleConstants.Superscript,
1159                                   CSS.Attribute.VERTICAL_ALIGN);
1160         styleConstantToCssMap.put(StyleConstants.Subscript,
1161                                   CSS.Attribute.VERTICAL_ALIGN);
1162         styleConstantToCssMap.put(StyleConstants.Foreground,
1163                                   CSS.Attribute.COLOR);
1164         styleConstantToCssMap.put(StyleConstants.Background,
1165                                   CSS.Attribute.BACKGROUND_COLOR);
1166         styleConstantToCssMap.put(StyleConstants.FirstLineIndent,
1167                                   CSS.Attribute.TEXT_INDENT);
1168         styleConstantToCssMap.put(StyleConstants.LeftIndent,
1169                                   CSS.Attribute.MARGIN_LEFT);
1170         styleConstantToCssMap.put(StyleConstants.RightIndent,
1171                                   CSS.Attribute.MARGIN_RIGHT);
1172         styleConstantToCssMap.put(StyleConstants.SpaceAbove,
1173                                   CSS.Attribute.MARGIN_TOP);
1174         styleConstantToCssMap.put(StyleConstants.SpaceBelow,
1175                                   CSS.Attribute.MARGIN_BOTTOM);
1176         styleConstantToCssMap.put(StyleConstants.Alignment,
1177                                   CSS.Attribute.TEXT_ALIGN);
1178 
1179         // HTML->CSS
1180         htmlValueToCssValueMap.put("disc", CSS.Value.DISC);
1181         htmlValueToCssValueMap.put("square", CSS.Value.SQUARE);
1182         htmlValueToCssValueMap.put("circle", CSS.Value.CIRCLE);
1183         htmlValueToCssValueMap.put("1", CSS.Value.DECIMAL);
1184         htmlValueToCssValueMap.put("a", CSS.Value.LOWER_ALPHA);
1185         htmlValueToCssValueMap.put("A", CSS.Value.UPPER_ALPHA);
1186         htmlValueToCssValueMap.put("i", CSS.Value.LOWER_ROMAN);
1187         htmlValueToCssValueMap.put("I", CSS.Value.UPPER_ROMAN);
1188 
1189         // CSS-> internal CSS
1190         cssValueToInternalValueMap.put("none", CSS.Value.NONE);
1191         cssValueToInternalValueMap.put("disc", CSS.Value.DISC);
1192         cssValueToInternalValueMap.put("square", CSS.Value.SQUARE);
1193         cssValueToInternalValueMap.put("circle", CSS.Value.CIRCLE);
1194         cssValueToInternalValueMap.put("decimal", CSS.Value.DECIMAL);
1195         cssValueToInternalValueMap.put("lower-roman", CSS.Value.LOWER_ROMAN);
1196         cssValueToInternalValueMap.put("upper-roman", CSS.Value.UPPER_ROMAN);
1197         cssValueToInternalValueMap.put("lower-alpha", CSS.Value.LOWER_ALPHA);
1198         cssValueToInternalValueMap.put("upper-alpha", CSS.Value.UPPER_ALPHA);
1199         cssValueToInternalValueMap.put("repeat", CSS.Value.BACKGROUND_REPEAT);
1200         cssValueToInternalValueMap.put("no-repeat",
1201                                        CSS.Value.BACKGROUND_NO_REPEAT);
1202         cssValueToInternalValueMap.put("repeat-x",
1203                                        CSS.Value.BACKGROUND_REPEAT_X);
1204         cssValueToInternalValueMap.put("repeat-y",
1205                                        CSS.Value.BACKGROUND_REPEAT_Y);
1206         cssValueToInternalValueMap.put("scroll",
1207                                        CSS.Value.BACKGROUND_SCROLL);
1208         cssValueToInternalValueMap.put("fixed",
1209                                        CSS.Value.BACKGROUND_FIXED);
1210 
1211         // Register all the CSS attribute keys for archival/unarchival
1212         Object[] keys = CSS.Attribute.allAttributes;
1213         try {
1214             for (Object key : keys) {
1215                 StyleContext.registerStaticAttributeKey(key);
1216             }
1217         } catch (Throwable e) {
1218             e.printStackTrace();
1219         }
1220 
1221         // Register all the CSS Values for archival/unarchival
1222         keys = CSS.Value.allValues;
1223         try {
1224             for (Object key : keys) {
1225                 StyleContext.registerStaticAttributeKey(key);
1226             }
1227         } catch (Throwable e) {
1228             e.printStackTrace();
1229         }
1230     }
1231 
1232     /**
1233      * Return the set of all possible CSS attribute keys.
1234      *
1235      * @return the set of all possible CSS attribute keys
1236      */
1237     public static Attribute[] getAllAttributeKeys() {
1238         Attribute[] keys = new Attribute[Attribute.allAttributes.length];
1239         System.arraycopy(Attribute.allAttributes, 0, keys, 0, Attribute.allAttributes.length);
1240         return keys;
1241     }
1242 
1243     /**
1244      * Translates a string to a <code>CSS.Attribute</code> object.
1245      * This will return <code>null</code> if there is no attribute
1246      * by the given name.
1247      *
1248      * @param name the name of the CSS attribute to fetch the
1249      *  typesafe enumeration for
1250      * @return the <code>CSS.Attribute</code> object,
1251      *  or <code>null</code> if the string
1252      *  doesn't represent a valid attribute key
1253      */
1254     public static final Attribute getAttribute(String name) {
1255         return attributeMap.get(name);
1256     }
1257 
1258     /**
1259      * Translates a string to a <code>CSS.Value</code> object.
1260      * This will return <code>null</code> if there is no value
1261      * by the given name.
1262      *
1263      * @param name the name of the CSS value to fetch the
1264      *  typesafe enumeration for
1265      * @return the <code>CSS.Value</code> object,
1266      *  or <code>null</code> if the string
1267      *  doesn't represent a valid CSS value name; this does
1268      *  not mean that it doesn't represent a valid CSS value
1269      */
1270     static final Value getValue(String name) {
1271         return valueMap.get(name);
1272     }
1273 
1274 
1275     //
1276     // Conversion related methods/classes
1277     //
1278 
1279     /**
1280      * Returns a URL for the given CSS url string. If relative,
1281      * <code>base</code> is used as the parent. If a valid URL can not
1282      * be found, this will not throw a MalformedURLException, instead
1283      * null will be returned.
1284      */
1285     static URL getURL(URL base, String cssString) {
1286         if (cssString == null) {
1287             return null;
1288         }
1289         if (cssString.startsWith("url(") &&
1290             cssString.endsWith(")")) {
1291             cssString = cssString.substring(4, cssString.length() - 1);
1292         }
1293         // Absolute first
1294         try {
1295             URL url = new URL(cssString);
1296             if (url != null) {
1297                 return url;
1298             }
1299         } catch (MalformedURLException mue) {
1300         }
1301         // Then relative
1302         if (base != null) {
1303             // Relative URL, try from base
1304             try {
1305                 URL url = new URL(base, cssString);
1306                 return url;
1307             }
1308             catch (MalformedURLException muee) {
1309             }
1310         }
1311         return null;
1312     }
1313 
1314     /**
1315      * Converts a type Color to a hex string
1316      * in the format "#RRGGBB"
1317      */
1318     static String colorToHex(Color color) {
1319 
1320       String colorstr = "#";
1321 
1322       // Red
1323       String str = Integer.toHexString(color.getRed());
1324       if (str.length() > 2)
1325         str = str.substring(0, 2);
1326       else if (str.length() < 2)
1327         colorstr += "0" + str;
1328       else
1329         colorstr += str;
1330 
1331       // Green
1332       str = Integer.toHexString(color.getGreen());
1333       if (str.length() > 2)
1334         str = str.substring(0, 2);
1335       else if (str.length() < 2)
1336         colorstr += "0" + str;
1337       else
1338         colorstr += str;
1339 
1340       // Blue
1341       str = Integer.toHexString(color.getBlue());
1342       if (str.length() > 2)
1343         str = str.substring(0, 2);
1344       else if (str.length() < 2)
1345         colorstr += "0" + str;
1346       else
1347         colorstr += str;
1348 
1349       return colorstr;
1350     }
1351 
1352      /**
1353       * Convert a "#FFFFFF" hex string to a Color.
1354       * If the color specification is bad, an attempt
1355       * will be made to fix it up.
1356       */
1357     static final Color hexToColor(String value) {
1358         String digits;
1359         int n = value.length();
1360         if (value.startsWith("#")) {
1361             digits = value.substring(1, Math.min(value.length(), 7));
1362         } else {
1363             digits = value;
1364         }
1365         String hstr = "0x" + digits;
1366         Color c;
1367         try {
1368             c = Color.decode(hstr);
1369         } catch (NumberFormatException nfe) {
1370             c = null;
1371         }
1372          return c;
1373      }
1374 
1375     /**
1376      * Convert a color string such as "RED" or "#NNNNNN" or "rgb(r, g, b)"
1377      * to a Color.
1378      */
1379     static Color stringToColor(String str) {
1380       Color color;
1381 
1382       if (str == null) {
1383           return null;
1384       }
1385       if (str.length() == 0)
1386         color = Color.black;
1387       else if (str.startsWith("rgb(")) {
1388           color = parseRGB(str);
1389       }
1390       else if (str.charAt(0) == '#')
1391         color = hexToColor(str);
1392       else if (str.equalsIgnoreCase("Black"))
1393         color = hexToColor("#000000");
1394       else if(str.equalsIgnoreCase("Silver"))
1395         color = hexToColor("#C0C0C0");
1396       else if(str.equalsIgnoreCase("Gray"))
1397         color = hexToColor("#808080");
1398       else if(str.equalsIgnoreCase("White"))
1399         color = hexToColor("#FFFFFF");
1400       else if(str.equalsIgnoreCase("Maroon"))
1401         color = hexToColor("#800000");
1402       else if(str.equalsIgnoreCase("Red"))
1403         color = hexToColor("#FF0000");
1404       else if(str.equalsIgnoreCase("Purple"))
1405         color = hexToColor("#800080");
1406       else if(str.equalsIgnoreCase("Fuchsia"))
1407         color = hexToColor("#FF00FF");
1408       else if(str.equalsIgnoreCase("Green"))
1409         color = hexToColor("#008000");
1410       else if(str.equalsIgnoreCase("Lime"))
1411         color = hexToColor("#00FF00");
1412       else if(str.equalsIgnoreCase("Olive"))
1413         color = hexToColor("#808000");
1414       else if(str.equalsIgnoreCase("Yellow"))
1415         color = hexToColor("#FFFF00");
1416       else if(str.equalsIgnoreCase("Navy"))
1417         color = hexToColor("#000080");
1418       else if(str.equalsIgnoreCase("Blue"))
1419         color = hexToColor("#0000FF");
1420       else if(str.equalsIgnoreCase("Teal"))
1421         color = hexToColor("#008080");
1422       else if(str.equalsIgnoreCase("Aqua"))
1423         color = hexToColor("#00FFFF");
1424       else if(str.equalsIgnoreCase("Orange"))
1425         color = hexToColor("#FF8000");
1426       else
1427           color = hexToColor(str); // sometimes get specified without leading #
1428       return color;
1429     }
1430 
1431     /**
1432      * Parses a String in the format <code>rgb(r, g, b)</code> where
1433      * each of the Color components is either an integer, or a floating number
1434      * with a % after indicating a percentage value of 255. Values are
1435      * constrained to fit with 0-255. The resulting Color is returned.
1436      */
1437     private static Color parseRGB(String string) {
1438         // Find the next numeric char
1439         int[] index = new int[1];
1440 
1441         index[0] = 4;
1442         int red = getColorComponent(string, index);
1443         int green = getColorComponent(string, index);
1444         int blue = getColorComponent(string, index);
1445 
1446         return new Color(red, green, blue);
1447     }
1448 
1449     /**
1450      * Returns the next integer value from <code>string</code> starting
1451      * at <code>index[0]</code>. The value can either can an integer, or
1452      * a percentage (floating number ending with %), in which case it is
1453      * multiplied by 255.
1454      */
1455     private static int getColorComponent(String string, int[] index) {
1456         int length = string.length();
1457         char aChar;
1458 
1459         // Skip non-decimal chars
1460         while(index[0] < length && (aChar = string.charAt(index[0])) != '-' &&
1461               !Character.isDigit(aChar) && aChar != '.') {
1462             index[0]++;
1463         }
1464 
1465         int start = index[0];
1466 
1467         if (start < length && string.charAt(index[0]) == '-') {
1468             index[0]++;
1469         }
1470         while(index[0] < length &&
1471                          Character.isDigit(string.charAt(index[0]))) {
1472             index[0]++;
1473         }
1474         if (index[0] < length && string.charAt(index[0]) == '.') {
1475             // Decimal value
1476             index[0]++;
1477             while(index[0] < length &&
1478                   Character.isDigit(string.charAt(index[0]))) {
1479                 index[0]++;
1480             }
1481         }
1482         if (start != index[0]) {
1483             try {
1484                 float value = Float.parseFloat(string.substring
1485                                                (start, index[0]));
1486 
1487                 if (index[0] < length && string.charAt(index[0]) == '%') {
1488                     index[0]++;
1489                     value = value * 255f / 100f;
1490                 }
1491                 return Math.min(255, Math.max(0, (int)value));
1492             } catch (NumberFormatException nfe) {
1493                 // Treat as 0
1494             }
1495         }
1496         return 0;
1497     }
1498 
1499     static int getIndexOfSize(float pt, int[] sizeMap) {
1500         for (int i = 0; i < sizeMap.length; i ++ )
1501                 if (pt <= sizeMap[i])
1502                         return i + 1;
1503         return sizeMap.length;
1504     }
1505 
1506     static int getIndexOfSize(float pt, StyleSheet ss) {
1507         int[] sizeMap = (ss != null) ? ss.getSizeMap() :
1508             StyleSheet.sizeMapDefault;
1509         return getIndexOfSize(pt, sizeMap);
1510     }
1511 
1512 
1513     /**
1514      * @return an array of all the strings in <code>value</code>
1515      *         that are separated by whitespace.
1516      */
1517     static String[] parseStrings(String value) {
1518         int         current, last;
1519         int         length = (value == null) ? 0 : value.length();
1520         Vector<String> temp = new Vector<String>(4);
1521 
1522         current = 0;
1523         while (current < length) {
1524             // Skip ws
1525             while (current < length && Character.isWhitespace
1526                    (value.charAt(current))) {
1527                 current++;
1528             }
1529             last = current;
1530             int inParentheses = 0;
1531             char ch;
1532             while (current < length && (
1533                     !Character.isWhitespace(ch = value.charAt(current))
1534                             || inParentheses > 0)) {
1535                 if (ch == '(') {
1536                     inParentheses++;
1537                 } else if (ch == ')') {
1538                     inParentheses--;
1539                 }
1540                 current++;
1541             }
1542             if (last != current) {
1543                 temp.addElement(value.substring(last, current));
1544             }
1545             current++;
1546         }
1547         String[] retValue = new String[temp.size()];
1548         temp.copyInto(retValue);
1549         return retValue;
1550     }
1551 
1552     /**
1553      * Return the point size, given a size index. Legal HTML index sizes
1554      * are 1-7.
1555      */
1556     float getPointSize(int index, StyleSheet ss) {
1557         ss = getStyleSheet(ss);
1558         int[] sizeMap = (ss != null) ? ss.getSizeMap() :
1559             StyleSheet.sizeMapDefault;
1560         --index;
1561         if (index < 0)
1562           return sizeMap[0];
1563         else if (index > sizeMap.length - 1)
1564           return sizeMap[sizeMap.length - 1];
1565         else
1566           return sizeMap[index];
1567     }
1568 
1569 
1570     private void translateEmbeddedAttributes(AttributeSet htmlAttrSet,
1571                                              MutableAttributeSet cssAttrSet) {
1572         Enumeration<?> keys = htmlAttrSet.getAttributeNames();
1573         if (htmlAttrSet.getAttribute(StyleConstants.NameAttribute) ==
1574             HTML.Tag.HR) {
1575             // HR needs special handling due to us treating it as a leaf.
1576             translateAttributes(HTML.Tag.HR, htmlAttrSet, cssAttrSet);
1577         }
1578         while (keys.hasMoreElements()) {
1579             Object key = keys.nextElement();
1580             if (key instanceof HTML.Tag) {
1581                 HTML.Tag tag = (HTML.Tag)key;
1582                 Object o = htmlAttrSet.getAttribute(tag);
1583                 if (o != null && o instanceof AttributeSet) {
1584                     translateAttributes(tag, (AttributeSet)o, cssAttrSet);
1585                 }
1586             } else if (key instanceof CSS.Attribute) {
1587                 cssAttrSet.addAttribute(key, htmlAttrSet.getAttribute(key));
1588             }
1589         }
1590     }
1591 
1592     private void translateAttributes(HTML.Tag tag,
1593                                             AttributeSet htmlAttrSet,
1594                                             MutableAttributeSet cssAttrSet) {
1595         Enumeration<?> names = htmlAttrSet.getAttributeNames();
1596         while (names.hasMoreElements()) {
1597             Object name = names.nextElement();
1598 
1599             if (name instanceof HTML.Attribute) {
1600                 HTML.Attribute key = (HTML.Attribute)name;
1601 
1602                 /*
1603                  * HTML.Attribute.ALIGN needs special processing.
1604                  * It can map to 1 of many(3) possible CSS attributes
1605                  * depending on the nature of the tag the attribute is
1606                  * part off and depending on the value of the attribute.
1607                  */
1608                 if (key == HTML.Attribute.ALIGN) {
1609                     String htmlAttrValue = (String)htmlAttrSet.getAttribute(HTML.Attribute.ALIGN);
1610                     if (htmlAttrValue != null) {
1611                         CSS.Attribute cssAttr = getCssAlignAttribute(tag, htmlAttrSet);
1612                         if (cssAttr != null) {
1613                             Object o = getCssValue(cssAttr, htmlAttrValue);
1614                             if (o != null) {
1615                                 cssAttrSet.addAttribute(cssAttr, o);
1616                             }
1617                         }
1618                     }
1619                 } else {
1620                     if (key == HTML.Attribute.SIZE && !isHTMLFontTag(tag)) {
1621                         /*
1622                          * The html size attribute has a mapping in the CSS world only
1623                          * if it is par of a font or base font tag.
1624                          */
1625                     } else if (tag == HTML.Tag.TABLE && key == HTML.Attribute.BORDER) {
1626                         int borderWidth = getTableBorder(htmlAttrSet);
1627 
1628                         if (borderWidth > 0) {
1629                             translateAttribute(HTML.Attribute.BORDER, Integer.toString(borderWidth), cssAttrSet);
1630                         }
1631                     } else {
1632                         translateAttribute(key, (String) htmlAttrSet.getAttribute(key), cssAttrSet);
1633                     }
1634                 }
1635             } else if (name instanceof CSS.Attribute) {
1636                 cssAttrSet.addAttribute(name, htmlAttrSet.getAttribute(name));
1637             }
1638         }
1639     }
1640 
1641     private void translateAttribute(HTML.Attribute key,
1642                                            String htmlAttrValue,
1643                                            MutableAttributeSet cssAttrSet) {
1644         /*
1645          * In the case of all remaining HTML.Attribute's they
1646          * map to 1 or more CCS.Attribute.
1647          */
1648         CSS.Attribute[] cssAttrList = getCssAttribute(key);
1649 
1650         if (cssAttrList == null || htmlAttrValue == null) {
1651             return;
1652         }
1653         for (Attribute cssAttr : cssAttrList) {
1654             Object o = getCssValue(cssAttr, htmlAttrValue);
1655             if (o != null) {
1656                 cssAttrSet.addAttribute(cssAttr , o);
1657             }
1658         }
1659     }
1660 
1661     /**
1662      * Given a CSS.Attribute object and its corresponding HTML.Attribute's
1663      * value, this method returns a CssValue object to associate with the
1664      * CSS attribute.
1665      *
1666      * @param cssAttr the CSS.Attribute
1667      * @param htmlAttrValue a String containing the value associated HTML.Attribute.
1668      */
1669     Object getCssValue(CSS.Attribute cssAttr, String htmlAttrValue) {
1670         CssValue value = (CssValue)valueConvertor.get(cssAttr);
1671         Object o = value.parseHtmlValue(htmlAttrValue);
1672         return o;
1673     }
1674 
1675     /**
1676      * Maps an HTML.Attribute object to its appropriate CSS.Attributes.
1677      *
1678      * @param hAttr HTML.Attribute
1679      * @return CSS.Attribute[]
1680      */
1681     private CSS.Attribute[] getCssAttribute(HTML.Attribute hAttr) {
1682         return htmlAttrToCssAttrMap.get(hAttr);
1683     }
1684 
1685     /**
1686      * Maps HTML.Attribute.ALIGN to either:
1687      *     CSS.Attribute.TEXT_ALIGN
1688      *     CSS.Attribute.FLOAT
1689      *     CSS.Attribute.VERTICAL_ALIGN
1690      * based on the tag associated with the attribute and the
1691      * value of the attribute.
1692      *
1693      * @param tag the AttributeSet containing HTML attributes.
1694      * @return CSS.Attribute mapping for HTML.Attribute.ALIGN.
1695      */
1696     private CSS.Attribute getCssAlignAttribute(HTML.Tag tag,
1697                                                    AttributeSet htmlAttrSet) {
1698         return CSS.Attribute.TEXT_ALIGN;
1699 /*
1700         String htmlAttrValue = (String)htmlAttrSet.getAttribute(HTML.Attribute.ALIGN);
1701         CSS.Attribute cssAttr = CSS.Attribute.TEXT_ALIGN;
1702         if (htmlAttrValue != null && htmlAttrSet instanceof Element) {
1703             Element elem = (Element)htmlAttrSet;
1704             if (!elem.isLeaf() && tag.isBlock() && validTextAlignValue(htmlAttrValue)) {
1705                 return CSS.Attribute.TEXT_ALIGN;
1706             } else if (isFloater(htmlAttrValue)) {
1707                 return CSS.Attribute.FLOAT;
1708             } else if (elem.isLeaf()) {
1709                 return CSS.Attribute.VERTICAL_ALIGN;
1710             }
1711         }
1712         return null;
1713         */
1714     }
1715 
1716     /**
1717      * Fetches the tag associated with the HTML AttributeSet.
1718      *
1719      * @param  htmlAttrSet the AttributeSet containing the HTML attributes.
1720      * @return HTML.Tag
1721      */
1722     private HTML.Tag getHTMLTag(AttributeSet htmlAttrSet) {
1723         Object o = htmlAttrSet.getAttribute(StyleConstants.NameAttribute);
1724         if (o instanceof HTML.Tag) {
1725             HTML.Tag tag = (HTML.Tag) o;
1726             return tag;
1727         }
1728         return null;
1729     }
1730 
1731 
1732     private boolean isHTMLFontTag(HTML.Tag tag) {
1733         return (tag != null && ((tag == HTML.Tag.FONT) || (tag == HTML.Tag.BASEFONT)));
1734     }
1735 
1736 
1737     private boolean isFloater(String alignValue) {
1738         return (alignValue.equals("left") || alignValue.equals("right"));
1739     }
1740 
1741     private boolean validTextAlignValue(String alignValue) {
1742         return (isFloater(alignValue) || alignValue.equals("center"));
1743     }
1744 
1745     /**
1746      * Base class to CSS values in the attribute sets.  This
1747      * is intended to act as a convertor to/from other attribute
1748      * formats.
1749      * <p>
1750      * The CSS parser uses the parseCssValue method to convert
1751      * a string to whatever format is appropriate a given key
1752      * (i.e. these convertors are stored in a map using the
1753      * CSS.Attribute as a key and the CssValue as the value).
1754      * <p>
1755      * The HTML to CSS conversion process first converts the
1756      * HTML.Attribute to a CSS.Attribute, and then calls
1757      * the parseHtmlValue method on the value of the HTML
1758      * attribute to produce the corresponding CSS value.
1759      * <p>
1760      * The StyleConstants to CSS conversion process first
1761      * converts the StyleConstants attribute to a
1762      * CSS.Attribute, and then calls the fromStyleConstants
1763      * method to convert the StyleConstants value to a
1764      * CSS value.
1765      * <p>
1766      * The CSS to StyleConstants conversion process first
1767      * converts the StyleConstants attribute to a
1768      * CSS.Attribute, and then calls the toStyleConstants
1769      * method to convert the CSS value to a StyleConstants
1770      * value.
1771      */
1772     @SuppressWarnings("serial") // Same-version serialization only
1773     static class CssValue implements Serializable {
1774 
1775         /**
1776          * Convert a CSS value string to the internal format
1777          * (for fast processing) used in the attribute sets.
1778          * The fallback storage for any value that we don't
1779          * have a special binary format for is a String.
1780          */
1781         Object parseCssValue(String value) {
1782             return value;
1783         }
1784 
1785         /**
1786          * Convert an HTML attribute value to a CSS attribute
1787          * value.  If there is no conversion, return null.
1788          * This is implemented to simply forward to the CSS
1789          * parsing by default (since some of the attribute
1790          * values are the same).  If the attribute value
1791          * isn't recognized as a CSS value it is generally
1792          * returned as null.
1793          */
1794         Object parseHtmlValue(String value) {
1795             return parseCssValue(value);
1796         }
1797 
1798         /**
1799          * Converts a <code>StyleConstants</code> attribute value to
1800          * a CSS attribute value.  If there is no conversion,
1801          * returns <code>null</code>.  By default, there is no conversion.
1802          *
1803          * @param key the <code>StyleConstants</code> attribute
1804          * @param value the value of a <code>StyleConstants</code>
1805          *   attribute to be converted
1806          * @return the CSS value that represents the
1807          *   <code>StyleConstants</code> value
1808          */
1809         Object fromStyleConstants(StyleConstants key, Object value) {
1810             return null;
1811         }
1812 
1813         /**
1814          * Converts a CSS attribute value to a
1815          * <code>StyleConstants</code>
1816          * value.  If there is no conversion, returns
1817          * <code>null</code>.
1818          * By default, there is no conversion.
1819          *
1820          * @param key the <code>StyleConstants</code> attribute
1821          * @param v the view containing <code>AttributeSet</code>
1822          * @return the <code>StyleConstants</code> attribute value that
1823          *   represents the CSS attribute value
1824          */
1825         Object toStyleConstants(StyleConstants key, View v) {
1826             return null;
1827         }
1828 
1829         /**
1830          * Return the CSS format of the value
1831          */
1832         public String toString() {
1833             return svalue;
1834         }
1835 
1836         /**
1837          * The value as a string... before conversion to a
1838          * binary format.
1839          */
1840         String svalue;
1841     }
1842 
1843     /**
1844      * By default CSS attributes are represented as simple
1845      * strings.  They also have no conversion to/from
1846      * StyleConstants by default. This class represents the
1847      * value as a string (via the superclass), but
1848      * provides StyleConstants conversion support for the
1849      * CSS attributes that are held as strings.
1850      */
1851     @SuppressWarnings("serial") // Same-version serialization only
1852     static class StringValue extends CssValue {
1853 
1854         /**
1855          * Convert a CSS value string to the internal format
1856          * (for fast processing) used in the attribute sets.
1857          * This produces a StringValue, so that it can be
1858          * used to convert from CSS to StyleConstants values.
1859          */
1860         Object parseCssValue(String value) {
1861             StringValue sv = new StringValue();
1862             sv.svalue = value;
1863             return sv;
1864         }
1865 
1866         /**
1867          * Converts a <code>StyleConstants</code> attribute value to
1868          * a CSS attribute value.  If there is no conversion
1869          * returns <code>null</code>.
1870          *
1871          * @param key the <code>StyleConstants</code> attribute
1872          * @param value the value of a <code>StyleConstants</code>
1873          *   attribute to be converted
1874          * @return the CSS value that represents the
1875          *   <code>StyleConstants</code> value
1876          */
1877         Object fromStyleConstants(StyleConstants key, Object value) {
1878             if (key == StyleConstants.Italic) {
1879                 if (value.equals(Boolean.TRUE)) {
1880                     return parseCssValue("italic");
1881                 }
1882                 return parseCssValue("");
1883             } else if (key == StyleConstants.Underline) {
1884                 if (value.equals(Boolean.TRUE)) {
1885                     return parseCssValue("underline");
1886                 }
1887                 return parseCssValue("");
1888             } else if (key == StyleConstants.Alignment) {
1889                 int align = ((Integer)value).intValue();
1890                 String ta;
1891                 switch(align) {
1892                 case StyleConstants.ALIGN_LEFT:
1893                     ta = "left";
1894                     break;
1895                 case StyleConstants.ALIGN_RIGHT:
1896                     ta = "right";
1897                     break;
1898                 case StyleConstants.ALIGN_CENTER:
1899                     ta = "center";
1900                     break;
1901                 case StyleConstants.ALIGN_JUSTIFIED:
1902                     ta = "justify";
1903                     break;
1904                 default:
1905                     ta = "left";
1906                 }
1907                 return parseCssValue(ta);
1908             } else if (key == StyleConstants.StrikeThrough) {
1909                 if (value.equals(Boolean.TRUE)) {
1910                     return parseCssValue("line-through");
1911                 }
1912                 return parseCssValue("");
1913             } else if (key == StyleConstants.Superscript) {
1914                 if (value.equals(Boolean.TRUE)) {
1915                     return parseCssValue("super");
1916                 }
1917                 return parseCssValue("");
1918             } else if (key == StyleConstants.Subscript) {
1919                 if (value.equals(Boolean.TRUE)) {
1920                     return parseCssValue("sub");
1921                 }
1922                 return parseCssValue("");
1923             }
1924             return null;
1925         }
1926 
1927         /**
1928          * Converts a CSS attribute value to a
1929          * <code>StyleConstants</code> value.
1930          * If there is no conversion, returns <code>null</code>.
1931          * By default, there is no conversion.
1932          *
1933          * @param key the <code>StyleConstants</code> attribute
1934          * @return the <code>StyleConstants</code> attribute value that
1935          *   represents the CSS attribute value
1936          */
1937         Object toStyleConstants(StyleConstants key, View v) {
1938             if (key == StyleConstants.Italic) {
1939                 if (svalue.indexOf("italic") >= 0) {
1940                     return Boolean.TRUE;
1941                 }
1942                 return Boolean.FALSE;
1943             } else if (key == StyleConstants.Underline) {
1944                 if (svalue.indexOf("underline") >= 0) {
1945                     return Boolean.TRUE;
1946                 }
1947                 return Boolean.FALSE;
1948             } else if (key == StyleConstants.Alignment) {
1949                 if (svalue.equals("right")) {
1950                     return StyleConstants.ALIGN_RIGHT;
1951                 } else if (svalue.equals("center")) {
1952                     return StyleConstants.ALIGN_CENTER;
1953                 } else if  (svalue.equals("justify")) {
1954                     return StyleConstants.ALIGN_JUSTIFIED;
1955                 }
1956                 return StyleConstants.ALIGN_LEFT;
1957             } else if (key == StyleConstants.StrikeThrough) {
1958                 if (svalue.indexOf("line-through") >= 0) {
1959                     return Boolean.TRUE;
1960                 }
1961                 return Boolean.FALSE;
1962             } else if (key == StyleConstants.Superscript) {
1963                 if (svalue.indexOf("super") >= 0) {
1964                     return Boolean.TRUE;
1965                 }
1966                 return Boolean.FALSE;
1967             } else if (key == StyleConstants.Subscript) {
1968                 if (svalue.indexOf("sub") >= 0) {
1969                     return Boolean.TRUE;
1970                 }
1971                 return Boolean.FALSE;
1972             }
1973             return null;
1974         }
1975 
1976         // Used by ViewAttributeSet
1977         boolean isItalic() {
1978             return (svalue.indexOf("italic") != -1);
1979         }
1980 
1981         boolean isStrike() {
1982             return (svalue.indexOf("line-through") != -1);
1983         }
1984 
1985         boolean isUnderline() {
1986             return (svalue.indexOf("underline") != -1);
1987         }
1988 
1989         boolean isSub() {
1990             return (svalue.indexOf("sub") != -1);
1991         }
1992 
1993         boolean isSup() {
1994             return (svalue.indexOf("sup") != -1);
1995         }
1996     }
1997 
1998     /**
1999      * Represents a value for the CSS.FONT_SIZE attribute.
2000      * The binary format of the value can be one of several
2001      * types.  If the type is Float,
2002      * the value is specified in terms of point or
2003      * percentage, depending upon the ending of the
2004      * associated string.
2005      * If the type is Integer, the value is specified
2006      * in terms of a size index.
2007      */
2008     @SuppressWarnings("serial") // Same-version serialization only
2009     class FontSize extends CssValue {
2010 
2011         /**
2012          * Returns the size in points.  This is ultimately
2013          * what we need for the purpose of creating/fetching
2014          * a Font object.
2015          *
2016          * @param a the attribute set the value is being
2017          *  requested from.  We may need to walk up the
2018          *  resolve hierarchy if it's relative.
2019          */
2020         int getValue(AttributeSet a, StyleSheet ss) {
2021             ss = getStyleSheet(ss);
2022             if (index) {
2023                 // it's an index, translate from size table
2024                 return Math.round(getPointSize((int) value, ss));
2025             }
2026             else if (lu == null) {
2027                 return Math.round(value);
2028             }
2029             else {
2030                 if (lu.type == 0) {
2031                     boolean isW3CLengthUnits = (ss == null) ? false : ss.isW3CLengthUnits();
2032                     return Math.round(lu.getValue(isW3CLengthUnits));
2033                 }
2034                 if (a != null) {
2035                     AttributeSet resolveParent = a.getResolveParent();
2036 
2037                     if (resolveParent != null) {
2038                         int pValue = StyleConstants.getFontSize(resolveParent);
2039 
2040                         float retValue;
2041                         if (lu.type == 1 || lu.type == 3) {
2042                             retValue = lu.value * (float)pValue;
2043                         }
2044                         else {
2045                             retValue = lu.value + (float)pValue;
2046                         }
2047                         return Math.round(retValue);
2048                     }
2049                 }
2050                 // a is null, or no resolve parent.
2051                 return 12;
2052             }
2053         }
2054 
2055         Object parseCssValue(String value) {
2056             FontSize fs = new FontSize();
2057             fs.svalue = value;
2058             try {
2059                 if (value.equals("xx-small")) {
2060                     fs.value = 1;
2061                     fs.index = true;
2062                 } else if (value.equals("x-small")) {
2063                     fs.value = 2;
2064                     fs.index = true;
2065                 } else if (value.equals("small")) {
2066                     fs.value = 3;
2067                     fs.index = true;
2068                 } else if (value.equals("medium")) {
2069                     fs.value = 4;
2070                     fs.index = true;
2071                 } else if (value.equals("large")) {
2072                     fs.value = 5;
2073                     fs.index = true;
2074                 } else if (value.equals("x-large")) {
2075                     fs.value = 6;
2076                     fs.index = true;
2077                 } else if (value.equals("xx-large")) {
2078                     fs.value = 7;
2079                     fs.index = true;
2080                 } else {
2081                     fs.lu = new LengthUnit(value, (short)1, 1f);
2082                 }
2083                 // relative sizes, larger | smaller (adjust from parent by
2084                 // 1.5 pixels)
2085                 // em, ex refer to parent sizes
2086                 // lengths: pt, mm, cm, pc, in, px
2087                 //          em (font height 3em would be 3 times font height)
2088                 //          ex (height of X)
2089                 // lengths are (+/-) followed by a number and two letter
2090                 // unit identifier
2091             } catch (NumberFormatException nfe) {
2092                 fs = null;
2093             }
2094             return fs;
2095         }
2096 
2097         Object parseHtmlValue(String value) {
2098             if ((value == null) || (value.length() == 0)) {
2099                 return null;
2100             }
2101             FontSize fs = new FontSize();
2102             fs.svalue = value;
2103 
2104             try {
2105                 /*
2106                  * relative sizes in the size attribute are relative
2107                  * to the <basefont>'s size.
2108                  */
2109                 int baseFontSize = getBaseFontSize();
2110                 if (value.charAt(0) == '+') {
2111                     int relSize = Integer.valueOf(value.substring(1)).intValue();
2112                     fs.value = baseFontSize + relSize;
2113                     fs.index = true;
2114                 } else if (value.charAt(0) == '-') {
2115                     int relSize = -Integer.valueOf(value.substring(1)).intValue();
2116                     fs.value = baseFontSize + relSize;
2117                     fs.index = true;
2118                 } else {
2119                     fs.value = Integer.parseInt(value);
2120                     if (fs.value > 7) {
2121                         fs.value = 7;
2122                     } else if (fs.value < 0) {
2123                         fs.value = 0;
2124                     }
2125                     fs.index = true;
2126                 }
2127 
2128             } catch (NumberFormatException nfe) {
2129                 fs = null;
2130             }
2131             return fs;
2132         }
2133 
2134         /**
2135          * Converts a <code>StyleConstants</code> attribute value to
2136          * a CSS attribute value.  If there is no conversion
2137          * returns <code>null</code>.  By default, there is no conversion.
2138          *
2139          * @param key the <code>StyleConstants</code> attribute
2140          * @param value the value of a <code>StyleConstants</code>
2141          *   attribute to be converted
2142          * @return the CSS value that represents the
2143          *   <code>StyleConstants</code> value
2144          */
2145         Object fromStyleConstants(StyleConstants key, Object value) {
2146             if (value instanceof Number) {
2147                 FontSize fs = new FontSize();
2148 
2149                 fs.value = getIndexOfSize(((Number)value).floatValue(), StyleSheet.sizeMapDefault);
2150                 fs.svalue = Integer.toString((int)fs.value);
2151                 fs.index = true;
2152                 return fs;
2153             }
2154             return parseCssValue(value.toString());
2155         }
2156 
2157         /**
2158          * Converts a CSS attribute value to a <code>StyleConstants</code>
2159          * value.  If there is no conversion, returns <code>null</code>.
2160          * By default, there is no conversion.
2161          *
2162          * @param key the <code>StyleConstants</code> attribute
2163          * @return the <code>StyleConstants</code> attribute value that
2164          *   represents the CSS attribute value
2165          */
2166         Object toStyleConstants(StyleConstants key, View v) {
2167             if (v != null) {
2168                 return Integer.valueOf(getValue(v.getAttributes(), null));
2169             }
2170             return Integer.valueOf(getValue(null, null));
2171         }
2172 
2173         float value;
2174         boolean index;
2175         LengthUnit lu;
2176     }
2177 
2178     @SuppressWarnings("serial") // Same-version serialization only
2179     static class FontFamily extends CssValue {
2180 
2181         /**
2182          * Returns the font family to use.
2183          */
2184         String getValue() {
2185             return family;
2186         }
2187 
2188         Object parseCssValue(String value) {
2189             int cIndex = value.indexOf(',');
2190             FontFamily ff = new FontFamily();
2191             ff.svalue = value;
2192             ff.family = null;
2193 
2194             if (cIndex == -1) {
2195                 setFontName(ff, value);
2196             }
2197             else {
2198                 boolean done = false;
2199                 int lastIndex;
2200                 int length = value.length();
2201                 cIndex = 0;
2202                 while (!done) {
2203                     // skip ws.
2204                     while (cIndex < length &&
2205                            Character.isWhitespace(value.charAt(cIndex)))
2206                         cIndex++;
2207                     // Find next ','
2208                     lastIndex = cIndex;
2209                     cIndex = value.indexOf(',', cIndex);
2210                     if (cIndex == -1) {
2211                         cIndex = length;
2212                     }
2213                     if (lastIndex < length) {
2214                         if (lastIndex != cIndex) {
2215                             int lastCharIndex = cIndex;
2216                             if (cIndex > 0 && value.charAt(cIndex - 1) == ' '){
2217                                 lastCharIndex--;
2218                             }
2219                             setFontName(ff, value.substring
2220                                         (lastIndex, lastCharIndex));
2221                             done = (ff.family != null);
2222                         }
2223                         cIndex++;
2224                     }
2225                     else {
2226                         done = true;
2227                     }
2228                 }
2229             }
2230             if (ff.family == null) {
2231                 ff.family = Font.SANS_SERIF;
2232             }
2233             return ff;
2234         }
2235 
2236         private void setFontName(FontFamily ff, String fontName) {
2237             ff.family = fontName;
2238         }
2239 
2240         Object parseHtmlValue(String value) {
2241             // TBD
2242             return parseCssValue(value);
2243         }
2244 
2245         /**
2246          * Converts a <code>StyleConstants</code> attribute value to
2247          * a CSS attribute value.  If there is no conversion
2248          * returns <code>null</code>.  By default, there is no conversion.
2249          *
2250          * @param key the <code>StyleConstants</code> attribute
2251          * @param value the value of a <code>StyleConstants</code>
2252          *   attribute to be converted
2253          * @return the CSS value that represents the
2254          *   <code>StyleConstants</code> value
2255          */
2256         Object fromStyleConstants(StyleConstants key, Object value) {
2257             return parseCssValue(value.toString());
2258         }
2259 
2260         /**
2261          * Converts a CSS attribute value to a <code>StyleConstants</code>
2262          * value.  If there is no conversion, returns <code>null</code>.
2263          * By default, there is no conversion.
2264          *
2265          * @param key the <code>StyleConstants</code> attribute
2266          * @return the <code>StyleConstants</code> attribute value that
2267          *   represents the CSS attribute value
2268          */
2269         Object toStyleConstants(StyleConstants key, View v) {
2270             return family;
2271         }
2272 
2273         String family;
2274     }
2275 
2276     @SuppressWarnings("serial") // Same-version serialization only
2277     static class FontWeight extends CssValue {
2278 
2279         int getValue() {
2280             return weight;
2281         }
2282 
2283         Object parseCssValue(String value) {
2284             FontWeight fw = new FontWeight();
2285             fw.svalue = value;
2286             if (value.equals("bold")) {
2287                 fw.weight = 700;
2288             } else if (value.equals("normal")) {
2289                 fw.weight = 400;
2290             } else {
2291                 // PENDING(prinz) add support for relative values
2292                 try {
2293                     fw.weight = Integer.parseInt(value);
2294                 } catch (NumberFormatException nfe) {
2295                     fw = null;
2296                 }
2297             }
2298             return fw;
2299         }
2300 
2301         /**
2302          * Converts a <code>StyleConstants</code> attribute value to
2303          * a CSS attribute value.  If there is no conversion
2304          * returns <code>null</code>.  By default, there is no conversion.
2305          *
2306          * @param key the <code>StyleConstants</code> attribute
2307          * @param value the value of a <code>StyleConstants</code>
2308          *   attribute to be converted
2309          * @return the CSS value that represents the
2310          *   <code>StyleConstants</code> value
2311          */
2312         Object fromStyleConstants(StyleConstants key, Object value) {
2313             if (value.equals(Boolean.TRUE)) {
2314                 return parseCssValue("bold");
2315             }
2316             return parseCssValue("normal");
2317         }
2318 
2319         /**
2320          * Converts a CSS attribute value to a <code>StyleConstants</code>
2321          * value.  If there is no conversion, returns <code>null</code>.
2322          * By default, there is no conversion.
2323          *
2324          * @param key the <code>StyleConstants</code> attribute
2325          * @return the <code>StyleConstants</code> attribute value that
2326          *   represents the CSS attribute value
2327          */
2328         Object toStyleConstants(StyleConstants key, View v) {
2329             return (weight > 500) ? Boolean.TRUE : Boolean.FALSE;
2330         }
2331 
2332         boolean isBold() {
2333             return (weight > 500);
2334         }
2335 
2336         int weight;
2337     }
2338 
2339     @SuppressWarnings("serial") // Same-version serialization only
2340     static class ColorValue extends CssValue {
2341 
2342         /**
2343          * Returns the color to use.
2344          */
2345         Color getValue() {
2346             return c;
2347         }
2348 
2349         Object parseCssValue(String value) {
2350 
2351             Color c = stringToColor(value);
2352             if (c != null) {
2353                 ColorValue cv = new ColorValue();
2354                 cv.svalue = value;
2355                 cv.c = c;
2356                 return cv;
2357             }
2358             return null;
2359         }
2360 
2361         Object parseHtmlValue(String value) {
2362             return parseCssValue(value);
2363         }
2364 
2365         /**
2366          * Converts a <code>StyleConstants</code> attribute value to
2367          * a CSS attribute value.  If there is no conversion
2368          * returns <code>null</code>.  By default, there is no conversion.
2369          *
2370          * @param key the <code>StyleConstants</code> attribute
2371          * @param value the value of a <code>StyleConstants</code>
2372          *   attribute to be converted
2373          * @return the CSS value that represents the
2374          *   <code>StyleConstants</code> value
2375          */
2376         Object fromStyleConstants(StyleConstants key, Object value) {
2377             ColorValue colorValue = new ColorValue();
2378             colorValue.c = (Color)value;
2379             colorValue.svalue = colorToHex(colorValue.c);
2380             return colorValue;
2381         }
2382 
2383         /**
2384          * Converts a CSS attribute value to a <code>StyleConstants</code>
2385          * value.  If there is no conversion, returns <code>null</code>.
2386          * By default, there is no conversion.
2387          *
2388          * @param key the <code>StyleConstants</code> attribute
2389          * @return the <code>StyleConstants</code> attribute value that
2390          *   represents the CSS attribute value
2391          */
2392         Object toStyleConstants(StyleConstants key, View v) {
2393             return c;
2394         }
2395 
2396         Color c;
2397     }
2398 
2399     @SuppressWarnings("serial") // Same-version serialization only
2400     static class BorderStyle extends CssValue {
2401 
2402         CSS.Value getValue() {
2403             return style;
2404         }
2405 
2406         Object parseCssValue(String value) {
2407             CSS.Value cssv = CSS.getValue(value);
2408             if (cssv != null) {
2409                 if ((cssv == CSS.Value.INSET) ||
2410                     (cssv == CSS.Value.OUTSET) ||
2411                     (cssv == CSS.Value.NONE) ||
2412                     (cssv == CSS.Value.DOTTED) ||
2413                     (cssv == CSS.Value.DASHED) ||
2414                     (cssv == CSS.Value.SOLID) ||
2415                     (cssv == CSS.Value.DOUBLE) ||
2416                     (cssv == CSS.Value.GROOVE) ||
2417                     (cssv == CSS.Value.RIDGE)) {
2418 
2419                     BorderStyle bs = new BorderStyle();
2420                     bs.svalue = value;
2421                     bs.style = cssv;
2422                     return bs;
2423                 }
2424             }
2425             return null;
2426         }
2427 
2428         private void writeObject(java.io.ObjectOutputStream s)
2429                      throws IOException {
2430             s.defaultWriteObject();
2431             if (style == null) {
2432                 s.writeObject(null);
2433             }
2434             else {
2435                 s.writeObject(style.toString());
2436             }
2437         }
2438 
2439         private void readObject(ObjectInputStream s)
2440                 throws ClassNotFoundException, IOException {
2441             s.defaultReadObject();
2442             Object value = s.readObject();
2443             if (value != null) {
2444                 style = CSS.getValue((String)value);
2445             }
2446         }
2447 
2448         // CSS.Values are static, don't archive it.
2449         private transient CSS.Value style;
2450     }
2451 
2452     @SuppressWarnings("serial") // Same-version serialization only
2453     static class LengthValue extends CssValue {
2454 
2455         /**
2456          * if this length value may be negative.
2457          */
2458         boolean mayBeNegative;
2459 
2460         LengthValue() {
2461             this(false);
2462         }
2463 
2464         LengthValue(boolean mayBeNegative) {
2465             this.mayBeNegative = mayBeNegative;
2466         }
2467 
2468         /**
2469          * Returns the length (span) to use.
2470          */
2471         float getValue() {
2472             return getValue(false);
2473         }
2474 
2475         float getValue(boolean isW3CLengthUnits) {
2476             return getValue(0, isW3CLengthUnits);
2477         }
2478 
2479         /**
2480          * Returns the length (span) to use. If the value represents
2481          * a percentage, it is scaled based on <code>currentValue</code>.
2482          */
2483         float getValue(float currentValue) {
2484             return getValue(currentValue, false);
2485         }
2486         float getValue(float currentValue, boolean isW3CLengthUnits) {
2487             if (percentage) {
2488                 return span * currentValue;
2489             }
2490             return LengthUnit.getValue(span, units, isW3CLengthUnits);
2491         }
2492 
2493         /**
2494          * Returns true if the length represents a percentage of the
2495          * containing box.
2496          */
2497         boolean isPercentage() {
2498             return percentage;
2499         }
2500 
2501         Object parseCssValue(String value) {
2502             LengthValue lv;
2503             try {
2504                 // Assume pixels
2505                 float absolute = Float.valueOf(value).floatValue();
2506                 lv = new LengthValue();
2507                 lv.span = absolute;
2508             } catch (NumberFormatException nfe) {
2509                 // Not pixels, use LengthUnit
2510                 LengthUnit lu = new LengthUnit(value,
2511                                                LengthUnit.UNINITALIZED_LENGTH,
2512                                                0);
2513 
2514                 // PENDING: currently, we only support absolute values and
2515                 // percentages.
2516                 switch (lu.type) {
2517                 case 0:
2518                     // Absolute
2519                     lv = new LengthValue();
2520                     lv.span =
2521                         (mayBeNegative) ? lu.value : Math.max(0, lu.value);
2522                     lv.units = lu.units;
2523                     break;
2524                 case 1:
2525                     // %
2526                     lv = new LengthValue();
2527                     lv.span = Math.max(0, Math.min(1, lu.value));
2528                     lv.percentage = true;
2529                     break;
2530                 default:
2531                     return null;
2532                 }
2533             }
2534             lv.svalue = value;
2535             return lv;
2536         }
2537 
2538         Object parseHtmlValue(String value) {
2539             if (value.equals(HTML.NULL_ATTRIBUTE_VALUE)) {
2540                 value = "1";
2541             }
2542             return parseCssValue(value);
2543         }
2544         /**
2545          * Converts a <code>StyleConstants</code> attribute value to
2546          * a CSS attribute value.  If there is no conversion,
2547          * returns <code>null</code>.  By default, there is no conversion.
2548          *
2549          * @param key the <code>StyleConstants</code> attribute
2550          * @param value the value of a <code>StyleConstants</code>
2551          *   attribute to be converted
2552          * @return the CSS value that represents the
2553          *   <code>StyleConstants</code> value
2554          */
2555         Object fromStyleConstants(StyleConstants key, Object value) {
2556             LengthValue v = new LengthValue();
2557             v.svalue = value.toString();
2558             v.span = ((Float)value).floatValue();
2559             return v;
2560         }
2561 
2562         /**
2563          * Converts a CSS attribute value to a <code>StyleConstants</code>
2564          * value.  If there is no conversion, returns <code>null</code>.
2565          * By default, there is no conversion.
2566          *
2567          * @param key the <code>StyleConstants</code> attribute
2568          * @return the <code>StyleConstants</code> attribute value that
2569          *   represents the CSS attribute value
2570          */
2571         Object toStyleConstants(StyleConstants key, View v) {
2572             return Float.valueOf(getValue(false));
2573         }
2574 
2575         /** If true, span is a percentage value, and that to determine
2576          * the length another value needs to be passed in. */
2577         boolean percentage;
2578         /** Either the absolute value (percentage == false) or
2579          * a percentage value. */
2580         float span;
2581 
2582         String units = null;
2583     }
2584 
2585 
2586     /**
2587      * BorderWidthValue is used to model BORDER_XXX_WIDTH and adds support
2588      * for the thin/medium/thick values.
2589      */
2590     @SuppressWarnings("serial") // Same-version serialization only
2591     static class BorderWidthValue extends LengthValue {
2592         BorderWidthValue(String svalue, int index) {
2593             this.svalue = svalue;
2594             span = values[index];
2595             percentage = false;
2596         }
2597 
2598         Object parseCssValue(String value) {
2599             if (value != null) {
2600                 if (value.equals("thick")) {
2601                     return new BorderWidthValue(value, 2);
2602                 }
2603                 else if (value.equals("medium")) {
2604                     return new BorderWidthValue(value, 1);
2605                 }
2606                 else if (value.equals("thin")) {
2607                     return new BorderWidthValue(value, 0);
2608                 }
2609             }
2610             // Assume its a length.
2611             return super.parseCssValue(value);
2612         }
2613 
2614         Object parseHtmlValue(String value) {
2615             if (value == HTML.NULL_ATTRIBUTE_VALUE) {
2616                 return parseCssValue("medium");
2617             }
2618             return parseCssValue(value);
2619         }
2620 
2621         /** Values used to represent border width. */
2622         private static final float[] values = { 1, 2, 4 };
2623    }
2624 
2625 
2626     /**
2627      * Handles uniquing of CSS values, like lists, and background image
2628      * repeating.
2629      */
2630     @SuppressWarnings("serial") // Same-version serialization only
2631     static class CssValueMapper extends CssValue {
2632         Object parseCssValue(String value) {
2633             Object retValue = cssValueToInternalValueMap.get(value);
2634             if (retValue == null) {
2635                 retValue = cssValueToInternalValueMap.get(value.toLowerCase());
2636             }
2637             return retValue;
2638         }
2639 
2640 
2641         Object parseHtmlValue(String value) {
2642             Object retValue = htmlValueToCssValueMap.get(value);
2643             if (retValue == null) {
2644                 retValue = htmlValueToCssValueMap.get(value.toLowerCase());
2645             }
2646             return retValue;
2647         }
2648     }
2649 
2650 
2651     /**
2652      * Used for background images, to represent the position.
2653      */
2654     @SuppressWarnings("serial") // Same-version serialization only
2655     static class BackgroundPosition extends CssValue {
2656         float horizontalPosition;
2657         float verticalPosition;
2658         // bitmask: bit 0, horizontal relative, bit 1 horizontal relative to
2659         // font size, 2 vertical relative to size, 3 vertical relative to
2660         // font size.
2661         //
2662         short relative;
2663 
2664         Object parseCssValue(String value) {
2665             // 'top left' and 'left top' both mean the same as '0% 0%'.
2666             // 'top', 'top center' and 'center top' mean the same as '50% 0%'.
2667             // 'right top' and 'top right' mean the same as '100% 0%'.
2668             // 'left', 'left center' and 'center left' mean the same as
2669             //        '0% 50%'.
2670             // 'center' and 'center center' mean the same as '50% 50%'.
2671             // 'right', 'right center' and 'center right' mean the same as
2672             //        '100% 50%'.
2673             // 'bottom left' and 'left bottom' mean the same as '0% 100%'.
2674             // 'bottom', 'bottom center' and 'center bottom' mean the same as
2675             //        '50% 100%'.
2676             // 'bottom right' and 'right bottom' mean the same as '100% 100%'.
2677             String[]  strings = CSS.parseStrings(value);
2678             int count = strings.length;
2679             BackgroundPosition bp = new BackgroundPosition();
2680             bp.relative = 5;
2681             bp.svalue = value;
2682 
2683             if (count > 0) {
2684                 // bit 0 for vert, 1 hor, 2 for center
2685                 short found = 0;
2686                 int index = 0;
2687                 while (index < count) {
2688                     // First, check for keywords
2689                     String string = strings[index++];
2690                     if (string.equals("center")) {
2691                         found |= 4;
2692                         continue;
2693                     }
2694                     else {
2695                         if ((found & 1) == 0) {
2696                             if (string.equals("top")) {
2697                                 found |= 1;
2698                             }
2699                             else if (string.equals("bottom")) {
2700                                 found |= 1;
2701                                 bp.verticalPosition = 1;
2702                                 continue;
2703                             }
2704                         }
2705                         if ((found & 2) == 0) {
2706                             if (string.equals("left")) {
2707                                 found |= 2;
2708                                 bp.horizontalPosition = 0;
2709                             }
2710                             else if (string.equals("right")) {
2711                                 found |= 2;
2712                                 bp.horizontalPosition = 1;
2713                             }
2714                         }
2715                     }
2716                 }
2717                 if (found != 0) {
2718                     if ((found & 1) == 1) {
2719                         if ((found & 2) == 0) {
2720                             // vert and no horiz.
2721                             bp.horizontalPosition = .5f;
2722                         }
2723                     }
2724                     else if ((found & 2) == 2) {
2725                         // horiz and no vert.
2726                         bp.verticalPosition = .5f;
2727                     }
2728                     else {
2729                         // no horiz, no vert, but center
2730                         bp.horizontalPosition = bp.verticalPosition = .5f;
2731                     }
2732                 }
2733                 else {
2734                     // Assume lengths
2735                     LengthUnit lu = new LengthUnit(strings[0], (short)0, 0f);
2736 
2737                     if (lu.type == 0) {
2738                         bp.horizontalPosition = lu.value;
2739                         bp.relative = (short)(1 ^ bp.relative);
2740                     }
2741                     else if (lu.type == 1) {
2742                         bp.horizontalPosition = lu.value;
2743                     }
2744                     else if (lu.type == 3) {
2745                         bp.horizontalPosition = lu.value;
2746                         bp.relative = (short)((1 ^ bp.relative) | 2);
2747                     }
2748                     if (count > 1) {
2749                         lu = new LengthUnit(strings[1], (short)0, 0f);
2750 
2751                         if (lu.type == 0) {
2752                             bp.verticalPosition = lu.value;
2753                             bp.relative = (short)(4 ^ bp.relative);
2754                         }
2755                         else if (lu.type == 1) {
2756                             bp.verticalPosition = lu.value;
2757                         }
2758                         else if (lu.type == 3) {
2759                             bp.verticalPosition = lu.value;
2760                             bp.relative = (short)((4 ^ bp.relative) | 8);
2761                         }
2762                     }
2763                     else {
2764                         bp.verticalPosition = .5f;
2765                     }
2766                 }
2767             }
2768             return bp;
2769         }
2770 
2771         boolean isHorizontalPositionRelativeToSize() {
2772             return ((relative & 1) == 1);
2773         }
2774 
2775         boolean isHorizontalPositionRelativeToFontSize() {
2776             return ((relative & 2) == 2);
2777         }
2778 
2779         float getHorizontalPosition() {
2780             return horizontalPosition;
2781         }
2782 
2783         boolean isVerticalPositionRelativeToSize() {
2784             return ((relative & 4) == 4);
2785         }
2786 
2787         boolean isVerticalPositionRelativeToFontSize() {
2788             return ((relative & 8) == 8);
2789         }
2790 
2791         float getVerticalPosition() {
2792             return verticalPosition;
2793         }
2794     }
2795 
2796 
2797     /**
2798      * Used for BackgroundImages.
2799      */
2800     @SuppressWarnings("serial") // Same-version serialization only
2801     static class BackgroundImage extends CssValue {
2802         private boolean    loadedImage;
2803         private ImageIcon  image;
2804 
2805         Object parseCssValue(String value) {
2806             BackgroundImage retValue = new BackgroundImage();
2807             retValue.svalue = value;
2808             return retValue;
2809         }
2810 
2811         Object parseHtmlValue(String value) {
2812             return parseCssValue(value);
2813         }
2814 
2815         // PENDING: this base is wrong for linked style sheets.
2816         ImageIcon getImage(URL base) {
2817             if (!loadedImage) {
2818                 synchronized(this) {
2819                     if (!loadedImage) {
2820                         URL url = CSS.getURL(base, svalue);
2821                         loadedImage = true;
2822                         if (url != null) {
2823                             image = new ImageIcon();
2824                             Image tmpImg = Toolkit.getDefaultToolkit().createImage(url);
2825                             if (tmpImg != null) {
2826                                 image.setImage(tmpImg);
2827                             }
2828                         }
2829                     }
2830                 }
2831             }
2832             return image;
2833         }
2834     }
2835 
2836     /**
2837      * Parses a length value, this is used internally, and never added
2838      * to an AttributeSet or returned to the developer.
2839      */
2840     @SuppressWarnings("serial") // Same-version serialization only
2841     static class LengthUnit implements Serializable {
2842         static Hashtable<String, Float> lengthMapping = new Hashtable<String, Float>(6);
2843         static Hashtable<String, Float> w3cLengthMapping = new Hashtable<String, Float>(6);
2844         static {
2845             lengthMapping.put("pt", Float.valueOf(1f));
2846             // Not sure about 1.3, determined by experiementation.
2847             lengthMapping.put("px", Float.valueOf(1.3f));
2848             lengthMapping.put("mm", Float.valueOf(2.83464f));
2849             lengthMapping.put("cm", Float.valueOf(28.3464f));
2850             lengthMapping.put("pc", Float.valueOf(12f));
2851             lengthMapping.put("in", Float.valueOf(72f));
2852             int res = 72;
2853             try {
2854                 res = Toolkit.getDefaultToolkit().getScreenResolution();
2855             } catch (HeadlessException e) {
2856             }
2857             // mapping according to the CSS2 spec
2858             w3cLengthMapping.put("pt", Float.valueOf(res/72f));
2859             w3cLengthMapping.put("px", Float.valueOf(1f));
2860             w3cLengthMapping.put("mm", Float.valueOf(res/25.4f));
2861             w3cLengthMapping.put("cm", Float.valueOf(res/2.54f));
2862             w3cLengthMapping.put("pc", Float.valueOf(res/6f));
2863             w3cLengthMapping.put("in", Float.valueOf((float)res));
2864         }
2865 
2866         LengthUnit(String value, short defaultType, float defaultValue) {
2867             parse(value, defaultType, defaultValue);
2868         }
2869 
2870         void parse(String value, short defaultType, float defaultValue) {
2871             type = defaultType;
2872             this.value = defaultValue;
2873 
2874             int length = value.length();
2875             if (length > 0 && value.charAt(length - 1) == '%') {
2876                 try {
2877                     this.value = Float.valueOf(value.substring(0, length - 1)).
2878                                                floatValue() / 100.0f;
2879                     type = 1;
2880                 }
2881                 catch (NumberFormatException nfe) { }
2882             }
2883             if (length >= 2) {
2884                 units = value.substring(length - 2, length);
2885                 Float scale = lengthMapping.get(units);
2886                 if (scale != null) {
2887                     try {
2888                         this.value = Float.valueOf(value.substring(0,
2889                                length - 2)).floatValue();
2890                         type = 0;
2891                     }
2892                     catch (NumberFormatException nfe) { }
2893                 }
2894                 else if (units.equals("em") ||
2895                          units.equals("ex")) {
2896                     try {
2897                         this.value = Float.valueOf(value.substring(0,
2898                                       length - 2)).floatValue();
2899                         type = 3;
2900                     }
2901                     catch (NumberFormatException nfe) { }
2902                 }
2903                 else if (value.equals("larger")) {
2904                     this.value = 2f;
2905                     type = 2;
2906                 }
2907                 else if (value.equals("smaller")) {
2908                     this.value = -2;
2909                     type = 2;
2910                 }
2911                 else {
2912                     // treat like points.
2913                     try {
2914                         this.value = Float.valueOf(value).floatValue();
2915                         type = 0;
2916                     } catch (NumberFormatException nfe) {}
2917                 }
2918             }
2919             else if (length > 0) {
2920                 // treat like points.
2921                 try {
2922                     this.value = Float.valueOf(value).floatValue();
2923                     type = 0;
2924                 } catch (NumberFormatException nfe) {}
2925             }
2926         }
2927 
2928         float getValue(boolean w3cLengthUnits) {
2929             Hashtable<String, Float> mapping = (w3cLengthUnits) ? w3cLengthMapping : lengthMapping;
2930             float scale = 1;
2931             if (units != null) {
2932                 Float scaleFloat = mapping.get(units);
2933                 if (scaleFloat != null) {
2934                     scale = scaleFloat.floatValue();
2935                 }
2936             }
2937             return this.value * scale;
2938 
2939         }
2940 
2941         static float getValue(float value, String units, Boolean w3cLengthUnits) {
2942             Hashtable<String, Float> mapping = (w3cLengthUnits) ? w3cLengthMapping : lengthMapping;
2943             float scale = 1;
2944             if (units != null) {
2945                 Float scaleFloat = mapping.get(units);
2946                 if (scaleFloat != null) {
2947                     scale = scaleFloat.floatValue();
2948                 }
2949             }
2950             return value * scale;
2951         }
2952 
2953         public String toString() {
2954             return type + " " + value;
2955         }
2956 
2957         // 0 - value indicates real value
2958         // 1 - % value, value relative to depends upon key.
2959         //     50% will have a value = .5
2960         // 2 - add value to parent value.
2961         // 3 - em/ex relative to font size of element (except for
2962         //     font-size, which is relative to parent).
2963         short type;
2964         float value;
2965         String units = null;
2966 
2967 
2968         static final short UNINITALIZED_LENGTH = (short)10;
2969     }
2970 
2971 
2972     /**
2973      * Class used to parse font property. The font property is shorthand
2974      * for the other font properties. This expands the properties, placing
2975      * them in the attributeset.
2976      */
2977     static class ShorthandFontParser {
2978         /**
2979          * Parses the shorthand font string <code>value</code>, placing the
2980          * result in <code>attr</code>.
2981          */
2982         static void parseShorthandFont(CSS css, String value,
2983                                        MutableAttributeSet attr) {
2984             // font is of the form:
2985             // [ <font-style> || <font-variant> || <font-weight> ]? <font-size>
2986             //   [ / <line-height> ]? <font-family>
2987             String[]   strings = CSS.parseStrings(value);
2988             int        count = strings.length;
2989             int        index = 0;
2990             // bitmask, 1 for style, 2 for variant, 3 for weight
2991             short      found = 0;
2992             int        maxC = Math.min(3, count);
2993 
2994             // Check for font-style font-variant font-weight
2995             while (index < maxC) {
2996                 if ((found & 1) == 0 && isFontStyle(strings[index])) {
2997                     css.addInternalCSSValue(attr, CSS.Attribute.FONT_STYLE,
2998                                             strings[index++]);
2999                     found |= 1;
3000                 }
3001                 else if ((found & 2) == 0 && isFontVariant(strings[index])) {
3002                     css.addInternalCSSValue(attr, CSS.Attribute.FONT_VARIANT,
3003                                             strings[index++]);
3004                     found |= 2;
3005                 }
3006                 else if ((found & 4) == 0 && isFontWeight(strings[index])) {
3007                     css.addInternalCSSValue(attr, CSS.Attribute.FONT_WEIGHT,
3008                                             strings[index++]);
3009                     found |= 4;
3010                 }
3011                 else if (strings[index].equals("normal")) {
3012                     index++;
3013                 }
3014                 else {
3015                     break;
3016                 }
3017             }
3018             if ((found & 1) == 0) {
3019                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_STYLE,
3020                                         "normal");
3021             }
3022             if ((found & 2) == 0) {
3023                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_VARIANT,
3024                                         "normal");
3025             }
3026             if ((found & 4) == 0) {
3027                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_WEIGHT,
3028                                         "normal");
3029             }
3030 
3031             // string at index should be the font-size
3032             if (index < count) {
3033                 String fontSize = strings[index];
3034                 int slashIndex = fontSize.indexOf('/');
3035 
3036                 if (slashIndex != -1) {
3037                     fontSize = fontSize.substring(0, slashIndex);
3038                     strings[index] = strings[index].substring(slashIndex);
3039                 }
3040                 else {
3041                     index++;
3042                 }
3043                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_SIZE,
3044                                         fontSize);
3045             }
3046             else {
3047                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_SIZE,
3048                                         "medium");
3049             }
3050 
3051             // Check for line height
3052             if (index < count && strings[index].startsWith("/")) {
3053                 String lineHeight = null;
3054                 if (strings[index].equals("/")) {
3055                     if (++index < count) {
3056                         lineHeight = strings[index++];
3057                     }
3058                 }
3059                 else {
3060                     lineHeight = strings[index++].substring(1);
3061                 }
3062                 // line height
3063                 if (lineHeight != null) {
3064                     css.addInternalCSSValue(attr, CSS.Attribute.LINE_HEIGHT,
3065                                             lineHeight);
3066                 }
3067                 else {
3068                     css.addInternalCSSValue(attr, CSS.Attribute.LINE_HEIGHT,
3069                                             "normal");
3070                 }
3071             }
3072             else {
3073                 css.addInternalCSSValue(attr, CSS.Attribute.LINE_HEIGHT,
3074                                         "normal");
3075             }
3076 
3077             // remainder of strings are font-family
3078             if (index < count) {
3079                 String family = strings[index++];
3080 
3081                 while (index < count) {
3082                     family += " " + strings[index++];
3083                 }
3084                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_FAMILY,
3085                                         family);
3086             }
3087             else {
3088                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_FAMILY,
3089                                         Font.SANS_SERIF);
3090             }
3091         }
3092 
3093         private static boolean isFontStyle(String string) {
3094             return (string.equals("italic") ||
3095                     string.equals("oblique"));
3096         }
3097 
3098         private static boolean isFontVariant(String string) {
3099             return (string.equals("small-caps"));
3100         }
3101 
3102         private static boolean isFontWeight(String string) {
3103             if (string.equals("bold") || string.equals("bolder") ||
3104                 string.equals("italic") || string.equals("lighter")) {
3105                 return true;
3106             }
3107             // test for 100-900
3108             return (string.length() == 3 &&
3109                     string.charAt(0) >= '1' && string.charAt(0) <= '9' &&
3110                     string.charAt(1) == '0' && string.charAt(2) == '0');
3111         }
3112 
3113     }
3114 
3115 
3116     /**
3117      * Parses the background property into its intrinsic values.
3118      */
3119     static class ShorthandBackgroundParser {
3120         /**
3121          * Parses the shorthand font string <code>value</code>, placing the
3122          * result in <code>attr</code>.
3123          */
3124         static void parseShorthandBackground(CSS css, String value,
3125                                              MutableAttributeSet attr) {
3126             String[] strings = parseStrings(value);
3127             int count = strings.length;
3128             int index = 0;
3129             // bitmask: 0 for image, 1 repeat, 2 attachment, 3 position,
3130             //          4 color
3131             short found = 0;
3132 
3133             while (index < count) {
3134                 String string = strings[index++];
3135                 if ((found & 1) == 0 && isImage(string)) {
3136                     css.addInternalCSSValue(attr, CSS.Attribute.
3137                                             BACKGROUND_IMAGE, string);
3138                     found |= 1;
3139                 }
3140                 else if ((found & 2) == 0 && isRepeat(string)) {
3141                     css.addInternalCSSValue(attr, CSS.Attribute.
3142                                             BACKGROUND_REPEAT, string);
3143                     found |= 2;
3144                 }
3145                 else if ((found & 4) == 0 && isAttachment(string)) {
3146                     css.addInternalCSSValue(attr, CSS.Attribute.
3147                                             BACKGROUND_ATTACHMENT, string);
3148                     found |= 4;
3149                 }
3150                 else if ((found & 8) == 0 && isPosition(string)) {
3151                     if (index < count && isPosition(strings[index])) {
3152                         css.addInternalCSSValue(attr, CSS.Attribute.
3153                                                 BACKGROUND_POSITION,
3154                                                 string + " " +
3155                                                 strings[index++]);
3156                     }
3157                     else {
3158                         css.addInternalCSSValue(attr, CSS.Attribute.
3159                                                 BACKGROUND_POSITION, string);
3160                     }
3161                     found |= 8;
3162                 }
3163                 else if ((found & 16) == 0 && isColor(string)) {
3164                     css.addInternalCSSValue(attr, CSS.Attribute.
3165                                             BACKGROUND_COLOR, string);
3166                     found |= 16;
3167                 }
3168             }
3169             if ((found & 1) == 0) {
3170                 css.addInternalCSSValue(attr, CSS.Attribute.BACKGROUND_IMAGE,
3171                                         null);
3172             }
3173             if ((found & 2) == 0) {
3174                 css.addInternalCSSValue(attr, CSS.Attribute.BACKGROUND_REPEAT,
3175                                         "repeat");
3176             }
3177             if ((found & 4) == 0) {
3178                 css.addInternalCSSValue(attr, CSS.Attribute.
3179                                         BACKGROUND_ATTACHMENT, "scroll");
3180             }
3181             if ((found & 8) == 0) {
3182                 css.addInternalCSSValue(attr, CSS.Attribute.
3183                                         BACKGROUND_POSITION, null);
3184             }
3185             // Currently, there is no good way to express this.
3186             /*
3187             if ((found & 16) == 0) {
3188                 css.addInternalCSSValue(attr, CSS.Attribute.BACKGROUND_COLOR,
3189                                         null);
3190             }
3191             */
3192         }
3193 
3194         static boolean isImage(String string) {
3195             return (string.startsWith("url(") && string.endsWith(")"));
3196         }
3197 
3198         static boolean isRepeat(String string) {
3199             return (string.equals("repeat-x") || string.equals("repeat-y") ||
3200                     string.equals("repeat") || string.equals("no-repeat"));
3201         }
3202 
3203         static boolean isAttachment(String string) {
3204             return (string.equals("fixed") || string.equals("scroll"));
3205         }
3206 
3207         static boolean isPosition(String string) {
3208             return (string.equals("top") || string.equals("bottom") ||
3209                     string.equals("left") || string.equals("right") ||
3210                     string.equals("center") ||
3211                     (string.length() > 0 &&
3212                      Character.isDigit(string.charAt(0))));
3213         }
3214 
3215         static boolean isColor(String string) {
3216             return (CSS.stringToColor(string) != null);
3217         }
3218     }
3219 
3220 
3221     /**
3222      * Used to parser margin and padding.
3223      */
3224     static class ShorthandMarginParser {
3225         /**
3226          * Parses the shorthand margin/padding/border string
3227          * <code>value</code>, placing the result in <code>attr</code>.
3228          * <code>names</code> give the 4 instrinsic property names.
3229          */
3230         static void parseShorthandMargin(CSS css, String value,
3231                                          MutableAttributeSet attr,
3232                                          CSS.Attribute[] names) {
3233             String[] strings = parseStrings(value);
3234             int count = strings.length;
3235             int index = 0;
3236             switch (count) {
3237             case 0:
3238                 // empty string
3239                 return;
3240             case 1:
3241                 // Identifies all values.
3242                 for (int counter = 0; counter < 4; counter++) {
3243                     css.addInternalCSSValue(attr, names[counter], strings[0]);
3244                 }
3245                 break;
3246             case 2:
3247                 // 0 & 2 = strings[0], 1 & 3 = strings[1]
3248                 css.addInternalCSSValue(attr, names[0], strings[0]);
3249                 css.addInternalCSSValue(attr, names[2], strings[0]);
3250                 css.addInternalCSSValue(attr, names[1], strings[1]);
3251                 css.addInternalCSSValue(attr, names[3], strings[1]);
3252                 break;
3253             case 3:
3254                 css.addInternalCSSValue(attr, names[0], strings[0]);
3255                 css.addInternalCSSValue(attr, names[1], strings[1]);
3256                 css.addInternalCSSValue(attr, names[2], strings[2]);
3257                 css.addInternalCSSValue(attr, names[3], strings[1]);
3258                 break;
3259             default:
3260                 for (int counter = 0; counter < 4; counter++) {
3261                     css.addInternalCSSValue(attr, names[counter],
3262                                             strings[counter]);
3263                 }
3264                 break;
3265             }
3266         }
3267     }
3268 
3269     static class ShorthandBorderParser {
3270         static Attribute[] keys = {
3271             Attribute.BORDER_TOP, Attribute.BORDER_RIGHT,
3272             Attribute.BORDER_BOTTOM, Attribute.BORDER_LEFT,
3273         };
3274 
3275         static void parseShorthandBorder(MutableAttributeSet attributes,
3276                                             CSS.Attribute key, String value) {
3277             Object[] parts = new Object[CSSBorder.PARSERS.length];
3278             String[] strings = parseStrings(value);
3279             for (String s : strings) {
3280                 boolean valid = false;
3281                 for (int i = 0; i < parts.length; i++) {
3282                     Object v = CSSBorder.PARSERS[i].parseCssValue(s);
3283                     if (v != null) {
3284                         if (parts[i] == null) {
3285                             parts[i] = v;
3286                             valid = true;
3287                         }
3288                         break;
3289                     }
3290                 }
3291                 if (!valid) {
3292                     // Part is non-parseable or occurred more than once.
3293                     return;
3294                 }
3295             }
3296 
3297             // Unspecified parts get default values.
3298             for (int i = 0; i < parts.length; i++) {
3299                 if (parts[i] == null) {
3300                     parts[i] = CSSBorder.DEFAULTS[i];
3301                 }
3302             }
3303 
3304             // Dispatch collected values to individual properties.
3305             for (int i = 0; i < keys.length; i++) {
3306                 if ((key == Attribute.BORDER) || (key == keys[i])) {
3307                     for (int k = 0; k < parts.length; k++) {
3308                         attributes.addAttribute(
3309                                         CSSBorder.ATTRIBUTES[k][i], parts[k]);
3310                     }
3311                 }
3312             }
3313         }
3314     }
3315 
3316     /**
3317      * Calculate the requirements needed to tile the requirements
3318      * given by the iterator that would be tiled.  The calculation
3319      * takes into consideration margin and border spacing.
3320      */
3321     static SizeRequirements calculateTiledRequirements(LayoutIterator iter, SizeRequirements r) {
3322         long minimum = 0;
3323         long maximum = 0;
3324         long preferred = 0;
3325         int lastMargin = 0;
3326         int totalSpacing = 0;
3327         int n = iter.getCount();
3328         for (int i = 0; i < n; i++) {
3329             iter.setIndex(i);
3330             int margin0 = lastMargin;
3331             int margin1 = (int) iter.getLeadingCollapseSpan();
3332             totalSpacing += Math.max(margin0, margin1);
3333             preferred += (int) iter.getPreferredSpan(0);
3334             minimum += iter.getMinimumSpan(0);
3335             maximum += iter.getMaximumSpan(0);
3336 
3337             lastMargin = (int) iter.getTrailingCollapseSpan();
3338         }
3339         totalSpacing += lastMargin;
3340         totalSpacing += 2 * iter.getBorderWidth();
3341 
3342         // adjust for the spacing area
3343         minimum += totalSpacing;
3344         preferred += totalSpacing;
3345         maximum += totalSpacing;
3346 
3347         // set return value
3348         if (r == null) {
3349             r = new SizeRequirements();
3350         }
3351         r.minimum = (minimum > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)minimum;
3352         r.preferred = (preferred > Integer.MAX_VALUE) ? Integer.MAX_VALUE :(int) preferred;
3353         r.maximum = (maximum > Integer.MAX_VALUE) ? Integer.MAX_VALUE :(int) maximum;
3354         return r;
3355     }
3356 
3357     /**
3358      * Calculate a tiled layout for the given iterator.
3359      * This should be done collapsing the neighboring
3360      * margins to be a total of the maximum of the two
3361      * neighboring margin areas as described in the CSS spec.
3362      */
3363     static void calculateTiledLayout(LayoutIterator iter, int targetSpan) {
3364 
3365         /*
3366          * first pass, calculate the preferred sizes, adjustments needed because
3367          * of margin collapsing, and the flexibility to adjust the sizes.
3368          */
3369         long preferred = 0;
3370         long currentPreferred;
3371         int lastMargin = 0;
3372         int totalSpacing = 0;
3373         int n = iter.getCount();
3374         int adjustmentWeightsCount = LayoutIterator.WorstAdjustmentWeight + 1;
3375         //max gain we can get adjusting elements with adjustmentWeight <= i
3376         long gain[] = new long[adjustmentWeightsCount];
3377         //max loss we can get adjusting elements with adjustmentWeight <= i
3378         long loss[] = new long[adjustmentWeightsCount];
3379 
3380         for (int i = 0; i < adjustmentWeightsCount; i++) {
3381             gain[i] = loss[i] = 0;
3382         }
3383         for (int i = 0; i < n; i++) {
3384             iter.setIndex(i);
3385             int margin0 = lastMargin;
3386             int margin1 = (int) iter.getLeadingCollapseSpan();
3387 
3388             iter.setOffset(Math.max(margin0, margin1));
3389             totalSpacing += iter.getOffset();
3390 
3391             currentPreferred = (long)iter.getPreferredSpan(targetSpan);
3392             iter.setSpan((int) currentPreferred);
3393             preferred += currentPreferred;
3394             gain[iter.getAdjustmentWeight()] +=
3395                 (long)iter.getMaximumSpan(targetSpan) - currentPreferred;
3396             loss[iter.getAdjustmentWeight()] +=
3397                 currentPreferred - (long)iter.getMinimumSpan(targetSpan);
3398             lastMargin = (int) iter.getTrailingCollapseSpan();
3399         }
3400         totalSpacing += lastMargin;
3401         totalSpacing += 2 * iter.getBorderWidth();
3402 
3403         for (int i = 1; i < adjustmentWeightsCount; i++) {
3404             gain[i] += gain[i - 1];
3405             loss[i] += loss[i - 1];
3406         }
3407 
3408         /*
3409          * Second pass, expand or contract by as much as possible to reach
3410          * the target span.  This takes the margin collapsing into account
3411          * prior to adjusting the span.
3412          */
3413 
3414         // determine the adjustment to be made
3415         int allocated = targetSpan - totalSpacing;
3416         long desiredAdjustment = allocated - preferred;
3417         long adjustmentsArray[] = (desiredAdjustment > 0) ? gain : loss;
3418         desiredAdjustment = Math.abs(desiredAdjustment);
3419         int adjustmentLevel = 0;
3420         for (;adjustmentLevel <= LayoutIterator.WorstAdjustmentWeight;
3421              adjustmentLevel++) {
3422             // adjustmentsArray[] is sorted. I do not bother about
3423             // binary search though
3424             if (adjustmentsArray[adjustmentLevel] >= desiredAdjustment) {
3425                 break;
3426             }
3427         }
3428         float adjustmentFactor = 0.0f;
3429         if (adjustmentLevel <= LayoutIterator.WorstAdjustmentWeight) {
3430             desiredAdjustment -= (adjustmentLevel > 0) ?
3431                 adjustmentsArray[adjustmentLevel - 1] : 0;
3432             if (desiredAdjustment != 0) {
3433                 float maximumAdjustment =
3434                     adjustmentsArray[adjustmentLevel] -
3435                     ((adjustmentLevel > 0) ?
3436                      adjustmentsArray[adjustmentLevel - 1] : 0
3437                      );
3438                 adjustmentFactor = desiredAdjustment / maximumAdjustment;
3439             }
3440         }
3441         // make the adjustments
3442         int totalOffset = (int)iter.getBorderWidth();
3443         for (int i = 0; i < n; i++) {
3444             iter.setIndex(i);
3445             iter.setOffset( iter.getOffset() + totalOffset);
3446             if (iter.getAdjustmentWeight() < adjustmentLevel) {
3447                 iter.setSpan((int)
3448                              ((allocated > preferred) ?
3449                               Math.floor(iter.getMaximumSpan(targetSpan)) :
3450                               Math.ceil(iter.getMinimumSpan(targetSpan))
3451                               )
3452                              );
3453             } else if (iter.getAdjustmentWeight() == adjustmentLevel) {
3454                 int availableSpan = (allocated > preferred) ?
3455                     (int) iter.getMaximumSpan(targetSpan) - iter.getSpan() :
3456                     iter.getSpan() - (int) iter.getMinimumSpan(targetSpan);
3457                 int adj = (int)Math.floor(adjustmentFactor * availableSpan);
3458                 iter.setSpan(iter.getSpan() +
3459                              ((allocated > preferred) ? adj : -adj));
3460             }
3461             totalOffset = (int) Math.min((long) iter.getOffset() +
3462                                          (long) iter.getSpan(),
3463                                          Integer.MAX_VALUE);
3464         }
3465 
3466         // while rounding we could lose several pixels.
3467         int roundError = targetSpan - totalOffset -
3468             (int)iter.getTrailingCollapseSpan() -
3469             (int)iter.getBorderWidth();
3470         int adj = (roundError > 0) ? 1 : -1;
3471         roundError *= adj;
3472 
3473         boolean canAdjust = true;
3474         while (roundError > 0 && canAdjust) {
3475             // check for infinite loop
3476             canAdjust = false;
3477             int offsetAdjust = 0;
3478             // try to distribute roundError. one pixel per cell
3479             for (int i = 0; i < n; i++) {
3480                 iter.setIndex(i);
3481                 iter.setOffset(iter.getOffset() + offsetAdjust);
3482                 int curSpan = iter.getSpan();
3483                 if (roundError > 0) {
3484                     int boundGap = (adj > 0) ?
3485                         (int)Math.floor(iter.getMaximumSpan(targetSpan)) - curSpan :
3486                         curSpan - (int)Math.ceil(iter.getMinimumSpan(targetSpan));
3487                     if (boundGap >= 1) {
3488                         canAdjust = true;
3489                         iter.setSpan(curSpan + adj);
3490                         offsetAdjust += adj;
3491                         roundError--;
3492                     }
3493                 }
3494             }
3495         }
3496     }
3497 
3498     /**
3499      * An iterator to express the requirements to use when computing
3500      * layout.
3501      */
3502     interface LayoutIterator {
3503 
3504         void setOffset(int offs);
3505 
3506         int getOffset();
3507 
3508         void setSpan(int span);
3509 
3510         int getSpan();
3511 
3512         int getCount();
3513 
3514         void setIndex(int i);
3515 
3516         float getMinimumSpan(float parentSpan);
3517 
3518         float getPreferredSpan(float parentSpan);
3519 
3520         float getMaximumSpan(float parentSpan);
3521 
3522         int getAdjustmentWeight(); //0 is the best weight WorstAdjustmentWeight is a worst one
3523 
3524         //float getAlignment();
3525 
3526         float getBorderWidth();
3527 
3528         float getLeadingCollapseSpan();
3529 
3530         float getTrailingCollapseSpan();
3531         public static final int WorstAdjustmentWeight = 2;
3532     }
3533 
3534     //
3535     // Serialization support
3536     //
3537 
3538     private void writeObject(java.io.ObjectOutputStream s)
3539         throws IOException
3540     {
3541         s.defaultWriteObject();
3542 
3543         // Determine what values in valueConvertor need to be written out.
3544         Enumeration<?> keys = valueConvertor.keys();
3545         s.writeInt(valueConvertor.size());
3546         if (keys != null) {
3547             while (keys.hasMoreElements()) {
3548                 Object key = keys.nextElement();
3549                 Object value = valueConvertor.get(key);
3550                 if (!(key instanceof Serializable) &&
3551                     (key = StyleContext.getStaticAttributeKey(key)) == null) {
3552                     // Should we throw an exception here?
3553                     key = null;
3554                     value = null;
3555                 }
3556                 else if (!(value instanceof Serializable) &&
3557                     (value = StyleContext.getStaticAttributeKey(value)) == null){
3558                     // Should we throw an exception here?
3559                     key = null;
3560                     value = null;
3561                 }
3562                 s.writeObject(key);
3563                 s.writeObject(value);
3564             }
3565         }
3566     }
3567 
3568     private void readObject(ObjectInputStream s)
3569       throws ClassNotFoundException, IOException
3570     {
3571         ObjectInputStream.GetField f = s.readFields();
3572         int newBaseFontSize = f.get("baseFontSize", 0);
3573         setBaseFontSize(newBaseFontSize);
3574 
3575         // Reconstruct the hashtable.
3576         int numValues = s.readInt();
3577         valueConvertor = new Hashtable<>();
3578         while (numValues-- > 0) {
3579             Object key = s.readObject();
3580             Object value = s.readObject();
3581             Object staticKey = StyleContext.getStaticAttribute(key);
3582             if (staticKey != null) {
3583                 key = staticKey;
3584             }
3585             Object staticValue = StyleContext.getStaticAttribute(value);
3586             if (staticValue != null) {
3587                 value = staticValue;
3588             }
3589             if (key != null && value != null) {
3590                 valueConvertor.put(key, value);
3591             }
3592         }
3593     }
3594 
3595 
3596     /*
3597      * we need StyleSheet for resolving lenght units. (see
3598      * isW3CLengthUnits)
3599      * we can not pass stylesheet for handling relative sizes. (do not
3600      * think changing public API is necessary)
3601      * CSS is not likely to be accessed from more then one thread.
3602      * Having local storage for StyleSheet for resolving relative
3603      * sizes is safe
3604      *
3605      * idk 08/30/2004
3606      */
3607     private StyleSheet getStyleSheet(StyleSheet ss) {
3608         if (ss != null) {
3609             styleSheet = ss;
3610         }
3611         return styleSheet;
3612     }
3613     //
3614     // Instance variables
3615     //
3616 
3617     /** Maps from CSS key to CssValue. */
3618     private transient Hashtable<Object, Object> valueConvertor;
3619 
3620     /** Size used for relative units. */
3621     private int baseFontSize;
3622 
3623     private transient StyleSheet styleSheet = null;
3624 
3625     static int baseFontSizeIndex = 3;
3626 }