1 /*
   2  * Copyright (c) 1998, 2016, 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      * @param baseFontSize size to use for relative units.
 952      */
 953     float getPointSize(String size, StyleSheet ss) {
 954         int relSize, absSize, diff, index;
 955         ss = getStyleSheet(ss);
 956         if (size != null) {
 957             if (size.startsWith("+")) {
 958                 relSize = Integer.valueOf(size.substring(1)).intValue();
 959                 return getPointSize(baseFontSize + relSize, ss);
 960             } else if (size.startsWith("-")) {
 961                 relSize = -Integer.valueOf(size.substring(1)).intValue();
 962                 return getPointSize(baseFontSize + relSize, ss);
 963             } else {
 964                 absSize = Integer.valueOf(size).intValue();
 965                 return getPointSize(absSize, ss);
 966             }
 967         }
 968         return 0;
 969     }
 970 
 971     /**
 972      * Returns the length of the attribute in <code>a</code> with
 973      * key <code>key</code>.
 974      */
 975     float getLength(AttributeSet a, CSS.Attribute key, StyleSheet ss) {
 976         ss = getStyleSheet(ss);
 977         LengthValue lv = (LengthValue) a.getAttribute(key);
 978         boolean isW3CLengthUnits = (ss == null) ? false : ss.isW3CLengthUnits();
 979         float len = (lv != null) ? lv.getValue(isW3CLengthUnits) : 0;
 980         return len;
 981     }
 982 
 983     /**
 984      * Convert a set of HTML attributes to an equivalent
 985      * set of CSS attributes.
 986      *
 987      * @param htmlAttrSet AttributeSet containing the HTML attributes.
 988      * @return AttributeSet containing the corresponding CSS attributes.
 989      *        The AttributeSet will be empty if there are no mapping
 990      *        CSS attributes.
 991      */
 992     AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) {
 993         MutableAttributeSet cssAttrSet = new SimpleAttributeSet();
 994         Element elem = (Element)htmlAttrSet;
 995         HTML.Tag tag = getHTMLTag(htmlAttrSet);
 996         if ((tag == HTML.Tag.TD) || (tag == HTML.Tag.TH)) {
 997             // translate border width into the cells, if it has non-zero value.
 998             AttributeSet tableAttr = elem.getParentElement().
 999                                      getParentElement().getAttributes();
1000 
1001             int borderWidth = getTableBorder(tableAttr);
1002             if (borderWidth > 0) {
1003                 // If table contains the BORDER attribute cells should have border width equals 1
1004                 translateAttribute(HTML.Attribute.BORDER, "1", cssAttrSet);
1005             }
1006             String pad = (String)tableAttr.getAttribute(HTML.Attribute.CELLPADDING);
1007             if (pad != null) {
1008                 LengthValue v =
1009                     (LengthValue)getInternalCSSValue(CSS.Attribute.PADDING_TOP, pad);
1010                 v.span = (v.span < 0) ? 0 : v.span;
1011                 cssAttrSet.addAttribute(CSS.Attribute.PADDING_TOP, v);
1012                 cssAttrSet.addAttribute(CSS.Attribute.PADDING_BOTTOM, v);
1013                 cssAttrSet.addAttribute(CSS.Attribute.PADDING_LEFT, v);
1014                 cssAttrSet.addAttribute(CSS.Attribute.PADDING_RIGHT, v);
1015             }
1016         }
1017         if (elem.isLeaf()) {
1018             translateEmbeddedAttributes(htmlAttrSet, cssAttrSet);
1019         } else {
1020             translateAttributes(tag, htmlAttrSet, cssAttrSet);
1021         }
1022         if (tag == HTML.Tag.CAPTION) {
1023             /*
1024              * Navigator uses ALIGN for caption placement and IE uses VALIGN.
1025              */
1026             Object v = htmlAttrSet.getAttribute(HTML.Attribute.ALIGN);
1027             if ((v != null) && (v.equals("top") || v.equals("bottom"))) {
1028                 cssAttrSet.addAttribute(CSS.Attribute.CAPTION_SIDE, v);
1029                 cssAttrSet.removeAttribute(CSS.Attribute.TEXT_ALIGN);
1030             } else {
1031                 v = htmlAttrSet.getAttribute(HTML.Attribute.VALIGN);
1032                 if (v != null) {
1033                     cssAttrSet.addAttribute(CSS.Attribute.CAPTION_SIDE, v);
1034                 }
1035             }
1036         }
1037         return cssAttrSet;
1038     }
1039 
1040     private static int getTableBorder(AttributeSet tableAttr) {
1041         String borderValue = (String) tableAttr.getAttribute(HTML.Attribute.BORDER);
1042 
1043         if (borderValue == HTML.NULL_ATTRIBUTE_VALUE || "".equals(borderValue)) {
1044             // Some browsers accept <TABLE BORDER> and <TABLE BORDER=""> with the same semantics as BORDER=1
1045             return 1;
1046         }
1047 
1048         try {
1049             return Integer.parseInt(borderValue);
1050         } catch (NumberFormatException e) {
1051             return 0;
1052         }
1053     }
1054 
1055     private static final Hashtable<String, Attribute> attributeMap = new Hashtable<String, Attribute>();
1056     private static final Hashtable<String, Value> valueMap = new Hashtable<String, Value>();
1057 
1058     /**
1059      * The hashtable and the static initalization block below,
1060      * set up a mapping from well-known HTML attributes to
1061      * CSS attributes.  For the most part, there is a 1-1 mapping
1062      * between the two.  However in the case of certain HTML
1063      * attributes for example HTML.Attribute.VSPACE or
1064      * HTML.Attribute.HSPACE, end up mapping to two CSS.Attribute's.
1065      * Therefore, the value associated with each HTML.Attribute.
1066      * key ends up being an array of CSS.Attribute.* objects.
1067      */
1068     private static final Hashtable<HTML.Attribute, CSS.Attribute[]> htmlAttrToCssAttrMap = new Hashtable<HTML.Attribute, CSS.Attribute[]>(20);
1069 
1070     /**
1071      * The hashtable and static initialization that follows sets
1072      * up a translation from StyleConstants (i.e. the <em>well known</em>
1073      * attributes) to the associated CSS attributes.
1074      */
1075     private static final Hashtable<Object, Attribute> styleConstantToCssMap = new Hashtable<Object, Attribute>(17);
1076     /** Maps from HTML value to a CSS value. Used in internal mapping. */
1077     private static final Hashtable<String, CSS.Value> htmlValueToCssValueMap = new Hashtable<String, CSS.Value>(8);
1078     /** Maps from CSS value (string) to internal value. */
1079     private static final Hashtable<String, CSS.Value> cssValueToInternalValueMap = new Hashtable<String, CSS.Value>(13);
1080 
1081     static {
1082         // load the attribute map
1083         for (int i = 0; i < Attribute.allAttributes.length; i++ ) {
1084             attributeMap.put(Attribute.allAttributes[i].toString(),
1085                              Attribute.allAttributes[i]);
1086         }
1087         // load the value map
1088         for (int i = 0; i < Value.allValues.length; i++ ) {
1089             valueMap.put(Value.allValues[i].toString(),
1090                              Value.allValues[i]);
1091         }
1092 
1093         htmlAttrToCssAttrMap.put(HTML.Attribute.COLOR,
1094                                  new CSS.Attribute[]{CSS.Attribute.COLOR});
1095         htmlAttrToCssAttrMap.put(HTML.Attribute.TEXT,
1096                                  new CSS.Attribute[]{CSS.Attribute.COLOR});
1097         htmlAttrToCssAttrMap.put(HTML.Attribute.CLEAR,
1098                                  new CSS.Attribute[]{CSS.Attribute.CLEAR});
1099         htmlAttrToCssAttrMap.put(HTML.Attribute.BACKGROUND,
1100                                  new CSS.Attribute[]{CSS.Attribute.BACKGROUND_IMAGE});
1101         htmlAttrToCssAttrMap.put(HTML.Attribute.BGCOLOR,
1102                                  new CSS.Attribute[]{CSS.Attribute.BACKGROUND_COLOR});
1103         htmlAttrToCssAttrMap.put(HTML.Attribute.WIDTH,
1104                                  new CSS.Attribute[]{CSS.Attribute.WIDTH});
1105         htmlAttrToCssAttrMap.put(HTML.Attribute.HEIGHT,
1106                                  new CSS.Attribute[]{CSS.Attribute.HEIGHT});
1107         htmlAttrToCssAttrMap.put(HTML.Attribute.BORDER,
1108                                  new CSS.Attribute[]{CSS.Attribute.BORDER_TOP_WIDTH, CSS.Attribute.BORDER_RIGHT_WIDTH, CSS.Attribute.BORDER_BOTTOM_WIDTH, CSS.Attribute.BORDER_LEFT_WIDTH});
1109         htmlAttrToCssAttrMap.put(HTML.Attribute.CELLPADDING,
1110                                  new CSS.Attribute[]{CSS.Attribute.PADDING});
1111         htmlAttrToCssAttrMap.put(HTML.Attribute.CELLSPACING,
1112                                  new CSS.Attribute[]{CSS.Attribute.BORDER_SPACING});
1113         htmlAttrToCssAttrMap.put(HTML.Attribute.MARGINWIDTH,
1114                                  new CSS.Attribute[]{CSS.Attribute.MARGIN_LEFT,
1115                                                      CSS.Attribute.MARGIN_RIGHT});
1116         htmlAttrToCssAttrMap.put(HTML.Attribute.MARGINHEIGHT,
1117                                  new CSS.Attribute[]{CSS.Attribute.MARGIN_TOP,
1118                                                      CSS.Attribute.MARGIN_BOTTOM});
1119         htmlAttrToCssAttrMap.put(HTML.Attribute.HSPACE,
1120                                  new CSS.Attribute[]{CSS.Attribute.PADDING_LEFT,
1121                                                      CSS.Attribute.PADDING_RIGHT});
1122         htmlAttrToCssAttrMap.put(HTML.Attribute.VSPACE,
1123                                  new CSS.Attribute[]{CSS.Attribute.PADDING_BOTTOM,
1124                                                      CSS.Attribute.PADDING_TOP});
1125         htmlAttrToCssAttrMap.put(HTML.Attribute.FACE,
1126                                  new CSS.Attribute[]{CSS.Attribute.FONT_FAMILY});
1127         htmlAttrToCssAttrMap.put(HTML.Attribute.SIZE,
1128                                  new CSS.Attribute[]{CSS.Attribute.FONT_SIZE});
1129         htmlAttrToCssAttrMap.put(HTML.Attribute.VALIGN,
1130                                  new CSS.Attribute[]{CSS.Attribute.VERTICAL_ALIGN});
1131         htmlAttrToCssAttrMap.put(HTML.Attribute.ALIGN,
1132                                  new CSS.Attribute[]{CSS.Attribute.VERTICAL_ALIGN,
1133                                                      CSS.Attribute.TEXT_ALIGN,
1134                                                      CSS.Attribute.FLOAT});
1135         htmlAttrToCssAttrMap.put(HTML.Attribute.TYPE,
1136                                  new CSS.Attribute[]{CSS.Attribute.LIST_STYLE_TYPE});
1137         htmlAttrToCssAttrMap.put(HTML.Attribute.NOWRAP,
1138                                  new CSS.Attribute[]{CSS.Attribute.WHITE_SPACE});
1139 
1140         // initialize StyleConstants mapping
1141         styleConstantToCssMap.put(StyleConstants.FontFamily,
1142                                   CSS.Attribute.FONT_FAMILY);
1143         styleConstantToCssMap.put(StyleConstants.FontSize,
1144                                   CSS.Attribute.FONT_SIZE);
1145         styleConstantToCssMap.put(StyleConstants.Bold,
1146                                   CSS.Attribute.FONT_WEIGHT);
1147         styleConstantToCssMap.put(StyleConstants.Italic,
1148                                   CSS.Attribute.FONT_STYLE);
1149         styleConstantToCssMap.put(StyleConstants.Underline,
1150                                   CSS.Attribute.TEXT_DECORATION);
1151         styleConstantToCssMap.put(StyleConstants.StrikeThrough,
1152                                   CSS.Attribute.TEXT_DECORATION);
1153         styleConstantToCssMap.put(StyleConstants.Superscript,
1154                                   CSS.Attribute.VERTICAL_ALIGN);
1155         styleConstantToCssMap.put(StyleConstants.Subscript,
1156                                   CSS.Attribute.VERTICAL_ALIGN);
1157         styleConstantToCssMap.put(StyleConstants.Foreground,
1158                                   CSS.Attribute.COLOR);
1159         styleConstantToCssMap.put(StyleConstants.Background,
1160                                   CSS.Attribute.BACKGROUND_COLOR);
1161         styleConstantToCssMap.put(StyleConstants.FirstLineIndent,
1162                                   CSS.Attribute.TEXT_INDENT);
1163         styleConstantToCssMap.put(StyleConstants.LeftIndent,
1164                                   CSS.Attribute.MARGIN_LEFT);
1165         styleConstantToCssMap.put(StyleConstants.RightIndent,
1166                                   CSS.Attribute.MARGIN_RIGHT);
1167         styleConstantToCssMap.put(StyleConstants.SpaceAbove,
1168                                   CSS.Attribute.MARGIN_TOP);
1169         styleConstantToCssMap.put(StyleConstants.SpaceBelow,
1170                                   CSS.Attribute.MARGIN_BOTTOM);
1171         styleConstantToCssMap.put(StyleConstants.Alignment,
1172                                   CSS.Attribute.TEXT_ALIGN);
1173 
1174         // HTML->CSS
1175         htmlValueToCssValueMap.put("disc", CSS.Value.DISC);
1176         htmlValueToCssValueMap.put("square", CSS.Value.SQUARE);
1177         htmlValueToCssValueMap.put("circle", CSS.Value.CIRCLE);
1178         htmlValueToCssValueMap.put("1", CSS.Value.DECIMAL);
1179         htmlValueToCssValueMap.put("a", CSS.Value.LOWER_ALPHA);
1180         htmlValueToCssValueMap.put("A", CSS.Value.UPPER_ALPHA);
1181         htmlValueToCssValueMap.put("i", CSS.Value.LOWER_ROMAN);
1182         htmlValueToCssValueMap.put("I", CSS.Value.UPPER_ROMAN);
1183 
1184         // CSS-> internal CSS
1185         cssValueToInternalValueMap.put("none", CSS.Value.NONE);
1186         cssValueToInternalValueMap.put("disc", CSS.Value.DISC);
1187         cssValueToInternalValueMap.put("square", CSS.Value.SQUARE);
1188         cssValueToInternalValueMap.put("circle", CSS.Value.CIRCLE);
1189         cssValueToInternalValueMap.put("decimal", CSS.Value.DECIMAL);
1190         cssValueToInternalValueMap.put("lower-roman", CSS.Value.LOWER_ROMAN);
1191         cssValueToInternalValueMap.put("upper-roman", CSS.Value.UPPER_ROMAN);
1192         cssValueToInternalValueMap.put("lower-alpha", CSS.Value.LOWER_ALPHA);
1193         cssValueToInternalValueMap.put("upper-alpha", CSS.Value.UPPER_ALPHA);
1194         cssValueToInternalValueMap.put("repeat", CSS.Value.BACKGROUND_REPEAT);
1195         cssValueToInternalValueMap.put("no-repeat",
1196                                        CSS.Value.BACKGROUND_NO_REPEAT);
1197         cssValueToInternalValueMap.put("repeat-x",
1198                                        CSS.Value.BACKGROUND_REPEAT_X);
1199         cssValueToInternalValueMap.put("repeat-y",
1200                                        CSS.Value.BACKGROUND_REPEAT_Y);
1201         cssValueToInternalValueMap.put("scroll",
1202                                        CSS.Value.BACKGROUND_SCROLL);
1203         cssValueToInternalValueMap.put("fixed",
1204                                        CSS.Value.BACKGROUND_FIXED);
1205 
1206         // Register all the CSS attribute keys for archival/unarchival
1207         Object[] keys = CSS.Attribute.allAttributes;
1208         try {
1209             for (Object key : keys) {
1210                 StyleContext.registerStaticAttributeKey(key);
1211             }
1212         } catch (Throwable e) {
1213             e.printStackTrace();
1214         }
1215 
1216         // Register all the CSS Values for archival/unarchival
1217         keys = CSS.Value.allValues;
1218         try {
1219             for (Object key : keys) {
1220                 StyleContext.registerStaticAttributeKey(key);
1221             }
1222         } catch (Throwable e) {
1223             e.printStackTrace();
1224         }
1225     }
1226 
1227     /**
1228      * Return the set of all possible CSS attribute keys.
1229      *
1230      * @return the set of all possible CSS attribute keys
1231      */
1232     public static Attribute[] getAllAttributeKeys() {
1233         Attribute[] keys = new Attribute[Attribute.allAttributes.length];
1234         System.arraycopy(Attribute.allAttributes, 0, keys, 0, Attribute.allAttributes.length);
1235         return keys;
1236     }
1237 
1238     /**
1239      * Translates a string to a <code>CSS.Attribute</code> object.
1240      * This will return <code>null</code> if there is no attribute
1241      * by the given name.
1242      *
1243      * @param name the name of the CSS attribute to fetch the
1244      *  typesafe enumeration for
1245      * @return the <code>CSS.Attribute</code> object,
1246      *  or <code>null</code> if the string
1247      *  doesn't represent a valid attribute key
1248      */
1249     public static final Attribute getAttribute(String name) {
1250         return attributeMap.get(name);
1251     }
1252 
1253     /**
1254      * Translates a string to a <code>CSS.Value</code> object.
1255      * This will return <code>null</code> if there is no value
1256      * by the given name.
1257      *
1258      * @param name the name of the CSS value to fetch the
1259      *  typesafe enumeration for
1260      * @return the <code>CSS.Value</code> object,
1261      *  or <code>null</code> if the string
1262      *  doesn't represent a valid CSS value name; this does
1263      *  not mean that it doesn't represent a valid CSS value
1264      */
1265     static final Value getValue(String name) {
1266         return valueMap.get(name);
1267     }
1268 
1269 
1270     //
1271     // Conversion related methods/classes
1272     //
1273 
1274     /**
1275      * Returns a URL for the given CSS url string. If relative,
1276      * <code>base</code> is used as the parent. If a valid URL can not
1277      * be found, this will not throw a MalformedURLException, instead
1278      * null will be returned.
1279      */
1280     static URL getURL(URL base, String cssString) {
1281         if (cssString == null) {
1282             return null;
1283         }
1284         if (cssString.startsWith("url(") &&
1285             cssString.endsWith(")")) {
1286             cssString = cssString.substring(4, cssString.length() - 1);
1287         }
1288         // Absolute first
1289         try {
1290             URL url = new URL(cssString);
1291             if (url != null) {
1292                 return url;
1293             }
1294         } catch (MalformedURLException mue) {
1295         }
1296         // Then relative
1297         if (base != null) {
1298             // Relative URL, try from base
1299             try {
1300                 URL url = new URL(base, cssString);
1301                 return url;
1302             }
1303             catch (MalformedURLException muee) {
1304             }
1305         }
1306         return null;
1307     }
1308 
1309     /**
1310      * Converts a type Color to a hex string
1311      * in the format "#RRGGBB"
1312      */
1313     static String colorToHex(Color color) {
1314 
1315       String colorstr = "#";
1316 
1317       // Red
1318       String str = Integer.toHexString(color.getRed());
1319       if (str.length() > 2)
1320         str = str.substring(0, 2);
1321       else if (str.length() < 2)
1322         colorstr += "0" + str;
1323       else
1324         colorstr += str;
1325 
1326       // Green
1327       str = Integer.toHexString(color.getGreen());
1328       if (str.length() > 2)
1329         str = str.substring(0, 2);
1330       else if (str.length() < 2)
1331         colorstr += "0" + str;
1332       else
1333         colorstr += str;
1334 
1335       // Blue
1336       str = Integer.toHexString(color.getBlue());
1337       if (str.length() > 2)
1338         str = str.substring(0, 2);
1339       else if (str.length() < 2)
1340         colorstr += "0" + str;
1341       else
1342         colorstr += str;
1343 
1344       return colorstr;
1345     }
1346 
1347      /**
1348       * Convert a "#FFFFFF" hex string to a Color.
1349       * If the color specification is bad, an attempt
1350       * will be made to fix it up.
1351       */
1352     static final Color hexToColor(String value) {
1353         String digits;
1354         int n = value.length();
1355         if (value.startsWith("#")) {
1356             digits = value.substring(1, Math.min(value.length(), 7));
1357         } else {
1358             digits = value;
1359         }
1360         String hstr = "0x" + digits;
1361         Color c;
1362         try {
1363             c = Color.decode(hstr);
1364         } catch (NumberFormatException nfe) {
1365             c = null;
1366         }
1367          return c;
1368      }
1369 
1370     /**
1371      * Convert a color string such as "RED" or "#NNNNNN" or "rgb(r, g, b)"
1372      * to a Color.
1373      */
1374     static Color stringToColor(String str) {
1375       Color color;
1376 
1377       if (str == null) {
1378           return null;
1379       }
1380       if (str.length() == 0)
1381         color = Color.black;
1382       else if (str.startsWith("rgb(")) {
1383           color = parseRGB(str);
1384       }
1385       else if (str.charAt(0) == '#')
1386         color = hexToColor(str);
1387       else if (str.equalsIgnoreCase("Black"))
1388         color = hexToColor("#000000");
1389       else if(str.equalsIgnoreCase("Silver"))
1390         color = hexToColor("#C0C0C0");
1391       else if(str.equalsIgnoreCase("Gray"))
1392         color = hexToColor("#808080");
1393       else if(str.equalsIgnoreCase("White"))
1394         color = hexToColor("#FFFFFF");
1395       else if(str.equalsIgnoreCase("Maroon"))
1396         color = hexToColor("#800000");
1397       else if(str.equalsIgnoreCase("Red"))
1398         color = hexToColor("#FF0000");
1399       else if(str.equalsIgnoreCase("Purple"))
1400         color = hexToColor("#800080");
1401       else if(str.equalsIgnoreCase("Fuchsia"))
1402         color = hexToColor("#FF00FF");
1403       else if(str.equalsIgnoreCase("Green"))
1404         color = hexToColor("#008000");
1405       else if(str.equalsIgnoreCase("Lime"))
1406         color = hexToColor("#00FF00");
1407       else if(str.equalsIgnoreCase("Olive"))
1408         color = hexToColor("#808000");
1409       else if(str.equalsIgnoreCase("Yellow"))
1410         color = hexToColor("#FFFF00");
1411       else if(str.equalsIgnoreCase("Navy"))
1412         color = hexToColor("#000080");
1413       else if(str.equalsIgnoreCase("Blue"))
1414         color = hexToColor("#0000FF");
1415       else if(str.equalsIgnoreCase("Teal"))
1416         color = hexToColor("#008080");
1417       else if(str.equalsIgnoreCase("Aqua"))
1418         color = hexToColor("#00FFFF");
1419       else if(str.equalsIgnoreCase("Orange"))
1420         color = hexToColor("#FF8000");
1421       else
1422           color = hexToColor(str); // sometimes get specified without leading #
1423       return color;
1424     }
1425 
1426     /**
1427      * Parses a String in the format <code>rgb(r, g, b)</code> where
1428      * each of the Color components is either an integer, or a floating number
1429      * with a % after indicating a percentage value of 255. Values are
1430      * constrained to fit with 0-255. The resulting Color is returned.
1431      */
1432     private static Color parseRGB(String string) {
1433         // Find the next numeric char
1434         int[] index = new int[1];
1435 
1436         index[0] = 4;
1437         int red = getColorComponent(string, index);
1438         int green = getColorComponent(string, index);
1439         int blue = getColorComponent(string, index);
1440 
1441         return new Color(red, green, blue);
1442     }
1443 
1444     /**
1445      * Returns the next integer value from <code>string</code> starting
1446      * at <code>index[0]</code>. The value can either can an integer, or
1447      * a percentage (floating number ending with %), in which case it is
1448      * multiplied by 255.
1449      */
1450     private static int getColorComponent(String string, int[] index) {
1451         int length = string.length();
1452         char aChar;
1453 
1454         // Skip non-decimal chars
1455         while(index[0] < length && (aChar = string.charAt(index[0])) != '-' &&
1456               !Character.isDigit(aChar) && aChar != '.') {
1457             index[0]++;
1458         }
1459 
1460         int start = index[0];
1461 
1462         if (start < length && string.charAt(index[0]) == '-') {
1463             index[0]++;
1464         }
1465         while(index[0] < length &&
1466                          Character.isDigit(string.charAt(index[0]))) {
1467             index[0]++;
1468         }
1469         if (index[0] < length && string.charAt(index[0]) == '.') {
1470             // Decimal value
1471             index[0]++;
1472             while(index[0] < length &&
1473                   Character.isDigit(string.charAt(index[0]))) {
1474                 index[0]++;
1475             }
1476         }
1477         if (start != index[0]) {
1478             try {
1479                 float value = Float.parseFloat(string.substring
1480                                                (start, index[0]));
1481 
1482                 if (index[0] < length && string.charAt(index[0]) == '%') {
1483                     index[0]++;
1484                     value = value * 255f / 100f;
1485                 }
1486                 return Math.min(255, Math.max(0, (int)value));
1487             } catch (NumberFormatException nfe) {
1488                 // Treat as 0
1489             }
1490         }
1491         return 0;
1492     }
1493 
1494     static int getIndexOfSize(float pt, int[] sizeMap) {
1495         for (int i = 0; i < sizeMap.length; i ++ )
1496                 if (pt <= sizeMap[i])
1497                         return i + 1;
1498         return sizeMap.length;
1499     }
1500 
1501     static int getIndexOfSize(float pt, StyleSheet ss) {
1502         int[] sizeMap = (ss != null) ? ss.getSizeMap() :
1503             StyleSheet.sizeMapDefault;
1504         return getIndexOfSize(pt, sizeMap);
1505     }
1506 
1507 
1508     /**
1509      * @return an array of all the strings in <code>value</code>
1510      *         that are separated by whitespace.
1511      */
1512     static String[] parseStrings(String value) {
1513         int         current, last;
1514         int         length = (value == null) ? 0 : value.length();
1515         Vector<String> temp = new Vector<String>(4);
1516 
1517         current = 0;
1518         while (current < length) {
1519             // Skip ws
1520             while (current < length && Character.isWhitespace
1521                    (value.charAt(current))) {
1522                 current++;
1523             }
1524             last = current;
1525             int inParentheses = 0;
1526             char ch;
1527             while (current < length && (
1528                     !Character.isWhitespace(ch = value.charAt(current))
1529                             || inParentheses > 0)) {
1530                 if (ch == '(') {
1531                     inParentheses++;
1532                 } else if (ch == ')') {
1533                     inParentheses--;
1534                 }
1535                 current++;
1536             }
1537             if (last != current) {
1538                 temp.addElement(value.substring(last, current));
1539             }
1540             current++;
1541         }
1542         String[] retValue = new String[temp.size()];
1543         temp.copyInto(retValue);
1544         return retValue;
1545     }
1546 
1547     /**
1548      * Return the point size, given a size index. Legal HTML index sizes
1549      * are 1-7.
1550      */
1551     float getPointSize(int index, StyleSheet ss) {
1552         ss = getStyleSheet(ss);
1553         int[] sizeMap = (ss != null) ? ss.getSizeMap() :
1554             StyleSheet.sizeMapDefault;
1555         --index;
1556         if (index < 0)
1557           return sizeMap[0];
1558         else if (index > sizeMap.length - 1)
1559           return sizeMap[sizeMap.length - 1];
1560         else
1561           return sizeMap[index];
1562     }
1563 
1564 
1565     private void translateEmbeddedAttributes(AttributeSet htmlAttrSet,
1566                                              MutableAttributeSet cssAttrSet) {
1567         Enumeration<?> keys = htmlAttrSet.getAttributeNames();
1568         if (htmlAttrSet.getAttribute(StyleConstants.NameAttribute) ==
1569             HTML.Tag.HR) {
1570             // HR needs special handling due to us treating it as a leaf.
1571             translateAttributes(HTML.Tag.HR, htmlAttrSet, cssAttrSet);
1572         }
1573         while (keys.hasMoreElements()) {
1574             Object key = keys.nextElement();
1575             if (key instanceof HTML.Tag) {
1576                 HTML.Tag tag = (HTML.Tag)key;
1577                 Object o = htmlAttrSet.getAttribute(tag);
1578                 if (o != null && o instanceof AttributeSet) {
1579                     translateAttributes(tag, (AttributeSet)o, cssAttrSet);
1580                 }
1581             } else if (key instanceof CSS.Attribute) {
1582                 cssAttrSet.addAttribute(key, htmlAttrSet.getAttribute(key));
1583             }
1584         }
1585     }
1586 
1587     private void translateAttributes(HTML.Tag tag,
1588                                             AttributeSet htmlAttrSet,
1589                                             MutableAttributeSet cssAttrSet) {
1590         Enumeration<?> names = htmlAttrSet.getAttributeNames();
1591         while (names.hasMoreElements()) {
1592             Object name = names.nextElement();
1593 
1594             if (name instanceof HTML.Attribute) {
1595                 HTML.Attribute key = (HTML.Attribute)name;
1596 
1597                 /*
1598                  * HTML.Attribute.ALIGN needs special processing.
1599                  * It can map to 1 of many(3) possible CSS attributes
1600                  * depending on the nature of the tag the attribute is
1601                  * part off and depending on the value of the attribute.
1602                  */
1603                 if (key == HTML.Attribute.ALIGN) {
1604                     String htmlAttrValue = (String)htmlAttrSet.getAttribute(HTML.Attribute.ALIGN);
1605                     if (htmlAttrValue != null) {
1606                         CSS.Attribute cssAttr = getCssAlignAttribute(tag, htmlAttrSet);
1607                         if (cssAttr != null) {
1608                             Object o = getCssValue(cssAttr, htmlAttrValue);
1609                             if (o != null) {
1610                                 cssAttrSet.addAttribute(cssAttr, o);
1611                             }
1612                         }
1613                     }
1614                 } else {
1615                     if (key == HTML.Attribute.SIZE && !isHTMLFontTag(tag)) {
1616                         /*
1617                          * The html size attribute has a mapping in the CSS world only
1618                          * if it is par of a font or base font tag.
1619                          */
1620                     } else if (tag == HTML.Tag.TABLE && key == HTML.Attribute.BORDER) {
1621                         int borderWidth = getTableBorder(htmlAttrSet);
1622 
1623                         if (borderWidth > 0) {
1624                             translateAttribute(HTML.Attribute.BORDER, Integer.toString(borderWidth), cssAttrSet);
1625                         }
1626                     } else {
1627                         translateAttribute(key, (String) htmlAttrSet.getAttribute(key), cssAttrSet);
1628                     }
1629                 }
1630             } else if (name instanceof CSS.Attribute) {
1631                 cssAttrSet.addAttribute(name, htmlAttrSet.getAttribute(name));
1632             }
1633         }
1634     }
1635 
1636     private void translateAttribute(HTML.Attribute key,
1637                                            String htmlAttrValue,
1638                                            MutableAttributeSet cssAttrSet) {
1639         /*
1640          * In the case of all remaining HTML.Attribute's they
1641          * map to 1 or more CCS.Attribute.
1642          */
1643         CSS.Attribute[] cssAttrList = getCssAttribute(key);
1644 
1645         if (cssAttrList == null || htmlAttrValue == null) {
1646             return;
1647         }
1648         for (Attribute cssAttr : cssAttrList) {
1649             Object o = getCssValue(cssAttr, htmlAttrValue);
1650             if (o != null) {
1651                 cssAttrSet.addAttribute(cssAttr , o);
1652             }
1653         }
1654     }
1655 
1656     /**
1657      * Given a CSS.Attribute object and its corresponding HTML.Attribute's
1658      * value, this method returns a CssValue object to associate with the
1659      * CSS attribute.
1660      *
1661      * @param the CSS.Attribute
1662      * @param a String containing the value associated HTML.Attribtue.
1663      */
1664     Object getCssValue(CSS.Attribute cssAttr, String htmlAttrValue) {
1665         CssValue value = (CssValue)valueConvertor.get(cssAttr);
1666         Object o = value.parseHtmlValue(htmlAttrValue);
1667         return o;
1668     }
1669 
1670     /**
1671      * Maps an HTML.Attribute object to its appropriate CSS.Attributes.
1672      *
1673      * @param HTML.Attribute
1674      * @return CSS.Attribute[]
1675      */
1676     private CSS.Attribute[] getCssAttribute(HTML.Attribute hAttr) {
1677         return htmlAttrToCssAttrMap.get(hAttr);
1678     }
1679 
1680     /**
1681      * Maps HTML.Attribute.ALIGN to either:
1682      *     CSS.Attribute.TEXT_ALIGN
1683      *     CSS.Attribute.FLOAT
1684      *     CSS.Attribute.VERTICAL_ALIGN
1685      * based on the tag associated with the attribute and the
1686      * value of the attribute.
1687      *
1688      * @param AttributeSet containing HTML attributes.
1689      * @return CSS.Attribute mapping for HTML.Attribute.ALIGN.
1690      */
1691     private CSS.Attribute getCssAlignAttribute(HTML.Tag tag,
1692                                                    AttributeSet htmlAttrSet) {
1693         return CSS.Attribute.TEXT_ALIGN;
1694 /*
1695         String htmlAttrValue = (String)htmlAttrSet.getAttribute(HTML.Attribute.ALIGN);
1696         CSS.Attribute cssAttr = CSS.Attribute.TEXT_ALIGN;
1697         if (htmlAttrValue != null && htmlAttrSet instanceof Element) {
1698             Element elem = (Element)htmlAttrSet;
1699             if (!elem.isLeaf() && tag.isBlock() && validTextAlignValue(htmlAttrValue)) {
1700                 return CSS.Attribute.TEXT_ALIGN;
1701             } else if (isFloater(htmlAttrValue)) {
1702                 return CSS.Attribute.FLOAT;
1703             } else if (elem.isLeaf()) {
1704                 return CSS.Attribute.VERTICAL_ALIGN;
1705             }
1706         }
1707         return null;
1708         */
1709     }
1710 
1711     /**
1712      * Fetches the tag associated with the HTML AttributeSet.
1713      *
1714      * @param  AttributeSet containing the HTML attributes.
1715      * @return HTML.Tag
1716      */
1717     private HTML.Tag getHTMLTag(AttributeSet htmlAttrSet) {
1718         Object o = htmlAttrSet.getAttribute(StyleConstants.NameAttribute);
1719         if (o instanceof HTML.Tag) {
1720             HTML.Tag tag = (HTML.Tag) o;
1721             return tag;
1722         }
1723         return null;
1724     }
1725 
1726 
1727     private boolean isHTMLFontTag(HTML.Tag tag) {
1728         return (tag != null && ((tag == HTML.Tag.FONT) || (tag == HTML.Tag.BASEFONT)));
1729     }
1730 
1731 
1732     private boolean isFloater(String alignValue) {
1733         return (alignValue.equals("left") || alignValue.equals("right"));
1734     }
1735 
1736     private boolean validTextAlignValue(String alignValue) {
1737         return (isFloater(alignValue) || alignValue.equals("center"));
1738     }
1739 
1740     /**
1741      * Base class to CSS values in the attribute sets.  This
1742      * is intended to act as a convertor to/from other attribute
1743      * formats.
1744      * <p>
1745      * The CSS parser uses the parseCssValue method to convert
1746      * a string to whatever format is appropriate a given key
1747      * (i.e. these convertors are stored in a map using the
1748      * CSS.Attribute as a key and the CssValue as the value).
1749      * <p>
1750      * The HTML to CSS conversion process first converts the
1751      * HTML.Attribute to a CSS.Attribute, and then calls
1752      * the parseHtmlValue method on the value of the HTML
1753      * attribute to produce the corresponding CSS value.
1754      * <p>
1755      * The StyleConstants to CSS conversion process first
1756      * converts the StyleConstants attribute to a
1757      * CSS.Attribute, and then calls the fromStyleConstants
1758      * method to convert the StyleConstants value to a
1759      * CSS value.
1760      * <p>
1761      * The CSS to StyleConstants conversion process first
1762      * converts the StyleConstants attribute to a
1763      * CSS.Attribute, and then calls the toStyleConstants
1764      * method to convert the CSS value to a StyleConstants
1765      * value.
1766      */
1767     @SuppressWarnings("serial") // Same-version serialization only
1768     static class CssValue implements Serializable {
1769 
1770         /**
1771          * Convert a CSS value string to the internal format
1772          * (for fast processing) used in the attribute sets.
1773          * The fallback storage for any value that we don't
1774          * have a special binary format for is a String.
1775          */
1776         Object parseCssValue(String value) {
1777             return value;
1778         }
1779 
1780         /**
1781          * Convert an HTML attribute value to a CSS attribute
1782          * value.  If there is no conversion, return null.
1783          * This is implemented to simply forward to the CSS
1784          * parsing by default (since some of the attribute
1785          * values are the same).  If the attribute value
1786          * isn't recognized as a CSS value it is generally
1787          * returned as null.
1788          */
1789         Object parseHtmlValue(String value) {
1790             return parseCssValue(value);
1791         }
1792 
1793         /**
1794          * Converts a <code>StyleConstants</code> attribute value to
1795          * a CSS attribute value.  If there is no conversion,
1796          * returns <code>null</code>.  By default, there is no conversion.
1797          *
1798          * @param key the <code>StyleConstants</code> attribute
1799          * @param value the value of a <code>StyleConstants</code>
1800          *   attribute to be converted
1801          * @return the CSS value that represents the
1802          *   <code>StyleConstants</code> value
1803          */
1804         Object fromStyleConstants(StyleConstants key, Object value) {
1805             return null;
1806         }
1807 
1808         /**
1809          * Converts a CSS attribute value to a
1810          * <code>StyleConstants</code>
1811          * value.  If there is no conversion, returns
1812          * <code>null</code>.
1813          * By default, there is no conversion.
1814          *
1815          * @param key the <code>StyleConstants</code> attribute
1816          * @param v the view containing <code>AttributeSet</code>
1817          * @return the <code>StyleConstants</code> attribute value that
1818          *   represents the CSS attribute value
1819          */
1820         Object toStyleConstants(StyleConstants key, View v) {
1821             return null;
1822         }
1823 
1824         /**
1825          * Return the CSS format of the value
1826          */
1827         public String toString() {
1828             return svalue;
1829         }
1830 
1831         /**
1832          * The value as a string... before conversion to a
1833          * binary format.
1834          */
1835         String svalue;
1836     }
1837 
1838     /**
1839      * By default CSS attributes are represented as simple
1840      * strings.  They also have no conversion to/from
1841      * StyleConstants by default. This class represents the
1842      * value as a string (via the superclass), but
1843      * provides StyleConstants conversion support for the
1844      * CSS attributes that are held as strings.
1845      */
1846     @SuppressWarnings("serial") // Same-version serialization only
1847     static class StringValue extends CssValue {
1848 
1849         /**
1850          * Convert a CSS value string to the internal format
1851          * (for fast processing) used in the attribute sets.
1852          * This produces a StringValue, so that it can be
1853          * used to convert from CSS to StyleConstants values.
1854          */
1855         Object parseCssValue(String value) {
1856             StringValue sv = new StringValue();
1857             sv.svalue = value;
1858             return sv;
1859         }
1860 
1861         /**
1862          * Converts a <code>StyleConstants</code> attribute value to
1863          * a CSS attribute value.  If there is no conversion
1864          * returns <code>null</code>.
1865          *
1866          * @param key the <code>StyleConstants</code> attribute
1867          * @param value the value of a <code>StyleConstants</code>
1868          *   attribute to be converted
1869          * @return the CSS value that represents the
1870          *   <code>StyleConstants</code> value
1871          */
1872         Object fromStyleConstants(StyleConstants key, Object value) {
1873             if (key == StyleConstants.Italic) {
1874                 if (value.equals(Boolean.TRUE)) {
1875                     return parseCssValue("italic");
1876                 }
1877                 return parseCssValue("");
1878             } else if (key == StyleConstants.Underline) {
1879                 if (value.equals(Boolean.TRUE)) {
1880                     return parseCssValue("underline");
1881                 }
1882                 return parseCssValue("");
1883             } else if (key == StyleConstants.Alignment) {
1884                 int align = ((Integer)value).intValue();
1885                 String ta;
1886                 switch(align) {
1887                 case StyleConstants.ALIGN_LEFT:
1888                     ta = "left";
1889                     break;
1890                 case StyleConstants.ALIGN_RIGHT:
1891                     ta = "right";
1892                     break;
1893                 case StyleConstants.ALIGN_CENTER:
1894                     ta = "center";
1895                     break;
1896                 case StyleConstants.ALIGN_JUSTIFIED:
1897                     ta = "justify";
1898                     break;
1899                 default:
1900                     ta = "left";
1901                 }
1902                 return parseCssValue(ta);
1903             } else if (key == StyleConstants.StrikeThrough) {
1904                 if (value.equals(Boolean.TRUE)) {
1905                     return parseCssValue("line-through");
1906                 }
1907                 return parseCssValue("");
1908             } else if (key == StyleConstants.Superscript) {
1909                 if (value.equals(Boolean.TRUE)) {
1910                     return parseCssValue("super");
1911                 }
1912                 return parseCssValue("");
1913             } else if (key == StyleConstants.Subscript) {
1914                 if (value.equals(Boolean.TRUE)) {
1915                     return parseCssValue("sub");
1916                 }
1917                 return parseCssValue("");
1918             }
1919             return null;
1920         }
1921 
1922         /**
1923          * Converts a CSS attribute value to a
1924          * <code>StyleConstants</code> value.
1925          * If there is no conversion, returns <code>null</code>.
1926          * By default, there is no conversion.
1927          *
1928          * @param key the <code>StyleConstants</code> attribute
1929          * @return the <code>StyleConstants</code> attribute value that
1930          *   represents the CSS attribute value
1931          */
1932         Object toStyleConstants(StyleConstants key, View v) {
1933             if (key == StyleConstants.Italic) {
1934                 if (svalue.indexOf("italic") >= 0) {
1935                     return Boolean.TRUE;
1936                 }
1937                 return Boolean.FALSE;
1938             } else if (key == StyleConstants.Underline) {
1939                 if (svalue.indexOf("underline") >= 0) {
1940                     return Boolean.TRUE;
1941                 }
1942                 return Boolean.FALSE;
1943             } else if (key == StyleConstants.Alignment) {
1944                 if (svalue.equals("right")) {
1945                     return StyleConstants.ALIGN_RIGHT;
1946                 } else if (svalue.equals("center")) {
1947                     return StyleConstants.ALIGN_CENTER;
1948                 } else if  (svalue.equals("justify")) {
1949                     return StyleConstants.ALIGN_JUSTIFIED;
1950                 }
1951                 return StyleConstants.ALIGN_LEFT;
1952             } else if (key == StyleConstants.StrikeThrough) {
1953                 if (svalue.indexOf("line-through") >= 0) {
1954                     return Boolean.TRUE;
1955                 }
1956                 return Boolean.FALSE;
1957             } else if (key == StyleConstants.Superscript) {
1958                 if (svalue.indexOf("super") >= 0) {
1959                     return Boolean.TRUE;
1960                 }
1961                 return Boolean.FALSE;
1962             } else if (key == StyleConstants.Subscript) {
1963                 if (svalue.indexOf("sub") >= 0) {
1964                     return Boolean.TRUE;
1965                 }
1966                 return Boolean.FALSE;
1967             }
1968             return null;
1969         }
1970 
1971         // Used by ViewAttributeSet
1972         boolean isItalic() {
1973             return (svalue.indexOf("italic") != -1);
1974         }
1975 
1976         boolean isStrike() {
1977             return (svalue.indexOf("line-through") != -1);
1978         }
1979 
1980         boolean isUnderline() {
1981             return (svalue.indexOf("underline") != -1);
1982         }
1983 
1984         boolean isSub() {
1985             return (svalue.indexOf("sub") != -1);
1986         }
1987 
1988         boolean isSup() {
1989             return (svalue.indexOf("sup") != -1);
1990         }
1991     }
1992 
1993     /**
1994      * Represents a value for the CSS.FONT_SIZE attribute.
1995      * The binary format of the value can be one of several
1996      * types.  If the type is Float,
1997      * the value is specified in terms of point or
1998      * percentage, depending upon the ending of the
1999      * associated string.
2000      * If the type is Integer, the value is specified
2001      * in terms of a size index.
2002      */
2003     @SuppressWarnings("serial") // Same-version serialization only
2004     class FontSize extends CssValue {
2005 
2006         /**
2007          * Returns the size in points.  This is ultimately
2008          * what we need for the purpose of creating/fetching
2009          * a Font object.
2010          *
2011          * @param a the attribute set the value is being
2012          *  requested from.  We may need to walk up the
2013          *  resolve hierarchy if it's relative.
2014          */
2015         int getValue(AttributeSet a, StyleSheet ss) {
2016             ss = getStyleSheet(ss);
2017             if (index) {
2018                 // it's an index, translate from size table
2019                 return Math.round(getPointSize((int) value, ss));
2020             }
2021             else if (lu == null) {
2022                 return Math.round(value);
2023             }
2024             else {
2025                 if (lu.type == 0) {
2026                     boolean isW3CLengthUnits = (ss == null) ? false : ss.isW3CLengthUnits();
2027                     return Math.round(lu.getValue(isW3CLengthUnits));
2028                 }
2029                 if (a != null) {
2030                     AttributeSet resolveParent = a.getResolveParent();
2031 
2032                     if (resolveParent != null) {
2033                         int pValue = StyleConstants.getFontSize(resolveParent);
2034 
2035                         float retValue;
2036                         if (lu.type == 1 || lu.type == 3) {
2037                             retValue = lu.value * (float)pValue;
2038                         }
2039                         else {
2040                             retValue = lu.value + (float)pValue;
2041                         }
2042                         return Math.round(retValue);
2043                     }
2044                 }
2045                 // a is null, or no resolve parent.
2046                 return 12;
2047             }
2048         }
2049 
2050         Object parseCssValue(String value) {
2051             FontSize fs = new FontSize();
2052             fs.svalue = value;
2053             try {
2054                 if (value.equals("xx-small")) {
2055                     fs.value = 1;
2056                     fs.index = true;
2057                 } else if (value.equals("x-small")) {
2058                     fs.value = 2;
2059                     fs.index = true;
2060                 } else if (value.equals("small")) {
2061                     fs.value = 3;
2062                     fs.index = true;
2063                 } else if (value.equals("medium")) {
2064                     fs.value = 4;
2065                     fs.index = true;
2066                 } else if (value.equals("large")) {
2067                     fs.value = 5;
2068                     fs.index = true;
2069                 } else if (value.equals("x-large")) {
2070                     fs.value = 6;
2071                     fs.index = true;
2072                 } else if (value.equals("xx-large")) {
2073                     fs.value = 7;
2074                     fs.index = true;
2075                 } else {
2076                     fs.lu = new LengthUnit(value, (short)1, 1f);
2077                 }
2078                 // relative sizes, larger | smaller (adjust from parent by
2079                 // 1.5 pixels)
2080                 // em, ex refer to parent sizes
2081                 // lengths: pt, mm, cm, pc, in, px
2082                 //          em (font height 3em would be 3 times font height)
2083                 //          ex (height of X)
2084                 // lengths are (+/-) followed by a number and two letter
2085                 // unit identifier
2086             } catch (NumberFormatException nfe) {
2087                 fs = null;
2088             }
2089             return fs;
2090         }
2091 
2092         Object parseHtmlValue(String value) {
2093             if ((value == null) || (value.length() == 0)) {
2094                 return null;
2095             }
2096             FontSize fs = new FontSize();
2097             fs.svalue = value;
2098 
2099             try {
2100                 /*
2101                  * relative sizes in the size attribute are relative
2102                  * to the <basefont>'s size.
2103                  */
2104                 int baseFontSize = getBaseFontSize();
2105                 if (value.charAt(0) == '+') {
2106                     int relSize = Integer.valueOf(value.substring(1)).intValue();
2107                     fs.value = baseFontSize + relSize;
2108                     fs.index = true;
2109                 } else if (value.charAt(0) == '-') {
2110                     int relSize = -Integer.valueOf(value.substring(1)).intValue();
2111                     fs.value = baseFontSize + relSize;
2112                     fs.index = true;
2113                 } else {
2114                     fs.value = Integer.parseInt(value);
2115                     if (fs.value > 7) {
2116                         fs.value = 7;
2117                     } else if (fs.value < 0) {
2118                         fs.value = 0;
2119                     }
2120                     fs.index = true;
2121                 }
2122 
2123             } catch (NumberFormatException nfe) {
2124                 fs = null;
2125             }
2126             return fs;
2127         }
2128 
2129         /**
2130          * Converts a <code>StyleConstants</code> attribute value to
2131          * a CSS attribute value.  If there is no conversion
2132          * returns <code>null</code>.  By default, there is no conversion.
2133          *
2134          * @param key the <code>StyleConstants</code> attribute
2135          * @param value the value of a <code>StyleConstants</code>
2136          *   attribute to be converted
2137          * @return the CSS value that represents the
2138          *   <code>StyleConstants</code> value
2139          */
2140         Object fromStyleConstants(StyleConstants key, Object value) {
2141             if (value instanceof Number) {
2142                 FontSize fs = new FontSize();
2143 
2144                 fs.value = getIndexOfSize(((Number)value).floatValue(), StyleSheet.sizeMapDefault);
2145                 fs.svalue = Integer.toString((int)fs.value);
2146                 fs.index = true;
2147                 return fs;
2148             }
2149             return parseCssValue(value.toString());
2150         }
2151 
2152         /**
2153          * Converts a CSS attribute value to a <code>StyleConstants</code>
2154          * value.  If there is no conversion, returns <code>null</code>.
2155          * By default, there is no conversion.
2156          *
2157          * @param key the <code>StyleConstants</code> attribute
2158          * @return the <code>StyleConstants</code> attribute value that
2159          *   represents the CSS attribute value
2160          */
2161         Object toStyleConstants(StyleConstants key, View v) {
2162             if (v != null) {
2163                 return Integer.valueOf(getValue(v.getAttributes(), null));
2164             }
2165             return Integer.valueOf(getValue(null, null));
2166         }
2167 
2168         float value;
2169         boolean index;
2170         LengthUnit lu;
2171     }
2172 
2173     @SuppressWarnings("serial") // Same-version serialization only
2174     static class FontFamily extends CssValue {
2175 
2176         /**
2177          * Returns the font family to use.
2178          */
2179         String getValue() {
2180             return family;
2181         }
2182 
2183         Object parseCssValue(String value) {
2184             int cIndex = value.indexOf(',');
2185             FontFamily ff = new FontFamily();
2186             ff.svalue = value;
2187             ff.family = null;
2188 
2189             if (cIndex == -1) {
2190                 setFontName(ff, value);
2191             }
2192             else {
2193                 boolean done = false;
2194                 int lastIndex;
2195                 int length = value.length();
2196                 cIndex = 0;
2197                 while (!done) {
2198                     // skip ws.
2199                     while (cIndex < length &&
2200                            Character.isWhitespace(value.charAt(cIndex)))
2201                         cIndex++;
2202                     // Find next ','
2203                     lastIndex = cIndex;
2204                     cIndex = value.indexOf(',', cIndex);
2205                     if (cIndex == -1) {
2206                         cIndex = length;
2207                     }
2208                     if (lastIndex < length) {
2209                         if (lastIndex != cIndex) {
2210                             int lastCharIndex = cIndex;
2211                             if (cIndex > 0 && value.charAt(cIndex - 1) == ' '){
2212                                 lastCharIndex--;
2213                             }
2214                             setFontName(ff, value.substring
2215                                         (lastIndex, lastCharIndex));
2216                             done = (ff.family != null);
2217                         }
2218                         cIndex++;
2219                     }
2220                     else {
2221                         done = true;
2222                     }
2223                 }
2224             }
2225             if (ff.family == null) {
2226                 ff.family = Font.SANS_SERIF;
2227             }
2228             return ff;
2229         }
2230 
2231         private void setFontName(FontFamily ff, String fontName) {
2232             ff.family = fontName;
2233         }
2234 
2235         Object parseHtmlValue(String value) {
2236             // TBD
2237             return parseCssValue(value);
2238         }
2239 
2240         /**
2241          * Converts a <code>StyleConstants</code> attribute value to
2242          * a CSS attribute value.  If there is no conversion
2243          * returns <code>null</code>.  By default, there is no conversion.
2244          *
2245          * @param key the <code>StyleConstants</code> attribute
2246          * @param value the value of a <code>StyleConstants</code>
2247          *   attribute to be converted
2248          * @return the CSS value that represents the
2249          *   <code>StyleConstants</code> value
2250          */
2251         Object fromStyleConstants(StyleConstants key, Object value) {
2252             return parseCssValue(value.toString());
2253         }
2254 
2255         /**
2256          * Converts a CSS attribute value to a <code>StyleConstants</code>
2257          * value.  If there is no conversion, returns <code>null</code>.
2258          * By default, there is no conversion.
2259          *
2260          * @param key the <code>StyleConstants</code> attribute
2261          * @return the <code>StyleConstants</code> attribute value that
2262          *   represents the CSS attribute value
2263          */
2264         Object toStyleConstants(StyleConstants key, View v) {
2265             return family;
2266         }
2267 
2268         String family;
2269     }
2270 
2271     @SuppressWarnings("serial") // Same-version serialization only
2272     static class FontWeight extends CssValue {
2273 
2274         int getValue() {
2275             return weight;
2276         }
2277 
2278         Object parseCssValue(String value) {
2279             FontWeight fw = new FontWeight();
2280             fw.svalue = value;
2281             if (value.equals("bold")) {
2282                 fw.weight = 700;
2283             } else if (value.equals("normal")) {
2284                 fw.weight = 400;
2285             } else {
2286                 // PENDING(prinz) add support for relative values
2287                 try {
2288                     fw.weight = Integer.parseInt(value);
2289                 } catch (NumberFormatException nfe) {
2290                     fw = null;
2291                 }
2292             }
2293             return fw;
2294         }
2295 
2296         /**
2297          * Converts a <code>StyleConstants</code> attribute value to
2298          * a CSS attribute value.  If there is no conversion
2299          * returns <code>null</code>.  By default, there is no conversion.
2300          *
2301          * @param key the <code>StyleConstants</code> attribute
2302          * @param value the value of a <code>StyleConstants</code>
2303          *   attribute to be converted
2304          * @return the CSS value that represents the
2305          *   <code>StyleConstants</code> value
2306          */
2307         Object fromStyleConstants(StyleConstants key, Object value) {
2308             if (value.equals(Boolean.TRUE)) {
2309                 return parseCssValue("bold");
2310             }
2311             return parseCssValue("normal");
2312         }
2313 
2314         /**
2315          * Converts a CSS attribute value to a <code>StyleConstants</code>
2316          * value.  If there is no conversion, returns <code>null</code>.
2317          * By default, there is no conversion.
2318          *
2319          * @param key the <code>StyleConstants</code> attribute
2320          * @return the <code>StyleConstants</code> attribute value that
2321          *   represents the CSS attribute value
2322          */
2323         Object toStyleConstants(StyleConstants key, View v) {
2324             return (weight > 500) ? Boolean.TRUE : Boolean.FALSE;
2325         }
2326 
2327         boolean isBold() {
2328             return (weight > 500);
2329         }
2330 
2331         int weight;
2332     }
2333 
2334     @SuppressWarnings("serial") // Same-version serialization only
2335     static class ColorValue extends CssValue {
2336 
2337         /**
2338          * Returns the color to use.
2339          */
2340         Color getValue() {
2341             return c;
2342         }
2343 
2344         Object parseCssValue(String value) {
2345 
2346             Color c = stringToColor(value);
2347             if (c != null) {
2348                 ColorValue cv = new ColorValue();
2349                 cv.svalue = value;
2350                 cv.c = c;
2351                 return cv;
2352             }
2353             return null;
2354         }
2355 
2356         Object parseHtmlValue(String value) {
2357             return parseCssValue(value);
2358         }
2359 
2360         /**
2361          * Converts a <code>StyleConstants</code> attribute value to
2362          * a CSS attribute value.  If there is no conversion
2363          * returns <code>null</code>.  By default, there is no conversion.
2364          *
2365          * @param key the <code>StyleConstants</code> attribute
2366          * @param value the value of a <code>StyleConstants</code>
2367          *   attribute to be converted
2368          * @return the CSS value that represents the
2369          *   <code>StyleConstants</code> value
2370          */
2371         Object fromStyleConstants(StyleConstants key, Object value) {
2372             ColorValue colorValue = new ColorValue();
2373             colorValue.c = (Color)value;
2374             colorValue.svalue = colorToHex(colorValue.c);
2375             return colorValue;
2376         }
2377 
2378         /**
2379          * Converts a CSS attribute value to a <code>StyleConstants</code>
2380          * value.  If there is no conversion, returns <code>null</code>.
2381          * By default, there is no conversion.
2382          *
2383          * @param key the <code>StyleConstants</code> attribute
2384          * @return the <code>StyleConstants</code> attribute value that
2385          *   represents the CSS attribute value
2386          */
2387         Object toStyleConstants(StyleConstants key, View v) {
2388             return c;
2389         }
2390 
2391         Color c;
2392     }
2393 
2394     @SuppressWarnings("serial") // Same-version serialization only
2395     static class BorderStyle extends CssValue {
2396 
2397         CSS.Value getValue() {
2398             return style;
2399         }
2400 
2401         Object parseCssValue(String value) {
2402             CSS.Value cssv = CSS.getValue(value);
2403             if (cssv != null) {
2404                 if ((cssv == CSS.Value.INSET) ||
2405                     (cssv == CSS.Value.OUTSET) ||
2406                     (cssv == CSS.Value.NONE) ||
2407                     (cssv == CSS.Value.DOTTED) ||
2408                     (cssv == CSS.Value.DASHED) ||
2409                     (cssv == CSS.Value.SOLID) ||
2410                     (cssv == CSS.Value.DOUBLE) ||
2411                     (cssv == CSS.Value.GROOVE) ||
2412                     (cssv == CSS.Value.RIDGE)) {
2413 
2414                     BorderStyle bs = new BorderStyle();
2415                     bs.svalue = value;
2416                     bs.style = cssv;
2417                     return bs;
2418                 }
2419             }
2420             return null;
2421         }
2422 
2423         private void writeObject(java.io.ObjectOutputStream s)
2424                      throws IOException {
2425             s.defaultWriteObject();
2426             if (style == null) {
2427                 s.writeObject(null);
2428             }
2429             else {
2430                 s.writeObject(style.toString());
2431             }
2432         }
2433 
2434         private void readObject(ObjectInputStream s)
2435                 throws ClassNotFoundException, IOException {
2436             s.defaultReadObject();
2437             Object value = s.readObject();
2438             if (value != null) {
2439                 style = CSS.getValue((String)value);
2440             }
2441         }
2442 
2443         // CSS.Values are static, don't archive it.
2444         private transient CSS.Value style;
2445     }
2446 
2447     @SuppressWarnings("serial") // Same-version serialization only
2448     static class LengthValue extends CssValue {
2449 
2450         /**
2451          * if this length value may be negative.
2452          */
2453         boolean mayBeNegative;
2454 
2455         LengthValue() {
2456             this(false);
2457         }
2458 
2459         LengthValue(boolean mayBeNegative) {
2460             this.mayBeNegative = mayBeNegative;
2461         }
2462 
2463         /**
2464          * Returns the length (span) to use.
2465          */
2466         float getValue() {
2467             return getValue(false);
2468         }
2469 
2470         float getValue(boolean isW3CLengthUnits) {
2471             return getValue(0, isW3CLengthUnits);
2472         }
2473 
2474         /**
2475          * Returns the length (span) to use. If the value represents
2476          * a percentage, it is scaled based on <code>currentValue</code>.
2477          */
2478         float getValue(float currentValue) {
2479             return getValue(currentValue, false);
2480         }
2481         float getValue(float currentValue, boolean isW3CLengthUnits) {
2482             if (percentage) {
2483                 return span * currentValue;
2484             }
2485             return LengthUnit.getValue(span, units, isW3CLengthUnits);
2486         }
2487 
2488         /**
2489          * Returns true if the length represents a percentage of the
2490          * containing box.
2491          */
2492         boolean isPercentage() {
2493             return percentage;
2494         }
2495 
2496         Object parseCssValue(String value) {
2497             LengthValue lv;
2498             try {
2499                 // Assume pixels
2500                 float absolute = Float.valueOf(value).floatValue();
2501                 lv = new LengthValue();
2502                 lv.span = absolute;
2503             } catch (NumberFormatException nfe) {
2504                 // Not pixels, use LengthUnit
2505                 LengthUnit lu = new LengthUnit(value,
2506                                                LengthUnit.UNINITALIZED_LENGTH,
2507                                                0);
2508 
2509                 // PENDING: currently, we only support absolute values and
2510                 // percentages.
2511                 switch (lu.type) {
2512                 case 0:
2513                     // Absolute
2514                     lv = new LengthValue();
2515                     lv.span =
2516                         (mayBeNegative) ? lu.value : Math.max(0, lu.value);
2517                     lv.units = lu.units;
2518                     break;
2519                 case 1:
2520                     // %
2521                     lv = new LengthValue();
2522                     lv.span = Math.max(0, Math.min(1, lu.value));
2523                     lv.percentage = true;
2524                     break;
2525                 default:
2526                     return null;
2527                 }
2528             }
2529             lv.svalue = value;
2530             return lv;
2531         }
2532 
2533         Object parseHtmlValue(String value) {
2534             if (value.equals(HTML.NULL_ATTRIBUTE_VALUE)) {
2535                 value = "1";
2536             }
2537             return parseCssValue(value);
2538         }
2539         /**
2540          * Converts a <code>StyleConstants</code> attribute value to
2541          * a CSS attribute value.  If there is no conversion,
2542          * returns <code>null</code>.  By default, there is no conversion.
2543          *
2544          * @param key the <code>StyleConstants</code> attribute
2545          * @param value the value of a <code>StyleConstants</code>
2546          *   attribute to be converted
2547          * @return the CSS value that represents the
2548          *   <code>StyleConstants</code> value
2549          */
2550         Object fromStyleConstants(StyleConstants key, Object value) {
2551             LengthValue v = new LengthValue();
2552             v.svalue = value.toString();
2553             v.span = ((Float)value).floatValue();
2554             return v;
2555         }
2556 
2557         /**
2558          * Converts a CSS attribute value to a <code>StyleConstants</code>
2559          * value.  If there is no conversion, returns <code>null</code>.
2560          * By default, there is no conversion.
2561          *
2562          * @param key the <code>StyleConstants</code> attribute
2563          * @return the <code>StyleConstants</code> attribute value that
2564          *   represents the CSS attribute value
2565          */
2566         Object toStyleConstants(StyleConstants key, View v) {
2567             return Float.valueOf(getValue(false));
2568         }
2569 
2570         /** If true, span is a percentage value, and that to determine
2571          * the length another value needs to be passed in. */
2572         boolean percentage;
2573         /** Either the absolute value (percentage == false) or
2574          * a percentage value. */
2575         float span;
2576 
2577         String units = null;
2578     }
2579 
2580 
2581     /**
2582      * BorderWidthValue is used to model BORDER_XXX_WIDTH and adds support
2583      * for the thin/medium/thick values.
2584      */
2585     @SuppressWarnings("serial") // Same-version serialization only
2586     static class BorderWidthValue extends LengthValue {
2587         BorderWidthValue(String svalue, int index) {
2588             this.svalue = svalue;
2589             span = values[index];
2590             percentage = false;
2591         }
2592 
2593         Object parseCssValue(String value) {
2594             if (value != null) {
2595                 if (value.equals("thick")) {
2596                     return new BorderWidthValue(value, 2);
2597                 }
2598                 else if (value.equals("medium")) {
2599                     return new BorderWidthValue(value, 1);
2600                 }
2601                 else if (value.equals("thin")) {
2602                     return new BorderWidthValue(value, 0);
2603                 }
2604             }
2605             // Assume its a length.
2606             return super.parseCssValue(value);
2607         }
2608 
2609         Object parseHtmlValue(String value) {
2610             if (value == HTML.NULL_ATTRIBUTE_VALUE) {
2611                 return parseCssValue("medium");
2612             }
2613             return parseCssValue(value);
2614         }
2615 
2616         /** Values used to represent border width. */
2617         private static final float[] values = { 1, 2, 4 };
2618    }
2619 
2620 
2621     /**
2622      * Handles uniquing of CSS values, like lists, and background image
2623      * repeating.
2624      */
2625     @SuppressWarnings("serial") // Same-version serialization only
2626     static class CssValueMapper extends CssValue {
2627         Object parseCssValue(String value) {
2628             Object retValue = cssValueToInternalValueMap.get(value);
2629             if (retValue == null) {
2630                 retValue = cssValueToInternalValueMap.get(value.toLowerCase());
2631             }
2632             return retValue;
2633         }
2634 
2635 
2636         Object parseHtmlValue(String value) {
2637             Object retValue = htmlValueToCssValueMap.get(value);
2638             if (retValue == null) {
2639                 retValue = htmlValueToCssValueMap.get(value.toLowerCase());
2640             }
2641             return retValue;
2642         }
2643     }
2644 
2645 
2646     /**
2647      * Used for background images, to represent the position.
2648      */
2649     @SuppressWarnings("serial") // Same-version serialization only
2650     static class BackgroundPosition extends CssValue {
2651         float horizontalPosition;
2652         float verticalPosition;
2653         // bitmask: bit 0, horizontal relative, bit 1 horizontal relative to
2654         // font size, 2 vertical relative to size, 3 vertical relative to
2655         // font size.
2656         //
2657         short relative;
2658 
2659         Object parseCssValue(String value) {
2660             // 'top left' and 'left top' both mean the same as '0% 0%'.
2661             // 'top', 'top center' and 'center top' mean the same as '50% 0%'.
2662             // 'right top' and 'top right' mean the same as '100% 0%'.
2663             // 'left', 'left center' and 'center left' mean the same as
2664             //        '0% 50%'.
2665             // 'center' and 'center center' mean the same as '50% 50%'.
2666             // 'right', 'right center' and 'center right' mean the same as
2667             //        '100% 50%'.
2668             // 'bottom left' and 'left bottom' mean the same as '0% 100%'.
2669             // 'bottom', 'bottom center' and 'center bottom' mean the same as
2670             //        '50% 100%'.
2671             // 'bottom right' and 'right bottom' mean the same as '100% 100%'.
2672             String[]  strings = CSS.parseStrings(value);
2673             int count = strings.length;
2674             BackgroundPosition bp = new BackgroundPosition();
2675             bp.relative = 5;
2676             bp.svalue = value;
2677 
2678             if (count > 0) {
2679                 // bit 0 for vert, 1 hor, 2 for center
2680                 short found = 0;
2681                 int index = 0;
2682                 while (index < count) {
2683                     // First, check for keywords
2684                     String string = strings[index++];
2685                     if (string.equals("center")) {
2686                         found |= 4;
2687                         continue;
2688                     }
2689                     else {
2690                         if ((found & 1) == 0) {
2691                             if (string.equals("top")) {
2692                                 found |= 1;
2693                             }
2694                             else if (string.equals("bottom")) {
2695                                 found |= 1;
2696                                 bp.verticalPosition = 1;
2697                                 continue;
2698                             }
2699                         }
2700                         if ((found & 2) == 0) {
2701                             if (string.equals("left")) {
2702                                 found |= 2;
2703                                 bp.horizontalPosition = 0;
2704                             }
2705                             else if (string.equals("right")) {
2706                                 found |= 2;
2707                                 bp.horizontalPosition = 1;
2708                             }
2709                         }
2710                     }
2711                 }
2712                 if (found != 0) {
2713                     if ((found & 1) == 1) {
2714                         if ((found & 2) == 0) {
2715                             // vert and no horiz.
2716                             bp.horizontalPosition = .5f;
2717                         }
2718                     }
2719                     else if ((found & 2) == 2) {
2720                         // horiz and no vert.
2721                         bp.verticalPosition = .5f;
2722                     }
2723                     else {
2724                         // no horiz, no vert, but center
2725                         bp.horizontalPosition = bp.verticalPosition = .5f;
2726                     }
2727                 }
2728                 else {
2729                     // Assume lengths
2730                     LengthUnit lu = new LengthUnit(strings[0], (short)0, 0f);
2731 
2732                     if (lu.type == 0) {
2733                         bp.horizontalPosition = lu.value;
2734                         bp.relative = (short)(1 ^ bp.relative);
2735                     }
2736                     else if (lu.type == 1) {
2737                         bp.horizontalPosition = lu.value;
2738                     }
2739                     else if (lu.type == 3) {
2740                         bp.horizontalPosition = lu.value;
2741                         bp.relative = (short)((1 ^ bp.relative) | 2);
2742                     }
2743                     if (count > 1) {
2744                         lu = new LengthUnit(strings[1], (short)0, 0f);
2745 
2746                         if (lu.type == 0) {
2747                             bp.verticalPosition = lu.value;
2748                             bp.relative = (short)(4 ^ bp.relative);
2749                         }
2750                         else if (lu.type == 1) {
2751                             bp.verticalPosition = lu.value;
2752                         }
2753                         else if (lu.type == 3) {
2754                             bp.verticalPosition = lu.value;
2755                             bp.relative = (short)((4 ^ bp.relative) | 8);
2756                         }
2757                     }
2758                     else {
2759                         bp.verticalPosition = .5f;
2760                     }
2761                 }
2762             }
2763             return bp;
2764         }
2765 
2766         boolean isHorizontalPositionRelativeToSize() {
2767             return ((relative & 1) == 1);
2768         }
2769 
2770         boolean isHorizontalPositionRelativeToFontSize() {
2771             return ((relative & 2) == 2);
2772         }
2773 
2774         float getHorizontalPosition() {
2775             return horizontalPosition;
2776         }
2777 
2778         boolean isVerticalPositionRelativeToSize() {
2779             return ((relative & 4) == 4);
2780         }
2781 
2782         boolean isVerticalPositionRelativeToFontSize() {
2783             return ((relative & 8) == 8);
2784         }
2785 
2786         float getVerticalPosition() {
2787             return verticalPosition;
2788         }
2789     }
2790 
2791 
2792     /**
2793      * Used for BackgroundImages.
2794      */
2795     @SuppressWarnings("serial") // Same-version serialization only
2796     static class BackgroundImage extends CssValue {
2797         private boolean    loadedImage;
2798         private ImageIcon  image;
2799 
2800         Object parseCssValue(String value) {
2801             BackgroundImage retValue = new BackgroundImage();
2802             retValue.svalue = value;
2803             return retValue;
2804         }
2805 
2806         Object parseHtmlValue(String value) {
2807             return parseCssValue(value);
2808         }
2809 
2810         // PENDING: this base is wrong for linked style sheets.
2811         ImageIcon getImage(URL base) {
2812             if (!loadedImage) {
2813                 synchronized(this) {
2814                     if (!loadedImage) {
2815                         URL url = CSS.getURL(base, svalue);
2816                         loadedImage = true;
2817                         if (url != null) {
2818                             image = new ImageIcon();
2819                             Image tmpImg = Toolkit.getDefaultToolkit().createImage(url);
2820                             if (tmpImg != null) {
2821                                 image.setImage(tmpImg);
2822                             }
2823                         }
2824                     }
2825                 }
2826             }
2827             return image;
2828         }
2829     }
2830 
2831     /**
2832      * Parses a length value, this is used internally, and never added
2833      * to an AttributeSet or returned to the developer.
2834      */
2835     @SuppressWarnings("serial") // Same-version serialization only
2836     static class LengthUnit implements Serializable {
2837         static Hashtable<String, Float> lengthMapping = new Hashtable<String, Float>(6);
2838         static Hashtable<String, Float> w3cLengthMapping = new Hashtable<String, Float>(6);
2839         static {
2840             lengthMapping.put("pt", Float.valueOf(1f));
2841             // Not sure about 1.3, determined by experiementation.
2842             lengthMapping.put("px", Float.valueOf(1.3f));
2843             lengthMapping.put("mm", Float.valueOf(2.83464f));
2844             lengthMapping.put("cm", Float.valueOf(28.3464f));
2845             lengthMapping.put("pc", Float.valueOf(12f));
2846             lengthMapping.put("in", Float.valueOf(72f));
2847             int res = 72;
2848             try {
2849                 res = Toolkit.getDefaultToolkit().getScreenResolution();
2850             } catch (HeadlessException e) {
2851             }
2852             // mapping according to the CSS2 spec
2853             w3cLengthMapping.put("pt", Float.valueOf(res/72f));
2854             w3cLengthMapping.put("px", Float.valueOf(1f));
2855             w3cLengthMapping.put("mm", Float.valueOf(res/25.4f));
2856             w3cLengthMapping.put("cm", Float.valueOf(res/2.54f));
2857             w3cLengthMapping.put("pc", Float.valueOf(res/6f));
2858             w3cLengthMapping.put("in", Float.valueOf((float)res));
2859         }
2860 
2861         LengthUnit(String value, short defaultType, float defaultValue) {
2862             parse(value, defaultType, defaultValue);
2863         }
2864 
2865         void parse(String value, short defaultType, float defaultValue) {
2866             type = defaultType;
2867             this.value = defaultValue;
2868 
2869             int length = value.length();
2870             if (length > 0 && value.charAt(length - 1) == '%') {
2871                 try {
2872                     this.value = Float.valueOf(value.substring(0, length - 1)).
2873                                                floatValue() / 100.0f;
2874                     type = 1;
2875                 }
2876                 catch (NumberFormatException nfe) { }
2877             }
2878             if (length >= 2) {
2879                 units = value.substring(length - 2, length);
2880                 Float scale = lengthMapping.get(units);
2881                 if (scale != null) {
2882                     try {
2883                         this.value = Float.valueOf(value.substring(0,
2884                                length - 2)).floatValue();
2885                         type = 0;
2886                     }
2887                     catch (NumberFormatException nfe) { }
2888                 }
2889                 else if (units.equals("em") ||
2890                          units.equals("ex")) {
2891                     try {
2892                         this.value = Float.valueOf(value.substring(0,
2893                                       length - 2)).floatValue();
2894                         type = 3;
2895                     }
2896                     catch (NumberFormatException nfe) { }
2897                 }
2898                 else if (value.equals("larger")) {
2899                     this.value = 2f;
2900                     type = 2;
2901                 }
2902                 else if (value.equals("smaller")) {
2903                     this.value = -2;
2904                     type = 2;
2905                 }
2906                 else {
2907                     // treat like points.
2908                     try {
2909                         this.value = Float.valueOf(value).floatValue();
2910                         type = 0;
2911                     } catch (NumberFormatException nfe) {}
2912                 }
2913             }
2914             else if (length > 0) {
2915                 // treat like points.
2916                 try {
2917                     this.value = Float.valueOf(value).floatValue();
2918                     type = 0;
2919                 } catch (NumberFormatException nfe) {}
2920             }
2921         }
2922 
2923         float getValue(boolean w3cLengthUnits) {
2924             Hashtable<String, Float> mapping = (w3cLengthUnits) ? w3cLengthMapping : lengthMapping;
2925             float scale = 1;
2926             if (units != null) {
2927                 Float scaleFloat = mapping.get(units);
2928                 if (scaleFloat != null) {
2929                     scale = scaleFloat.floatValue();
2930                 }
2931             }
2932             return this.value * scale;
2933 
2934         }
2935 
2936         static float getValue(float value, String units, Boolean w3cLengthUnits) {
2937             Hashtable<String, Float> mapping = (w3cLengthUnits) ? w3cLengthMapping : lengthMapping;
2938             float scale = 1;
2939             if (units != null) {
2940                 Float scaleFloat = mapping.get(units);
2941                 if (scaleFloat != null) {
2942                     scale = scaleFloat.floatValue();
2943                 }
2944             }
2945             return value * scale;
2946         }
2947 
2948         public String toString() {
2949             return type + " " + value;
2950         }
2951 
2952         // 0 - value indicates real value
2953         // 1 - % value, value relative to depends upon key.
2954         //     50% will have a value = .5
2955         // 2 - add value to parent value.
2956         // 3 - em/ex relative to font size of element (except for
2957         //     font-size, which is relative to parent).
2958         short type;
2959         float value;
2960         String units = null;
2961 
2962 
2963         static final short UNINITALIZED_LENGTH = (short)10;
2964     }
2965 
2966 
2967     /**
2968      * Class used to parse font property. The font property is shorthand
2969      * for the other font properties. This expands the properties, placing
2970      * them in the attributeset.
2971      */
2972     static class ShorthandFontParser {
2973         /**
2974          * Parses the shorthand font string <code>value</code>, placing the
2975          * result in <code>attr</code>.
2976          */
2977         static void parseShorthandFont(CSS css, String value,
2978                                        MutableAttributeSet attr) {
2979             // font is of the form:
2980             // [ <font-style> || <font-variant> || <font-weight> ]? <font-size>
2981             //   [ / <line-height> ]? <font-family>
2982             String[]   strings = CSS.parseStrings(value);
2983             int        count = strings.length;
2984             int        index = 0;
2985             // bitmask, 1 for style, 2 for variant, 3 for weight
2986             short      found = 0;
2987             int        maxC = Math.min(3, count);
2988 
2989             // Check for font-style font-variant font-weight
2990             while (index < maxC) {
2991                 if ((found & 1) == 0 && isFontStyle(strings[index])) {
2992                     css.addInternalCSSValue(attr, CSS.Attribute.FONT_STYLE,
2993                                             strings[index++]);
2994                     found |= 1;
2995                 }
2996                 else if ((found & 2) == 0 && isFontVariant(strings[index])) {
2997                     css.addInternalCSSValue(attr, CSS.Attribute.FONT_VARIANT,
2998                                             strings[index++]);
2999                     found |= 2;
3000                 }
3001                 else if ((found & 4) == 0 && isFontWeight(strings[index])) {
3002                     css.addInternalCSSValue(attr, CSS.Attribute.FONT_WEIGHT,
3003                                             strings[index++]);
3004                     found |= 4;
3005                 }
3006                 else if (strings[index].equals("normal")) {
3007                     index++;
3008                 }
3009                 else {
3010                     break;
3011                 }
3012             }
3013             if ((found & 1) == 0) {
3014                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_STYLE,
3015                                         "normal");
3016             }
3017             if ((found & 2) == 0) {
3018                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_VARIANT,
3019                                         "normal");
3020             }
3021             if ((found & 4) == 0) {
3022                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_WEIGHT,
3023                                         "normal");
3024             }
3025 
3026             // string at index should be the font-size
3027             if (index < count) {
3028                 String fontSize = strings[index];
3029                 int slashIndex = fontSize.indexOf('/');
3030 
3031                 if (slashIndex != -1) {
3032                     fontSize = fontSize.substring(0, slashIndex);
3033                     strings[index] = strings[index].substring(slashIndex);
3034                 }
3035                 else {
3036                     index++;
3037                 }
3038                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_SIZE,
3039                                         fontSize);
3040             }
3041             else {
3042                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_SIZE,
3043                                         "medium");
3044             }
3045 
3046             // Check for line height
3047             if (index < count && strings[index].startsWith("/")) {
3048                 String lineHeight = null;
3049                 if (strings[index].equals("/")) {
3050                     if (++index < count) {
3051                         lineHeight = strings[index++];
3052                     }
3053                 }
3054                 else {
3055                     lineHeight = strings[index++].substring(1);
3056                 }
3057                 // line height
3058                 if (lineHeight != null) {
3059                     css.addInternalCSSValue(attr, CSS.Attribute.LINE_HEIGHT,
3060                                             lineHeight);
3061                 }
3062                 else {
3063                     css.addInternalCSSValue(attr, CSS.Attribute.LINE_HEIGHT,
3064                                             "normal");
3065                 }
3066             }
3067             else {
3068                 css.addInternalCSSValue(attr, CSS.Attribute.LINE_HEIGHT,
3069                                         "normal");
3070             }
3071 
3072             // remainder of strings are font-family
3073             if (index < count) {
3074                 String family = strings[index++];
3075 
3076                 while (index < count) {
3077                     family += " " + strings[index++];
3078                 }
3079                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_FAMILY,
3080                                         family);
3081             }
3082             else {
3083                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_FAMILY,
3084                                         Font.SANS_SERIF);
3085             }
3086         }
3087 
3088         private static boolean isFontStyle(String string) {
3089             return (string.equals("italic") ||
3090                     string.equals("oblique"));
3091         }
3092 
3093         private static boolean isFontVariant(String string) {
3094             return (string.equals("small-caps"));
3095         }
3096 
3097         private static boolean isFontWeight(String string) {
3098             if (string.equals("bold") || string.equals("bolder") ||
3099                 string.equals("italic") || string.equals("lighter")) {
3100                 return true;
3101             }
3102             // test for 100-900
3103             return (string.length() == 3 &&
3104                     string.charAt(0) >= '1' && string.charAt(0) <= '9' &&
3105                     string.charAt(1) == '0' && string.charAt(2) == '0');
3106         }
3107 
3108     }
3109 
3110 
3111     /**
3112      * Parses the background property into its intrinsic values.
3113      */
3114     static class ShorthandBackgroundParser {
3115         /**
3116          * Parses the shorthand font string <code>value</code>, placing the
3117          * result in <code>attr</code>.
3118          */
3119         static void parseShorthandBackground(CSS css, String value,
3120                                              MutableAttributeSet attr) {
3121             String[] strings = parseStrings(value);
3122             int count = strings.length;
3123             int index = 0;
3124             // bitmask: 0 for image, 1 repeat, 2 attachment, 3 position,
3125             //          4 color
3126             short found = 0;
3127 
3128             while (index < count) {
3129                 String string = strings[index++];
3130                 if ((found & 1) == 0 && isImage(string)) {
3131                     css.addInternalCSSValue(attr, CSS.Attribute.
3132                                             BACKGROUND_IMAGE, string);
3133                     found |= 1;
3134                 }
3135                 else if ((found & 2) == 0 && isRepeat(string)) {
3136                     css.addInternalCSSValue(attr, CSS.Attribute.
3137                                             BACKGROUND_REPEAT, string);
3138                     found |= 2;
3139                 }
3140                 else if ((found & 4) == 0 && isAttachment(string)) {
3141                     css.addInternalCSSValue(attr, CSS.Attribute.
3142                                             BACKGROUND_ATTACHMENT, string);
3143                     found |= 4;
3144                 }
3145                 else if ((found & 8) == 0 && isPosition(string)) {
3146                     if (index < count && isPosition(strings[index])) {
3147                         css.addInternalCSSValue(attr, CSS.Attribute.
3148                                                 BACKGROUND_POSITION,
3149                                                 string + " " +
3150                                                 strings[index++]);
3151                     }
3152                     else {
3153                         css.addInternalCSSValue(attr, CSS.Attribute.
3154                                                 BACKGROUND_POSITION, string);
3155                     }
3156                     found |= 8;
3157                 }
3158                 else if ((found & 16) == 0 && isColor(string)) {
3159                     css.addInternalCSSValue(attr, CSS.Attribute.
3160                                             BACKGROUND_COLOR, string);
3161                     found |= 16;
3162                 }
3163             }
3164             if ((found & 1) == 0) {
3165                 css.addInternalCSSValue(attr, CSS.Attribute.BACKGROUND_IMAGE,
3166                                         null);
3167             }
3168             if ((found & 2) == 0) {
3169                 css.addInternalCSSValue(attr, CSS.Attribute.BACKGROUND_REPEAT,
3170                                         "repeat");
3171             }
3172             if ((found & 4) == 0) {
3173                 css.addInternalCSSValue(attr, CSS.Attribute.
3174                                         BACKGROUND_ATTACHMENT, "scroll");
3175             }
3176             if ((found & 8) == 0) {
3177                 css.addInternalCSSValue(attr, CSS.Attribute.
3178                                         BACKGROUND_POSITION, null);
3179             }
3180             // Currently, there is no good way to express this.
3181             /*
3182             if ((found & 16) == 0) {
3183                 css.addInternalCSSValue(attr, CSS.Attribute.BACKGROUND_COLOR,
3184                                         null);
3185             }
3186             */
3187         }
3188 
3189         static boolean isImage(String string) {
3190             return (string.startsWith("url(") && string.endsWith(")"));
3191         }
3192 
3193         static boolean isRepeat(String string) {
3194             return (string.equals("repeat-x") || string.equals("repeat-y") ||
3195                     string.equals("repeat") || string.equals("no-repeat"));
3196         }
3197 
3198         static boolean isAttachment(String string) {
3199             return (string.equals("fixed") || string.equals("scroll"));
3200         }
3201 
3202         static boolean isPosition(String string) {
3203             return (string.equals("top") || string.equals("bottom") ||
3204                     string.equals("left") || string.equals("right") ||
3205                     string.equals("center") ||
3206                     (string.length() > 0 &&
3207                      Character.isDigit(string.charAt(0))));
3208         }
3209 
3210         static boolean isColor(String string) {
3211             return (CSS.stringToColor(string) != null);
3212         }
3213     }
3214 
3215 
3216     /**
3217      * Used to parser margin and padding.
3218      */
3219     static class ShorthandMarginParser {
3220         /**
3221          * Parses the shorthand margin/padding/border string
3222          * <code>value</code>, placing the result in <code>attr</code>.
3223          * <code>names</code> give the 4 instrinsic property names.
3224          */
3225         static void parseShorthandMargin(CSS css, String value,
3226                                          MutableAttributeSet attr,
3227                                          CSS.Attribute[] names) {
3228             String[] strings = parseStrings(value);
3229             int count = strings.length;
3230             int index = 0;
3231             switch (count) {
3232             case 0:
3233                 // empty string
3234                 return;
3235             case 1:
3236                 // Identifies all values.
3237                 for (int counter = 0; counter < 4; counter++) {
3238                     css.addInternalCSSValue(attr, names[counter], strings[0]);
3239                 }
3240                 break;
3241             case 2:
3242                 // 0 & 2 = strings[0], 1 & 3 = strings[1]
3243                 css.addInternalCSSValue(attr, names[0], strings[0]);
3244                 css.addInternalCSSValue(attr, names[2], strings[0]);
3245                 css.addInternalCSSValue(attr, names[1], strings[1]);
3246                 css.addInternalCSSValue(attr, names[3], strings[1]);
3247                 break;
3248             case 3:
3249                 css.addInternalCSSValue(attr, names[0], strings[0]);
3250                 css.addInternalCSSValue(attr, names[1], strings[1]);
3251                 css.addInternalCSSValue(attr, names[2], strings[2]);
3252                 css.addInternalCSSValue(attr, names[3], strings[1]);
3253                 break;
3254             default:
3255                 for (int counter = 0; counter < 4; counter++) {
3256                     css.addInternalCSSValue(attr, names[counter],
3257                                             strings[counter]);
3258                 }
3259                 break;
3260             }
3261         }
3262     }
3263 
3264     static class ShorthandBorderParser {
3265         static Attribute[] keys = {
3266             Attribute.BORDER_TOP, Attribute.BORDER_RIGHT,
3267             Attribute.BORDER_BOTTOM, Attribute.BORDER_LEFT,
3268         };
3269 
3270         static void parseShorthandBorder(MutableAttributeSet attributes,
3271                                             CSS.Attribute key, String value) {
3272             Object[] parts = new Object[CSSBorder.PARSERS.length];
3273             String[] strings = parseStrings(value);
3274             for (String s : strings) {
3275                 boolean valid = false;
3276                 for (int i = 0; i < parts.length; i++) {
3277                     Object v = CSSBorder.PARSERS[i].parseCssValue(s);
3278                     if (v != null) {
3279                         if (parts[i] == null) {
3280                             parts[i] = v;
3281                             valid = true;
3282                         }
3283                         break;
3284                     }
3285                 }
3286                 if (!valid) {
3287                     // Part is non-parseable or occurred more than once.
3288                     return;
3289                 }
3290             }
3291 
3292             // Unspecified parts get default values.
3293             for (int i = 0; i < parts.length; i++) {
3294                 if (parts[i] == null) {
3295                     parts[i] = CSSBorder.DEFAULTS[i];
3296                 }
3297             }
3298 
3299             // Dispatch collected values to individual properties.
3300             for (int i = 0; i < keys.length; i++) {
3301                 if ((key == Attribute.BORDER) || (key == keys[i])) {
3302                     for (int k = 0; k < parts.length; k++) {
3303                         attributes.addAttribute(
3304                                         CSSBorder.ATTRIBUTES[k][i], parts[k]);
3305                     }
3306                 }
3307             }
3308         }
3309     }
3310 
3311     /**
3312      * Calculate the requirements needed to tile the requirements
3313      * given by the iterator that would be tiled.  The calculation
3314      * takes into consideration margin and border spacing.
3315      */
3316     static SizeRequirements calculateTiledRequirements(LayoutIterator iter, SizeRequirements r) {
3317         long minimum = 0;
3318         long maximum = 0;
3319         long preferred = 0;
3320         int lastMargin = 0;
3321         int totalSpacing = 0;
3322         int n = iter.getCount();
3323         for (int i = 0; i < n; i++) {
3324             iter.setIndex(i);
3325             int margin0 = lastMargin;
3326             int margin1 = (int) iter.getLeadingCollapseSpan();
3327             totalSpacing += Math.max(margin0, margin1);
3328             preferred += (int) iter.getPreferredSpan(0);
3329             minimum += iter.getMinimumSpan(0);
3330             maximum += iter.getMaximumSpan(0);
3331 
3332             lastMargin = (int) iter.getTrailingCollapseSpan();
3333         }
3334         totalSpacing += lastMargin;
3335         totalSpacing += 2 * iter.getBorderWidth();
3336 
3337         // adjust for the spacing area
3338         minimum += totalSpacing;
3339         preferred += totalSpacing;
3340         maximum += totalSpacing;
3341 
3342         // set return value
3343         if (r == null) {
3344             r = new SizeRequirements();
3345         }
3346         r.minimum = (minimum > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)minimum;
3347         r.preferred = (preferred > Integer.MAX_VALUE) ? Integer.MAX_VALUE :(int) preferred;
3348         r.maximum = (maximum > Integer.MAX_VALUE) ? Integer.MAX_VALUE :(int) maximum;
3349         return r;
3350     }
3351 
3352     /**
3353      * Calculate a tiled layout for the given iterator.
3354      * This should be done collapsing the neighboring
3355      * margins to be a total of the maximum of the two
3356      * neighboring margin areas as described in the CSS spec.
3357      */
3358     static void calculateTiledLayout(LayoutIterator iter, int targetSpan) {
3359 
3360         /*
3361          * first pass, calculate the preferred sizes, adjustments needed because
3362          * of margin collapsing, and the flexibility to adjust the sizes.
3363          */
3364         long preferred = 0;
3365         long currentPreferred;
3366         int lastMargin = 0;
3367         int totalSpacing = 0;
3368         int n = iter.getCount();
3369         int adjustmentWeightsCount = LayoutIterator.WorstAdjustmentWeight + 1;
3370         //max gain we can get adjusting elements with adjustmentWeight <= i
3371         long gain[] = new long[adjustmentWeightsCount];
3372         //max loss we can get adjusting elements with adjustmentWeight <= i
3373         long loss[] = new long[adjustmentWeightsCount];
3374 
3375         for (int i = 0; i < adjustmentWeightsCount; i++) {
3376             gain[i] = loss[i] = 0;
3377         }
3378         for (int i = 0; i < n; i++) {
3379             iter.setIndex(i);
3380             int margin0 = lastMargin;
3381             int margin1 = (int) iter.getLeadingCollapseSpan();
3382 
3383             iter.setOffset(Math.max(margin0, margin1));
3384             totalSpacing += iter.getOffset();
3385 
3386             currentPreferred = (long)iter.getPreferredSpan(targetSpan);
3387             iter.setSpan((int) currentPreferred);
3388             preferred += currentPreferred;
3389             gain[iter.getAdjustmentWeight()] +=
3390                 (long)iter.getMaximumSpan(targetSpan) - currentPreferred;
3391             loss[iter.getAdjustmentWeight()] +=
3392                 currentPreferred - (long)iter.getMinimumSpan(targetSpan);
3393             lastMargin = (int) iter.getTrailingCollapseSpan();
3394         }
3395         totalSpacing += lastMargin;
3396         totalSpacing += 2 * iter.getBorderWidth();
3397 
3398         for (int i = 1; i < adjustmentWeightsCount; i++) {
3399             gain[i] += gain[i - 1];
3400             loss[i] += loss[i - 1];
3401         }
3402 
3403         /*
3404          * Second pass, expand or contract by as much as possible to reach
3405          * the target span.  This takes the margin collapsing into account
3406          * prior to adjusting the span.
3407          */
3408 
3409         // determine the adjustment to be made
3410         int allocated = targetSpan - totalSpacing;
3411         long desiredAdjustment = allocated - preferred;
3412         long adjustmentsArray[] = (desiredAdjustment > 0) ? gain : loss;
3413         desiredAdjustment = Math.abs(desiredAdjustment);
3414         int adjustmentLevel = 0;
3415         for (;adjustmentLevel <= LayoutIterator.WorstAdjustmentWeight;
3416              adjustmentLevel++) {
3417             // adjustmentsArray[] is sorted. I do not bother about
3418             // binary search though
3419             if (adjustmentsArray[adjustmentLevel] >= desiredAdjustment) {
3420                 break;
3421             }
3422         }
3423         float adjustmentFactor = 0.0f;
3424         if (adjustmentLevel <= LayoutIterator.WorstAdjustmentWeight) {
3425             desiredAdjustment -= (adjustmentLevel > 0) ?
3426                 adjustmentsArray[adjustmentLevel - 1] : 0;
3427             if (desiredAdjustment != 0) {
3428                 float maximumAdjustment =
3429                     adjustmentsArray[adjustmentLevel] -
3430                     ((adjustmentLevel > 0) ?
3431                      adjustmentsArray[adjustmentLevel - 1] : 0
3432                      );
3433                 adjustmentFactor = desiredAdjustment / maximumAdjustment;
3434             }
3435         }
3436         // make the adjustments
3437         int totalOffset = (int)iter.getBorderWidth();
3438         for (int i = 0; i < n; i++) {
3439             iter.setIndex(i);
3440             iter.setOffset( iter.getOffset() + totalOffset);
3441             if (iter.getAdjustmentWeight() < adjustmentLevel) {
3442                 iter.setSpan((int)
3443                              ((allocated > preferred) ?
3444                               Math.floor(iter.getMaximumSpan(targetSpan)) :
3445                               Math.ceil(iter.getMinimumSpan(targetSpan))
3446                               )
3447                              );
3448             } else if (iter.getAdjustmentWeight() == adjustmentLevel) {
3449                 int availableSpan = (allocated > preferred) ?
3450                     (int) iter.getMaximumSpan(targetSpan) - iter.getSpan() :
3451                     iter.getSpan() - (int) iter.getMinimumSpan(targetSpan);
3452                 int adj = (int)Math.floor(adjustmentFactor * availableSpan);
3453                 iter.setSpan(iter.getSpan() +
3454                              ((allocated > preferred) ? adj : -adj));
3455             }
3456             totalOffset = (int) Math.min((long) iter.getOffset() +
3457                                          (long) iter.getSpan(),
3458                                          Integer.MAX_VALUE);
3459         }
3460 
3461         // while rounding we could lose several pixels.
3462         int roundError = targetSpan - totalOffset -
3463             (int)iter.getTrailingCollapseSpan() -
3464             (int)iter.getBorderWidth();
3465         int adj = (roundError > 0) ? 1 : -1;
3466         roundError *= adj;
3467 
3468         boolean canAdjust = true;
3469         while (roundError > 0 && canAdjust) {
3470             // check for infinite loop
3471             canAdjust = false;
3472             int offsetAdjust = 0;
3473             // try to distribute roundError. one pixel per cell
3474             for (int i = 0; i < n; i++) {
3475                 iter.setIndex(i);
3476                 iter.setOffset(iter.getOffset() + offsetAdjust);
3477                 int curSpan = iter.getSpan();
3478                 if (roundError > 0) {
3479                     int boundGap = (adj > 0) ?
3480                         (int)Math.floor(iter.getMaximumSpan(targetSpan)) - curSpan :
3481                         curSpan - (int)Math.ceil(iter.getMinimumSpan(targetSpan));
3482                     if (boundGap >= 1) {
3483                         canAdjust = true;
3484                         iter.setSpan(curSpan + adj);
3485                         offsetAdjust += adj;
3486                         roundError--;
3487                     }
3488                 }
3489             }
3490         }
3491     }
3492 
3493     /**
3494      * An iterator to express the requirements to use when computing
3495      * layout.
3496      */
3497     interface LayoutIterator {
3498 
3499         void setOffset(int offs);
3500 
3501         int getOffset();
3502 
3503         void setSpan(int span);
3504 
3505         int getSpan();
3506 
3507         int getCount();
3508 
3509         void setIndex(int i);
3510 
3511         float getMinimumSpan(float parentSpan);
3512 
3513         float getPreferredSpan(float parentSpan);
3514 
3515         float getMaximumSpan(float parentSpan);
3516 
3517         int getAdjustmentWeight(); //0 is the best weight WorstAdjustmentWeight is a worst one
3518 
3519         //float getAlignment();
3520 
3521         float getBorderWidth();
3522 
3523         float getLeadingCollapseSpan();
3524 
3525         float getTrailingCollapseSpan();
3526         public static final int WorstAdjustmentWeight = 2;
3527     }
3528 
3529     //
3530     // Serialization support
3531     //
3532 
3533     private void writeObject(java.io.ObjectOutputStream s)
3534         throws IOException
3535     {
3536         s.defaultWriteObject();
3537 
3538         // Determine what values in valueConvertor need to be written out.
3539         Enumeration<?> keys = valueConvertor.keys();
3540         s.writeInt(valueConvertor.size());
3541         if (keys != null) {
3542             while (keys.hasMoreElements()) {
3543                 Object key = keys.nextElement();
3544                 Object value = valueConvertor.get(key);
3545                 if (!(key instanceof Serializable) &&
3546                     (key = StyleContext.getStaticAttributeKey(key)) == null) {
3547                     // Should we throw an exception here?
3548                     key = null;
3549                     value = null;
3550                 }
3551                 else if (!(value instanceof Serializable) &&
3552                     (value = StyleContext.getStaticAttributeKey(value)) == null){
3553                     // Should we throw an exception here?
3554                     key = null;
3555                     value = null;
3556                 }
3557                 s.writeObject(key);
3558                 s.writeObject(value);
3559             }
3560         }
3561     }
3562 
3563     private void readObject(ObjectInputStream s)
3564       throws ClassNotFoundException, IOException
3565     {
3566         ObjectInputStream.GetField f = s.readFields();
3567         int newBaseFontSize = f.get("baseFontSize", 0);
3568         setBaseFontSize(newBaseFontSize);
3569 
3570         // Reconstruct the hashtable.
3571         int numValues = s.readInt();
3572         valueConvertor = new Hashtable<>(Math.max(1, numValues));
3573         while (numValues-- > 0) {
3574             Object key = s.readObject();
3575             Object value = s.readObject();
3576             Object staticKey = StyleContext.getStaticAttribute(key);
3577             if (staticKey != null) {
3578                 key = staticKey;
3579             }
3580             Object staticValue = StyleContext.getStaticAttribute(value);
3581             if (staticValue != null) {
3582                 value = staticValue;
3583             }
3584             if (key != null && value != null) {
3585                 valueConvertor.put(key, value);
3586             }
3587         }
3588     }
3589 
3590 
3591     /*
3592      * we need StyleSheet for resolving lenght units. (see
3593      * isW3CLengthUnits)
3594      * we can not pass stylesheet for handling relative sizes. (do not
3595      * think changing public API is necessary)
3596      * CSS is not likely to be accessed from more then one thread.
3597      * Having local storage for StyleSheet for resolving relative
3598      * sizes is safe
3599      *
3600      * idk 08/30/2004
3601      */
3602     private StyleSheet getStyleSheet(StyleSheet ss) {
3603         if (ss != null) {
3604             styleSheet = ss;
3605         }
3606         return styleSheet;
3607     }
3608     //
3609     // Instance variables
3610     //
3611 
3612     /** Maps from CSS key to CssValue. */
3613     private transient Hashtable<Object, Object> valueConvertor;
3614 
3615     /** Size used for relative units. */
3616     private int baseFontSize;
3617 
3618     private transient StyleSheet styleSheet = null;
3619 
3620     static int baseFontSizeIndex = 3;
3621 }