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