1 /*
   2  * Copyright (c) 1998, 2014, 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} is a CSS value, and is
 739      * not necessarily the point size. Use getPointSize to determine the
 740      * point size corresponding to {@code sz}.
 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} with value
 779      * {@code value} placing the result in {@code att}.
 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} which is
 827      * a CSS value of the CSS attribute named {@code key}. The receiver
 828      * should not modify {@code value}, and the first {@code count}
 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} 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} 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}.
 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} with
 973      * key {@code key}.
 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} object.
1240      * This will return {@code null} 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} object,
1246      *  or {@code null} 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} object.
1255      * This will return {@code null} 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} object,
1261      *  or {@code null} 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} 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)} 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} starting
1446      * at {@code index[0]}. 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}
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             while (current < length && !Character.isWhitespace
1526                    (value.charAt(current))) {
1527                 current++;
1528             }
1529             if (last != current) {
1530                 temp.addElement(value.substring(last, current));
1531             }
1532             current++;
1533         }
1534         String[] retValue = new String[temp.size()];
1535         temp.copyInto(retValue);
1536         return retValue;
1537     }
1538 
1539     /**
1540      * Return the point size, given a size index. Legal HTML index sizes
1541      * are 1-7.
1542      */
1543     float getPointSize(int index, StyleSheet ss) {
1544         ss = getStyleSheet(ss);
1545         int[] sizeMap = (ss != null) ? ss.getSizeMap() :
1546             StyleSheet.sizeMapDefault;
1547         --index;
1548         if (index < 0)
1549           return sizeMap[0];
1550         else if (index > sizeMap.length - 1)
1551           return sizeMap[sizeMap.length - 1];
1552         else
1553           return sizeMap[index];
1554     }
1555 
1556 
1557     private void translateEmbeddedAttributes(AttributeSet htmlAttrSet,
1558                                              MutableAttributeSet cssAttrSet) {
1559         Enumeration<?> keys = htmlAttrSet.getAttributeNames();
1560         if (htmlAttrSet.getAttribute(StyleConstants.NameAttribute) ==
1561             HTML.Tag.HR) {
1562             // HR needs special handling due to us treating it as a leaf.
1563             translateAttributes(HTML.Tag.HR, htmlAttrSet, cssAttrSet);
1564         }
1565         while (keys.hasMoreElements()) {
1566             Object key = keys.nextElement();
1567             if (key instanceof HTML.Tag) {
1568                 HTML.Tag tag = (HTML.Tag)key;
1569                 Object o = htmlAttrSet.getAttribute(tag);
1570                 if (o != null && o instanceof AttributeSet) {
1571                     translateAttributes(tag, (AttributeSet)o, cssAttrSet);
1572                 }
1573             } else if (key instanceof CSS.Attribute) {
1574                 cssAttrSet.addAttribute(key, htmlAttrSet.getAttribute(key));
1575             }
1576         }
1577     }
1578 
1579     private void translateAttributes(HTML.Tag tag,
1580                                             AttributeSet htmlAttrSet,
1581                                             MutableAttributeSet cssAttrSet) {
1582         Enumeration<?> names = htmlAttrSet.getAttributeNames();
1583         while (names.hasMoreElements()) {
1584             Object name = names.nextElement();
1585 
1586             if (name instanceof HTML.Attribute) {
1587                 HTML.Attribute key = (HTML.Attribute)name;
1588 
1589                 /*
1590                  * HTML.Attribute.ALIGN needs special processing.
1591                  * It can map to 1 of many(3) possible CSS attributes
1592                  * depending on the nature of the tag the attribute is
1593                  * part off and depending on the value of the attribute.
1594                  */
1595                 if (key == HTML.Attribute.ALIGN) {
1596                     String htmlAttrValue = (String)htmlAttrSet.getAttribute(HTML.Attribute.ALIGN);
1597                     if (htmlAttrValue != null) {
1598                         CSS.Attribute cssAttr = getCssAlignAttribute(tag, htmlAttrSet);
1599                         if (cssAttr != null) {
1600                             Object o = getCssValue(cssAttr, htmlAttrValue);
1601                             if (o != null) {
1602                                 cssAttrSet.addAttribute(cssAttr, o);
1603                             }
1604                         }
1605                     }
1606                 } else {
1607                     if (key == HTML.Attribute.SIZE && !isHTMLFontTag(tag)) {
1608                         /*
1609                          * The html size attribute has a mapping in the CSS world only
1610                          * if it is par of a font or base font tag.
1611                          */
1612                     } else if (tag == HTML.Tag.TABLE && key == HTML.Attribute.BORDER) {
1613                         int borderWidth = getTableBorder(htmlAttrSet);
1614 
1615                         if (borderWidth > 0) {
1616                             translateAttribute(HTML.Attribute.BORDER, Integer.toString(borderWidth), cssAttrSet);
1617                         }
1618                     } else {
1619                         translateAttribute(key, (String) htmlAttrSet.getAttribute(key), cssAttrSet);
1620                     }
1621                 }
1622             } else if (name instanceof CSS.Attribute) {
1623                 cssAttrSet.addAttribute(name, htmlAttrSet.getAttribute(name));
1624             }
1625         }
1626     }
1627 
1628     private void translateAttribute(HTML.Attribute key,
1629                                            String htmlAttrValue,
1630                                            MutableAttributeSet cssAttrSet) {
1631         /*
1632          * In the case of all remaining HTML.Attribute's they
1633          * map to 1 or more CCS.Attribute.
1634          */
1635         CSS.Attribute[] cssAttrList = getCssAttribute(key);
1636 
1637         if (cssAttrList == null || htmlAttrValue == null) {
1638             return;
1639         }
1640         for (Attribute cssAttr : cssAttrList) {
1641             Object o = getCssValue(cssAttr, htmlAttrValue);
1642             if (o != null) {
1643                 cssAttrSet.addAttribute(cssAttr , o);
1644             }
1645         }
1646     }
1647 
1648     /**
1649      * Given a CSS.Attribute object and its corresponding HTML.Attribute's
1650      * value, this method returns a CssValue object to associate with the
1651      * CSS attribute.
1652      *
1653      * @param the CSS.Attribute
1654      * @param a String containing the value associated HTML.Attribtue.
1655      */
1656     Object getCssValue(CSS.Attribute cssAttr, String htmlAttrValue) {
1657         CssValue value = (CssValue)valueConvertor.get(cssAttr);
1658         Object o = value.parseHtmlValue(htmlAttrValue);
1659         return o;
1660     }
1661 
1662     /**
1663      * Maps an HTML.Attribute object to its appropriate CSS.Attributes.
1664      *
1665      * @param HTML.Attribute
1666      * @return CSS.Attribute[]
1667      */
1668     private CSS.Attribute[] getCssAttribute(HTML.Attribute hAttr) {
1669         return htmlAttrToCssAttrMap.get(hAttr);
1670     }
1671 
1672     /**
1673      * Maps HTML.Attribute.ALIGN to either:
1674      *     CSS.Attribute.TEXT_ALIGN
1675      *     CSS.Attribute.FLOAT
1676      *     CSS.Attribute.VERTICAL_ALIGN
1677      * based on the tag associated with the attribute and the
1678      * value of the attribute.
1679      *
1680      * @param AttributeSet containing HTML attributes.
1681      * @return CSS.Attribute mapping for HTML.Attribute.ALIGN.
1682      */
1683     private CSS.Attribute getCssAlignAttribute(HTML.Tag tag,
1684                                                    AttributeSet htmlAttrSet) {
1685         return CSS.Attribute.TEXT_ALIGN;
1686 /*
1687         String htmlAttrValue = (String)htmlAttrSet.getAttribute(HTML.Attribute.ALIGN);
1688         CSS.Attribute cssAttr = CSS.Attribute.TEXT_ALIGN;
1689         if (htmlAttrValue != null && htmlAttrSet instanceof Element) {
1690             Element elem = (Element)htmlAttrSet;
1691             if (!elem.isLeaf() && tag.isBlock() && validTextAlignValue(htmlAttrValue)) {
1692                 return CSS.Attribute.TEXT_ALIGN;
1693             } else if (isFloater(htmlAttrValue)) {
1694                 return CSS.Attribute.FLOAT;
1695             } else if (elem.isLeaf()) {
1696                 return CSS.Attribute.VERTICAL_ALIGN;
1697             }
1698         }
1699         return null;
1700         */
1701     }
1702 
1703     /**
1704      * Fetches the tag associated with the HTML AttributeSet.
1705      *
1706      * @param  AttributeSet containing the HTML attributes.
1707      * @return HTML.Tag
1708      */
1709     private HTML.Tag getHTMLTag(AttributeSet htmlAttrSet) {
1710         Object o = htmlAttrSet.getAttribute(StyleConstants.NameAttribute);
1711         if (o instanceof HTML.Tag) {
1712             HTML.Tag tag = (HTML.Tag) o;
1713             return tag;
1714         }
1715         return null;
1716     }
1717 
1718 
1719     private boolean isHTMLFontTag(HTML.Tag tag) {
1720         return (tag != null && ((tag == HTML.Tag.FONT) || (tag == HTML.Tag.BASEFONT)));
1721     }
1722 
1723 
1724     private boolean isFloater(String alignValue) {
1725         return (alignValue.equals("left") || alignValue.equals("right"));
1726     }
1727 
1728     private boolean validTextAlignValue(String alignValue) {
1729         return (isFloater(alignValue) || alignValue.equals("center"));
1730     }
1731 
1732     /**
1733      * Base class to CSS values in the attribute sets.  This
1734      * is intended to act as a convertor to/from other attribute
1735      * formats.
1736      * <p>
1737      * The CSS parser uses the parseCssValue method to convert
1738      * a string to whatever format is appropriate a given key
1739      * (i.e. these convertors are stored in a map using the
1740      * CSS.Attribute as a key and the CssValue as the value).
1741      * <p>
1742      * The HTML to CSS conversion process first converts the
1743      * HTML.Attribute to a CSS.Attribute, and then calls
1744      * the parseHtmlValue method on the value of the HTML
1745      * attribute to produce the corresponding CSS value.
1746      * <p>
1747      * The StyleConstants to CSS conversion process first
1748      * converts the StyleConstants attribute to a
1749      * CSS.Attribute, and then calls the fromStyleConstants
1750      * method to convert the StyleConstants value to a
1751      * CSS value.
1752      * <p>
1753      * The CSS to StyleConstants conversion process first
1754      * converts the StyleConstants attribute to a
1755      * CSS.Attribute, and then calls the toStyleConstants
1756      * method to convert the CSS value to a StyleConstants
1757      * value.
1758      */
1759     @SuppressWarnings("serial") // Same-version serialization only
1760     static class CssValue implements Serializable {
1761 
1762         /**
1763          * Convert a CSS value string to the internal format
1764          * (for fast processing) used in the attribute sets.
1765          * The fallback storage for any value that we don't
1766          * have a special binary format for is a String.
1767          */
1768         Object parseCssValue(String value) {
1769             return value;
1770         }
1771 
1772         /**
1773          * Convert an HTML attribute value to a CSS attribute
1774          * value.  If there is no conversion, return null.
1775          * This is implemented to simply forward to the CSS
1776          * parsing by default (since some of the attribute
1777          * values are the same).  If the attribute value
1778          * isn't recognized as a CSS value it is generally
1779          * returned as null.
1780          */
1781         Object parseHtmlValue(String value) {
1782             return parseCssValue(value);
1783         }
1784 
1785         /**
1786          * Converts a {@code StyleConstants} attribute value to
1787          * a CSS attribute value.  If there is no conversion,
1788          * returns {@code null}.  By default, there is no conversion.
1789          *
1790          * @param key the {@code StyleConstants} attribute
1791          * @param value the value of a {@code StyleConstants}
1792          *   attribute to be converted
1793          * @return the CSS value that represents the
1794          *   {@code StyleConstants} value
1795          */
1796         Object fromStyleConstants(StyleConstants key, Object value) {
1797             return null;
1798         }
1799 
1800         /**
1801          * Converts a CSS attribute value to a
1802          * {@code StyleConstants}
1803          * value.  If there is no conversion, returns
1804          * {@code null}.
1805          * By default, there is no conversion.
1806          *
1807          * @param key the {@code StyleConstants} attribute
1808          * @param v the view containing {@code AttributeSet}
1809          * @return the {@code StyleConstants} attribute value that
1810          *   represents the CSS attribute value
1811          */
1812         Object toStyleConstants(StyleConstants key, View v) {
1813             return null;
1814         }
1815 
1816         /**
1817          * Return the CSS format of the value
1818          */
1819         public String toString() {
1820             return svalue;
1821         }
1822 
1823         /**
1824          * The value as a string... before conversion to a
1825          * binary format.
1826          */
1827         String svalue;
1828     }
1829 
1830     /**
1831      * By default CSS attributes are represented as simple
1832      * strings.  They also have no conversion to/from
1833      * StyleConstants by default. This class represents the
1834      * value as a string (via the superclass), but
1835      * provides StyleConstants conversion support for the
1836      * CSS attributes that are held as strings.
1837      */
1838     @SuppressWarnings("serial") // Same-version serialization only
1839     static class StringValue extends CssValue {
1840 
1841         /**
1842          * Convert a CSS value string to the internal format
1843          * (for fast processing) used in the attribute sets.
1844          * This produces a StringValue, so that it can be
1845          * used to convert from CSS to StyleConstants values.
1846          */
1847         Object parseCssValue(String value) {
1848             StringValue sv = new StringValue();
1849             sv.svalue = value;
1850             return sv;
1851         }
1852 
1853         /**
1854          * Converts a {@code StyleConstants} attribute value to
1855          * a CSS attribute value.  If there is no conversion
1856          * returns {@code null}.
1857          *
1858          * @param key the {@code StyleConstants} attribute
1859          * @param value the value of a {@code StyleConstants}
1860          *   attribute to be converted
1861          * @return the CSS value that represents the
1862          *   {@code StyleConstants} value
1863          */
1864         Object fromStyleConstants(StyleConstants key, Object value) {
1865             if (key == StyleConstants.Italic) {
1866                 if (value.equals(Boolean.TRUE)) {
1867                     return parseCssValue("italic");
1868                 }
1869                 return parseCssValue("");
1870             } else if (key == StyleConstants.Underline) {
1871                 if (value.equals(Boolean.TRUE)) {
1872                     return parseCssValue("underline");
1873                 }
1874                 return parseCssValue("");
1875             } else if (key == StyleConstants.Alignment) {
1876                 int align = ((Integer)value).intValue();
1877                 String ta;
1878                 switch(align) {
1879                 case StyleConstants.ALIGN_LEFT:
1880                     ta = "left";
1881                     break;
1882                 case StyleConstants.ALIGN_RIGHT:
1883                     ta = "right";
1884                     break;
1885                 case StyleConstants.ALIGN_CENTER:
1886                     ta = "center";
1887                     break;
1888                 case StyleConstants.ALIGN_JUSTIFIED:
1889                     ta = "justify";
1890                     break;
1891                 default:
1892                     ta = "left";
1893                 }
1894                 return parseCssValue(ta);
1895             } else if (key == StyleConstants.StrikeThrough) {
1896                 if (value.equals(Boolean.TRUE)) {
1897                     return parseCssValue("line-through");
1898                 }
1899                 return parseCssValue("");
1900             } else if (key == StyleConstants.Superscript) {
1901                 if (value.equals(Boolean.TRUE)) {
1902                     return parseCssValue("super");
1903                 }
1904                 return parseCssValue("");
1905             } else if (key == StyleConstants.Subscript) {
1906                 if (value.equals(Boolean.TRUE)) {
1907                     return parseCssValue("sub");
1908                 }
1909                 return parseCssValue("");
1910             }
1911             return null;
1912         }
1913 
1914         /**
1915          * Converts a CSS attribute value to a
1916          * {@code StyleConstants} value.
1917          * If there is no conversion, returns {@code null}.
1918          * By default, there is no conversion.
1919          *
1920          * @param key the {@code StyleConstants} attribute
1921          * @return the {@code StyleConstants} attribute value that
1922          *   represents the CSS attribute value
1923          */
1924         Object toStyleConstants(StyleConstants key, View v) {
1925             if (key == StyleConstants.Italic) {
1926                 if (svalue.indexOf("italic") >= 0) {
1927                     return Boolean.TRUE;
1928                 }
1929                 return Boolean.FALSE;
1930             } else if (key == StyleConstants.Underline) {
1931                 if (svalue.indexOf("underline") >= 0) {
1932                     return Boolean.TRUE;
1933                 }
1934                 return Boolean.FALSE;
1935             } else if (key == StyleConstants.Alignment) {
1936                 if (svalue.equals("right")) {
1937                     return StyleConstants.ALIGN_RIGHT;
1938                 } else if (svalue.equals("center")) {
1939                     return StyleConstants.ALIGN_CENTER;
1940                 } else if  (svalue.equals("justify")) {
1941                     return StyleConstants.ALIGN_JUSTIFIED;
1942                 }
1943                 return StyleConstants.ALIGN_LEFT;
1944             } else if (key == StyleConstants.StrikeThrough) {
1945                 if (svalue.indexOf("line-through") >= 0) {
1946                     return Boolean.TRUE;
1947                 }
1948                 return Boolean.FALSE;
1949             } else if (key == StyleConstants.Superscript) {
1950                 if (svalue.indexOf("super") >= 0) {
1951                     return Boolean.TRUE;
1952                 }
1953                 return Boolean.FALSE;
1954             } else if (key == StyleConstants.Subscript) {
1955                 if (svalue.indexOf("sub") >= 0) {
1956                     return Boolean.TRUE;
1957                 }
1958                 return Boolean.FALSE;
1959             }
1960             return null;
1961         }
1962 
1963         // Used by ViewAttributeSet
1964         boolean isItalic() {
1965             return (svalue.indexOf("italic") != -1);
1966         }
1967 
1968         boolean isStrike() {
1969             return (svalue.indexOf("line-through") != -1);
1970         }
1971 
1972         boolean isUnderline() {
1973             return (svalue.indexOf("underline") != -1);
1974         }
1975 
1976         boolean isSub() {
1977             return (svalue.indexOf("sub") != -1);
1978         }
1979 
1980         boolean isSup() {
1981             return (svalue.indexOf("sup") != -1);
1982         }
1983     }
1984 
1985     /**
1986      * Represents a value for the CSS.FONT_SIZE attribute.
1987      * The binary format of the value can be one of several
1988      * types.  If the type is Float,
1989      * the value is specified in terms of point or
1990      * percentage, depending upon the ending of the
1991      * associated string.
1992      * If the type is Integer, the value is specified
1993      * in terms of a size index.
1994      */
1995     @SuppressWarnings("serial") // Same-version serialization only
1996     class FontSize extends CssValue {
1997 
1998         /**
1999          * Returns the size in points.  This is ultimately
2000          * what we need for the purpose of creating/fetching
2001          * a Font object.
2002          *
2003          * @param a the attribute set the value is being
2004          *  requested from.  We may need to walk up the
2005          *  resolve hierarchy if it's relative.
2006          */
2007         int getValue(AttributeSet a, StyleSheet ss) {
2008             ss = getStyleSheet(ss);
2009             if (index) {
2010                 // it's an index, translate from size table
2011                 return Math.round(getPointSize((int) value, ss));
2012             }
2013             else if (lu == null) {
2014                 return Math.round(value);
2015             }
2016             else {
2017                 if (lu.type == 0) {
2018                     boolean isW3CLengthUnits = (ss == null) ? false : ss.isW3CLengthUnits();
2019                     return Math.round(lu.getValue(isW3CLengthUnits));
2020                 }
2021                 if (a != null) {
2022                     AttributeSet resolveParent = a.getResolveParent();
2023 
2024                     if (resolveParent != null) {
2025                         int pValue = StyleConstants.getFontSize(resolveParent);
2026 
2027                         float retValue;
2028                         if (lu.type == 1 || lu.type == 3) {
2029                             retValue = lu.value * (float)pValue;
2030                         }
2031                         else {
2032                             retValue = lu.value + (float)pValue;
2033                         }
2034                         return Math.round(retValue);
2035                     }
2036                 }
2037                 // a is null, or no resolve parent.
2038                 return 12;
2039             }
2040         }
2041 
2042         Object parseCssValue(String value) {
2043             FontSize fs = new FontSize();
2044             fs.svalue = value;
2045             try {
2046                 if (value.equals("xx-small")) {
2047                     fs.value = 1;
2048                     fs.index = true;
2049                 } else if (value.equals("x-small")) {
2050                     fs.value = 2;
2051                     fs.index = true;
2052                 } else if (value.equals("small")) {
2053                     fs.value = 3;
2054                     fs.index = true;
2055                 } else if (value.equals("medium")) {
2056                     fs.value = 4;
2057                     fs.index = true;
2058                 } else if (value.equals("large")) {
2059                     fs.value = 5;
2060                     fs.index = true;
2061                 } else if (value.equals("x-large")) {
2062                     fs.value = 6;
2063                     fs.index = true;
2064                 } else if (value.equals("xx-large")) {
2065                     fs.value = 7;
2066                     fs.index = true;
2067                 } else {
2068                     fs.lu = new LengthUnit(value, (short)1, 1f);
2069                 }
2070                 // relative sizes, larger | smaller (adjust from parent by
2071                 // 1.5 pixels)
2072                 // em, ex refer to parent sizes
2073                 // lengths: pt, mm, cm, pc, in, px
2074                 //          em (font height 3em would be 3 times font height)
2075                 //          ex (height of X)
2076                 // lengths are (+/-) followed by a number and two letter
2077                 // unit identifier
2078             } catch (NumberFormatException nfe) {
2079                 fs = null;
2080             }
2081             return fs;
2082         }
2083 
2084         Object parseHtmlValue(String value) {
2085             if ((value == null) || (value.length() == 0)) {
2086                 return null;
2087             }
2088             FontSize fs = new FontSize();
2089             fs.svalue = value;
2090 
2091             try {
2092                 /*
2093                  * relative sizes in the size attribute are relative
2094                  * to the <basefont>'s size.
2095                  */
2096                 int baseFontSize = getBaseFontSize();
2097                 if (value.charAt(0) == '+') {
2098                     int relSize = Integer.valueOf(value.substring(1)).intValue();
2099                     fs.value = baseFontSize + relSize;
2100                     fs.index = true;
2101                 } else if (value.charAt(0) == '-') {
2102                     int relSize = -Integer.valueOf(value.substring(1)).intValue();
2103                     fs.value = baseFontSize + relSize;
2104                     fs.index = true;
2105                 } else {
2106                     fs.value = Integer.parseInt(value);
2107                     if (fs.value > 7) {
2108                         fs.value = 7;
2109                     } else if (fs.value < 0) {
2110                         fs.value = 0;
2111                     }
2112                     fs.index = true;
2113                 }
2114 
2115             } catch (NumberFormatException nfe) {
2116                 fs = null;
2117             }
2118             return fs;
2119         }
2120 
2121         /**
2122          * Converts a {@code StyleConstants} attribute value to
2123          * a CSS attribute value.  If there is no conversion
2124          * returns {@code null}.  By default, there is no conversion.
2125          *
2126          * @param key the {@code StyleConstants} attribute
2127          * @param value the value of a {@code StyleConstants}
2128          *   attribute to be converted
2129          * @return the CSS value that represents the
2130          *   {@code StyleConstants} value
2131          */
2132         Object fromStyleConstants(StyleConstants key, Object value) {
2133             if (value instanceof Number) {
2134                 FontSize fs = new FontSize();
2135 
2136                 fs.value = getIndexOfSize(((Number)value).floatValue(), StyleSheet.sizeMapDefault);
2137                 fs.svalue = Integer.toString((int)fs.value);
2138                 fs.index = true;
2139                 return fs;
2140             }
2141             return parseCssValue(value.toString());
2142         }
2143 
2144         /**
2145          * Converts a CSS attribute value to a {@code StyleConstants}
2146          * value.  If there is no conversion, returns {@code null}.
2147          * By default, there is no conversion.
2148          *
2149          * @param key the {@code StyleConstants} attribute
2150          * @return the {@code StyleConstants} attribute value that
2151          *   represents the CSS attribute value
2152          */
2153         Object toStyleConstants(StyleConstants key, View v) {
2154             if (v != null) {
2155                 return Integer.valueOf(getValue(v.getAttributes(), null));
2156             }
2157             return Integer.valueOf(getValue(null, null));
2158         }
2159 
2160         float value;
2161         boolean index;
2162         LengthUnit lu;
2163     }
2164 
2165     @SuppressWarnings("serial") // Same-version serialization only
2166     static class FontFamily extends CssValue {
2167 
2168         /**
2169          * Returns the font family to use.
2170          */
2171         String getValue() {
2172             return family;
2173         }
2174 
2175         Object parseCssValue(String value) {
2176             int cIndex = value.indexOf(',');
2177             FontFamily ff = new FontFamily();
2178             ff.svalue = value;
2179             ff.family = null;
2180 
2181             if (cIndex == -1) {
2182                 setFontName(ff, value);
2183             }
2184             else {
2185                 boolean done = false;
2186                 int lastIndex;
2187                 int length = value.length();
2188                 cIndex = 0;
2189                 while (!done) {
2190                     // skip ws.
2191                     while (cIndex < length &&
2192                            Character.isWhitespace(value.charAt(cIndex)))
2193                         cIndex++;
2194                     // Find next ','
2195                     lastIndex = cIndex;
2196                     cIndex = value.indexOf(',', cIndex);
2197                     if (cIndex == -1) {
2198                         cIndex = length;
2199                     }
2200                     if (lastIndex < length) {
2201                         if (lastIndex != cIndex) {
2202                             int lastCharIndex = cIndex;
2203                             if (cIndex > 0 && value.charAt(cIndex - 1) == ' '){
2204                                 lastCharIndex--;
2205                             }
2206                             setFontName(ff, value.substring
2207                                         (lastIndex, lastCharIndex));
2208                             done = (ff.family != null);
2209                         }
2210                         cIndex++;
2211                     }
2212                     else {
2213                         done = true;
2214                     }
2215                 }
2216             }
2217             if (ff.family == null) {
2218                 ff.family = Font.SANS_SERIF;
2219             }
2220             return ff;
2221         }
2222 
2223         private void setFontName(FontFamily ff, String fontName) {
2224             ff.family = fontName;
2225         }
2226 
2227         Object parseHtmlValue(String value) {
2228             // TBD
2229             return parseCssValue(value);
2230         }
2231 
2232         /**
2233          * Converts a {@code StyleConstants} attribute value to
2234          * a CSS attribute value.  If there is no conversion
2235          * returns {@code null}.  By default, there is no conversion.
2236          *
2237          * @param key the {@code StyleConstants} attribute
2238          * @param value the value of a {@code StyleConstants}
2239          *   attribute to be converted
2240          * @return the CSS value that represents the
2241          *   {@code StyleConstants} value
2242          */
2243         Object fromStyleConstants(StyleConstants key, Object value) {
2244             return parseCssValue(value.toString());
2245         }
2246 
2247         /**
2248          * Converts a CSS attribute value to a {@code StyleConstants}
2249          * value.  If there is no conversion, returns {@code null}.
2250          * By default, there is no conversion.
2251          *
2252          * @param key the {@code StyleConstants} attribute
2253          * @return the {@code StyleConstants} attribute value that
2254          *   represents the CSS attribute value
2255          */
2256         Object toStyleConstants(StyleConstants key, View v) {
2257             return family;
2258         }
2259 
2260         String family;
2261     }
2262 
2263     @SuppressWarnings("serial") // Same-version serialization only
2264     static class FontWeight extends CssValue {
2265 
2266         int getValue() {
2267             return weight;
2268         }
2269 
2270         Object parseCssValue(String value) {
2271             FontWeight fw = new FontWeight();
2272             fw.svalue = value;
2273             if (value.equals("bold")) {
2274                 fw.weight = 700;
2275             } else if (value.equals("normal")) {
2276                 fw.weight = 400;
2277             } else {
2278                 // PENDING(prinz) add support for relative values
2279                 try {
2280                     fw.weight = Integer.parseInt(value);
2281                 } catch (NumberFormatException nfe) {
2282                     fw = null;
2283                 }
2284             }
2285             return fw;
2286         }
2287 
2288         /**
2289          * Converts a {@code StyleConstants} attribute value to
2290          * a CSS attribute value.  If there is no conversion
2291          * returns {@code null}.  By default, there is no conversion.
2292          *
2293          * @param key the {@code StyleConstants} attribute
2294          * @param value the value of a {@code StyleConstants}
2295          *   attribute to be converted
2296          * @return the CSS value that represents the
2297          *   {@code StyleConstants} value
2298          */
2299         Object fromStyleConstants(StyleConstants key, Object value) {
2300             if (value.equals(Boolean.TRUE)) {
2301                 return parseCssValue("bold");
2302             }
2303             return parseCssValue("normal");
2304         }
2305 
2306         /**
2307          * Converts a CSS attribute value to a {@code StyleConstants}
2308          * value.  If there is no conversion, returns {@code null}.
2309          * By default, there is no conversion.
2310          *
2311          * @param key the {@code StyleConstants} attribute
2312          * @return the {@code StyleConstants} attribute value that
2313          *   represents the CSS attribute value
2314          */
2315         Object toStyleConstants(StyleConstants key, View v) {
2316             return (weight > 500) ? Boolean.TRUE : Boolean.FALSE;
2317         }
2318 
2319         boolean isBold() {
2320             return (weight > 500);
2321         }
2322 
2323         int weight;
2324     }
2325 
2326     @SuppressWarnings("serial") // Same-version serialization only
2327     static class ColorValue extends CssValue {
2328 
2329         /**
2330          * Returns the color to use.
2331          */
2332         Color getValue() {
2333             return c;
2334         }
2335 
2336         Object parseCssValue(String value) {
2337 
2338             Color c = stringToColor(value);
2339             if (c != null) {
2340                 ColorValue cv = new ColorValue();
2341                 cv.svalue = value;
2342                 cv.c = c;
2343                 return cv;
2344             }
2345             return null;
2346         }
2347 
2348         Object parseHtmlValue(String value) {
2349             return parseCssValue(value);
2350         }
2351 
2352         /**
2353          * Converts a {@code StyleConstants} attribute value to
2354          * a CSS attribute value.  If there is no conversion
2355          * returns {@code null}.  By default, there is no conversion.
2356          *
2357          * @param key the {@code StyleConstants} attribute
2358          * @param value the value of a {@code StyleConstants}
2359          *   attribute to be converted
2360          * @return the CSS value that represents the
2361          *   {@code StyleConstants} value
2362          */
2363         Object fromStyleConstants(StyleConstants key, Object value) {
2364             ColorValue colorValue = new ColorValue();
2365             colorValue.c = (Color)value;
2366             colorValue.svalue = colorToHex(colorValue.c);
2367             return colorValue;
2368         }
2369 
2370         /**
2371          * Converts a CSS attribute value to a {@code StyleConstants}
2372          * value.  If there is no conversion, returns {@code null}.
2373          * By default, there is no conversion.
2374          *
2375          * @param key the {@code StyleConstants} attribute
2376          * @return the {@code StyleConstants} attribute value that
2377          *   represents the CSS attribute value
2378          */
2379         Object toStyleConstants(StyleConstants key, View v) {
2380             return c;
2381         }
2382 
2383         Color c;
2384     }
2385 
2386     @SuppressWarnings("serial") // Same-version serialization only
2387     static class BorderStyle extends CssValue {
2388 
2389         CSS.Value getValue() {
2390             return style;
2391         }
2392 
2393         Object parseCssValue(String value) {
2394             CSS.Value cssv = CSS.getValue(value);
2395             if (cssv != null) {
2396                 if ((cssv == CSS.Value.INSET) ||
2397                     (cssv == CSS.Value.OUTSET) ||
2398                     (cssv == CSS.Value.NONE) ||
2399                     (cssv == CSS.Value.DOTTED) ||
2400                     (cssv == CSS.Value.DASHED) ||
2401                     (cssv == CSS.Value.SOLID) ||
2402                     (cssv == CSS.Value.DOUBLE) ||
2403                     (cssv == CSS.Value.GROOVE) ||
2404                     (cssv == CSS.Value.RIDGE)) {
2405 
2406                     BorderStyle bs = new BorderStyle();
2407                     bs.svalue = value;
2408                     bs.style = cssv;
2409                     return bs;
2410                 }
2411             }
2412             return null;
2413         }
2414 
2415         private void writeObject(java.io.ObjectOutputStream s)
2416                      throws IOException {
2417             s.defaultWriteObject();
2418             if (style == null) {
2419                 s.writeObject(null);
2420             }
2421             else {
2422                 s.writeObject(style.toString());
2423             }
2424         }
2425 
2426         private void readObject(ObjectInputStream s)
2427                 throws ClassNotFoundException, IOException {
2428             s.defaultReadObject();
2429             Object value = s.readObject();
2430             if (value != null) {
2431                 style = CSS.getValue((String)value);
2432             }
2433         }
2434 
2435         // CSS.Values are static, don't archive it.
2436         private transient CSS.Value style;
2437     }
2438 
2439     @SuppressWarnings("serial") // Same-version serialization only
2440     static class LengthValue extends CssValue {
2441 
2442         /**
2443          * if this length value may be negative.
2444          */
2445         boolean mayBeNegative;
2446 
2447         LengthValue() {
2448             this(false);
2449         }
2450 
2451         LengthValue(boolean mayBeNegative) {
2452             this.mayBeNegative = mayBeNegative;
2453         }
2454 
2455         /**
2456          * Returns the length (span) to use.
2457          */
2458         float getValue() {
2459             return getValue(false);
2460         }
2461 
2462         float getValue(boolean isW3CLengthUnits) {
2463             return getValue(0, isW3CLengthUnits);
2464         }
2465 
2466         /**
2467          * Returns the length (span) to use. If the value represents
2468          * a percentage, it is scaled based on {@code currentValue}.
2469          */
2470         float getValue(float currentValue) {
2471             return getValue(currentValue, false);
2472         }
2473         float getValue(float currentValue, boolean isW3CLengthUnits) {
2474             if (percentage) {
2475                 return span * currentValue;
2476             }
2477             return LengthUnit.getValue(span, units, isW3CLengthUnits);
2478         }
2479 
2480         /**
2481          * Returns true if the length represents a percentage of the
2482          * containing box.
2483          */
2484         boolean isPercentage() {
2485             return percentage;
2486         }
2487 
2488         Object parseCssValue(String value) {
2489             LengthValue lv;
2490             try {
2491                 // Assume pixels
2492                 float absolute = Float.valueOf(value).floatValue();
2493                 lv = new LengthValue();
2494                 lv.span = absolute;
2495             } catch (NumberFormatException nfe) {
2496                 // Not pixels, use LengthUnit
2497                 LengthUnit lu = new LengthUnit(value,
2498                                                LengthUnit.UNINITALIZED_LENGTH,
2499                                                0);
2500 
2501                 // PENDING: currently, we only support absolute values and
2502                 // percentages.
2503                 switch (lu.type) {
2504                 case 0:
2505                     // Absolute
2506                     lv = new LengthValue();
2507                     lv.span =
2508                         (mayBeNegative) ? lu.value : Math.max(0, lu.value);
2509                     lv.units = lu.units;
2510                     break;
2511                 case 1:
2512                     // %
2513                     lv = new LengthValue();
2514                     lv.span = Math.max(0, Math.min(1, lu.value));
2515                     lv.percentage = true;
2516                     break;
2517                 default:
2518                     return null;
2519                 }
2520             }
2521             lv.svalue = value;
2522             return lv;
2523         }
2524 
2525         Object parseHtmlValue(String value) {
2526             if (value.equals(HTML.NULL_ATTRIBUTE_VALUE)) {
2527                 value = "1";
2528             }
2529             return parseCssValue(value);
2530         }
2531         /**
2532          * Converts a {@code StyleConstants} attribute value to
2533          * a CSS attribute value.  If there is no conversion,
2534          * returns {@code null}.  By default, there is no conversion.
2535          *
2536          * @param key the {@code StyleConstants} attribute
2537          * @param value the value of a {@code StyleConstants}
2538          *   attribute to be converted
2539          * @return the CSS value that represents the
2540          *   {@code StyleConstants} value
2541          */
2542         Object fromStyleConstants(StyleConstants key, Object value) {
2543             LengthValue v = new LengthValue();
2544             v.svalue = value.toString();
2545             v.span = ((Float)value).floatValue();
2546             return v;
2547         }
2548 
2549         /**
2550          * Converts a CSS attribute value to a {@code StyleConstants}
2551          * value.  If there is no conversion, returns {@code null}.
2552          * By default, there is no conversion.
2553          *
2554          * @param key the {@code StyleConstants} attribute
2555          * @return the {@code StyleConstants} attribute value that
2556          *   represents the CSS attribute value
2557          */
2558         Object toStyleConstants(StyleConstants key, View v) {
2559             return new Float(getValue(false));
2560         }
2561 
2562         /** If true, span is a percentage value, and that to determine
2563          * the length another value needs to be passed in. */
2564         boolean percentage;
2565         /** Either the absolute value (percentage == false) or
2566          * a percentage value. */
2567         float span;
2568 
2569         String units = null;
2570     }
2571 
2572 
2573     /**
2574      * BorderWidthValue is used to model BORDER_XXX_WIDTH and adds support
2575      * for the thin/medium/thick values.
2576      */
2577     @SuppressWarnings("serial") // Same-version serialization only
2578     static class BorderWidthValue extends LengthValue {
2579         BorderWidthValue(String svalue, int index) {
2580             this.svalue = svalue;
2581             span = values[index];
2582             percentage = false;
2583         }
2584 
2585         Object parseCssValue(String value) {
2586             if (value != null) {
2587                 if (value.equals("thick")) {
2588                     return new BorderWidthValue(value, 2);
2589                 }
2590                 else if (value.equals("medium")) {
2591                     return new BorderWidthValue(value, 1);
2592                 }
2593                 else if (value.equals("thin")) {
2594                     return new BorderWidthValue(value, 0);
2595                 }
2596             }
2597             // Assume its a length.
2598             return super.parseCssValue(value);
2599         }
2600 
2601         Object parseHtmlValue(String value) {
2602             if (value == HTML.NULL_ATTRIBUTE_VALUE) {
2603                 return parseCssValue("medium");
2604             }
2605             return parseCssValue(value);
2606         }
2607 
2608         /** Values used to represent border width. */
2609         private static final float[] values = { 1, 2, 4 };
2610    }
2611 
2612 
2613     /**
2614      * Handles uniquing of CSS values, like lists, and background image
2615      * repeating.
2616      */
2617     @SuppressWarnings("serial") // Same-version serialization only
2618     static class CssValueMapper extends CssValue {
2619         Object parseCssValue(String value) {
2620             Object retValue = cssValueToInternalValueMap.get(value);
2621             if (retValue == null) {
2622                 retValue = cssValueToInternalValueMap.get(value.toLowerCase());
2623             }
2624             return retValue;
2625         }
2626 
2627 
2628         Object parseHtmlValue(String value) {
2629             Object retValue = htmlValueToCssValueMap.get(value);
2630             if (retValue == null) {
2631                 retValue = htmlValueToCssValueMap.get(value.toLowerCase());
2632             }
2633             return retValue;
2634         }
2635     }
2636 
2637 
2638     /**
2639      * Used for background images, to represent the position.
2640      */
2641     @SuppressWarnings("serial") // Same-version serialization only
2642     static class BackgroundPosition extends CssValue {
2643         float horizontalPosition;
2644         float verticalPosition;
2645         // bitmask: bit 0, horizontal relative, bit 1 horizontal relative to
2646         // font size, 2 vertical relative to size, 3 vertical relative to
2647         // font size.
2648         //
2649         short relative;
2650 
2651         Object parseCssValue(String value) {
2652             // 'top left' and 'left top' both mean the same as '0% 0%'.
2653             // 'top', 'top center' and 'center top' mean the same as '50% 0%'.
2654             // 'right top' and 'top right' mean the same as '100% 0%'.
2655             // 'left', 'left center' and 'center left' mean the same as
2656             //        '0% 50%'.
2657             // 'center' and 'center center' mean the same as '50% 50%'.
2658             // 'right', 'right center' and 'center right' mean the same as
2659             //        '100% 50%'.
2660             // 'bottom left' and 'left bottom' mean the same as '0% 100%'.
2661             // 'bottom', 'bottom center' and 'center bottom' mean the same as
2662             //        '50% 100%'.
2663             // 'bottom right' and 'right bottom' mean the same as '100% 100%'.
2664             String[]  strings = CSS.parseStrings(value);
2665             int count = strings.length;
2666             BackgroundPosition bp = new BackgroundPosition();
2667             bp.relative = 5;
2668             bp.svalue = value;
2669 
2670             if (count > 0) {
2671                 // bit 0 for vert, 1 hor, 2 for center
2672                 short found = 0;
2673                 int index = 0;
2674                 while (index < count) {
2675                     // First, check for keywords
2676                     String string = strings[index++];
2677                     if (string.equals("center")) {
2678                         found |= 4;
2679                         continue;
2680                     }
2681                     else {
2682                         if ((found & 1) == 0) {
2683                             if (string.equals("top")) {
2684                                 found |= 1;
2685                             }
2686                             else if (string.equals("bottom")) {
2687                                 found |= 1;
2688                                 bp.verticalPosition = 1;
2689                                 continue;
2690                             }
2691                         }
2692                         if ((found & 2) == 0) {
2693                             if (string.equals("left")) {
2694                                 found |= 2;
2695                                 bp.horizontalPosition = 0;
2696                             }
2697                             else if (string.equals("right")) {
2698                                 found |= 2;
2699                                 bp.horizontalPosition = 1;
2700                             }
2701                         }
2702                     }
2703                 }
2704                 if (found != 0) {
2705                     if ((found & 1) == 1) {
2706                         if ((found & 2) == 0) {
2707                             // vert and no horiz.
2708                             bp.horizontalPosition = .5f;
2709                         }
2710                     }
2711                     else if ((found & 2) == 2) {
2712                         // horiz and no vert.
2713                         bp.verticalPosition = .5f;
2714                     }
2715                     else {
2716                         // no horiz, no vert, but center
2717                         bp.horizontalPosition = bp.verticalPosition = .5f;
2718                     }
2719                 }
2720                 else {
2721                     // Assume lengths
2722                     LengthUnit lu = new LengthUnit(strings[0], (short)0, 0f);
2723 
2724                     if (lu.type == 0) {
2725                         bp.horizontalPosition = lu.value;
2726                         bp.relative = (short)(1 ^ bp.relative);
2727                     }
2728                     else if (lu.type == 1) {
2729                         bp.horizontalPosition = lu.value;
2730                     }
2731                     else if (lu.type == 3) {
2732                         bp.horizontalPosition = lu.value;
2733                         bp.relative = (short)((1 ^ bp.relative) | 2);
2734                     }
2735                     if (count > 1) {
2736                         lu = new LengthUnit(strings[1], (short)0, 0f);
2737 
2738                         if (lu.type == 0) {
2739                             bp.verticalPosition = lu.value;
2740                             bp.relative = (short)(4 ^ bp.relative);
2741                         }
2742                         else if (lu.type == 1) {
2743                             bp.verticalPosition = lu.value;
2744                         }
2745                         else if (lu.type == 3) {
2746                             bp.verticalPosition = lu.value;
2747                             bp.relative = (short)((4 ^ bp.relative) | 8);
2748                         }
2749                     }
2750                     else {
2751                         bp.verticalPosition = .5f;
2752                     }
2753                 }
2754             }
2755             return bp;
2756         }
2757 
2758         boolean isHorizontalPositionRelativeToSize() {
2759             return ((relative & 1) == 1);
2760         }
2761 
2762         boolean isHorizontalPositionRelativeToFontSize() {
2763             return ((relative & 2) == 2);
2764         }
2765 
2766         float getHorizontalPosition() {
2767             return horizontalPosition;
2768         }
2769 
2770         boolean isVerticalPositionRelativeToSize() {
2771             return ((relative & 4) == 4);
2772         }
2773 
2774         boolean isVerticalPositionRelativeToFontSize() {
2775             return ((relative & 8) == 8);
2776         }
2777 
2778         float getVerticalPosition() {
2779             return verticalPosition;
2780         }
2781     }
2782 
2783 
2784     /**
2785      * Used for BackgroundImages.
2786      */
2787     @SuppressWarnings("serial") // Same-version serialization only
2788     static class BackgroundImage extends CssValue {
2789         private boolean    loadedImage;
2790         private ImageIcon  image;
2791 
2792         Object parseCssValue(String value) {
2793             BackgroundImage retValue = new BackgroundImage();
2794             retValue.svalue = value;
2795             return retValue;
2796         }
2797 
2798         Object parseHtmlValue(String value) {
2799             return parseCssValue(value);
2800         }
2801 
2802         // PENDING: this base is wrong for linked style sheets.
2803         ImageIcon getImage(URL base) {
2804             if (!loadedImage) {
2805                 synchronized(this) {
2806                     if (!loadedImage) {
2807                         URL url = CSS.getURL(base, svalue);
2808                         loadedImage = true;
2809                         if (url != null) {
2810                             image = new ImageIcon();
2811                             Image tmpImg = Toolkit.getDefaultToolkit().createImage(url);
2812                             if (tmpImg != null) {
2813                                 image.setImage(tmpImg);
2814                             }
2815                         }
2816                     }
2817                 }
2818             }
2819             return image;
2820         }
2821     }
2822 
2823     /**
2824      * Parses a length value, this is used internally, and never added
2825      * to an AttributeSet or returned to the developer.
2826      */
2827     @SuppressWarnings("serial") // Same-version serialization only
2828     static class LengthUnit implements Serializable {
2829         static Hashtable<String, Float> lengthMapping = new Hashtable<String, Float>(6);
2830         static Hashtable<String, Float> w3cLengthMapping = new Hashtable<String, Float>(6);
2831         static {
2832             lengthMapping.put("pt", new Float(1f));
2833             // Not sure about 1.3, determined by experiementation.
2834             lengthMapping.put("px", new Float(1.3f));
2835             lengthMapping.put("mm", new Float(2.83464f));
2836             lengthMapping.put("cm", new Float(28.3464f));
2837             lengthMapping.put("pc", new Float(12f));
2838             lengthMapping.put("in", new Float(72f));
2839             int res = 72;
2840             try {
2841                 res = Toolkit.getDefaultToolkit().getScreenResolution();
2842             } catch (HeadlessException e) {
2843             }
2844             // mapping according to the CSS2 spec
2845             w3cLengthMapping.put("pt", new Float(res/72f));
2846             w3cLengthMapping.put("px", new Float(1f));
2847             w3cLengthMapping.put("mm", new Float(res/25.4f));
2848             w3cLengthMapping.put("cm", new Float(res/2.54f));
2849             w3cLengthMapping.put("pc", new Float(res/6f));
2850             w3cLengthMapping.put("in", new Float(res));
2851         }
2852 
2853         LengthUnit(String value, short defaultType, float defaultValue) {
2854             parse(value, defaultType, defaultValue);
2855         }
2856 
2857         void parse(String value, short defaultType, float defaultValue) {
2858             type = defaultType;
2859             this.value = defaultValue;
2860 
2861             int length = value.length();
2862             if (length > 0 && value.charAt(length - 1) == '%') {
2863                 try {
2864                     this.value = Float.valueOf(value.substring(0, length - 1)).
2865                                                floatValue() / 100.0f;
2866                     type = 1;
2867                 }
2868                 catch (NumberFormatException nfe) { }
2869             }
2870             if (length >= 2) {
2871                 units = value.substring(length - 2, length);
2872                 Float scale = lengthMapping.get(units);
2873                 if (scale != null) {
2874                     try {
2875                         this.value = Float.valueOf(value.substring(0,
2876                                length - 2)).floatValue();
2877                         type = 0;
2878                     }
2879                     catch (NumberFormatException nfe) { }
2880                 }
2881                 else if (units.equals("em") ||
2882                          units.equals("ex")) {
2883                     try {
2884                         this.value = Float.valueOf(value.substring(0,
2885                                       length - 2)).floatValue();
2886                         type = 3;
2887                     }
2888                     catch (NumberFormatException nfe) { }
2889                 }
2890                 else if (value.equals("larger")) {
2891                     this.value = 2f;
2892                     type = 2;
2893                 }
2894                 else if (value.equals("smaller")) {
2895                     this.value = -2;
2896                     type = 2;
2897                 }
2898                 else {
2899                     // treat like points.
2900                     try {
2901                         this.value = Float.valueOf(value).floatValue();
2902                         type = 0;
2903                     } catch (NumberFormatException nfe) {}
2904                 }
2905             }
2906             else if (length > 0) {
2907                 // treat like points.
2908                 try {
2909                     this.value = Float.valueOf(value).floatValue();
2910                     type = 0;
2911                 } catch (NumberFormatException nfe) {}
2912             }
2913         }
2914 
2915         float getValue(boolean w3cLengthUnits) {
2916             Hashtable<String, Float> mapping = (w3cLengthUnits) ? w3cLengthMapping : lengthMapping;
2917             float scale = 1;
2918             if (units != null) {
2919                 Float scaleFloat = mapping.get(units);
2920                 if (scaleFloat != null) {
2921                     scale = scaleFloat.floatValue();
2922                 }
2923             }
2924             return this.value * scale;
2925 
2926         }
2927 
2928         static float getValue(float value, String units, Boolean w3cLengthUnits) {
2929             Hashtable<String, Float> mapping = (w3cLengthUnits) ? w3cLengthMapping : lengthMapping;
2930             float scale = 1;
2931             if (units != null) {
2932                 Float scaleFloat = mapping.get(units);
2933                 if (scaleFloat != null) {
2934                     scale = scaleFloat.floatValue();
2935                 }
2936             }
2937             return value * scale;
2938         }
2939 
2940         public String toString() {
2941             return type + " " + value;
2942         }
2943 
2944         // 0 - value indicates real value
2945         // 1 - % value, value relative to depends upon key.
2946         //     50% will have a value = .5
2947         // 2 - add value to parent value.
2948         // 3 - em/ex relative to font size of element (except for
2949         //     font-size, which is relative to parent).
2950         short type;
2951         float value;
2952         String units = null;
2953 
2954 
2955         static final short UNINITALIZED_LENGTH = (short)10;
2956     }
2957 
2958 
2959     /**
2960      * Class used to parse font property. The font property is shorthand
2961      * for the other font properties. This expands the properties, placing
2962      * them in the attributeset.
2963      */
2964     static class ShorthandFontParser {
2965         /**
2966          * Parses the shorthand font string {@code value}, placing the
2967          * result in {@code attr}.
2968          */
2969         static void parseShorthandFont(CSS css, String value,
2970                                        MutableAttributeSet attr) {
2971             // font is of the form:
2972             // [ <font-style> || <font-variant> || <font-weight> ]? <font-size>
2973             //   [ / <line-height> ]? <font-family>
2974             String[]   strings = CSS.parseStrings(value);
2975             int        count = strings.length;
2976             int        index = 0;
2977             // bitmask, 1 for style, 2 for variant, 3 for weight
2978             short      found = 0;
2979             int        maxC = Math.min(3, count);
2980 
2981             // Check for font-style font-variant font-weight
2982             while (index < maxC) {
2983                 if ((found & 1) == 0 && isFontStyle(strings[index])) {
2984                     css.addInternalCSSValue(attr, CSS.Attribute.FONT_STYLE,
2985                                             strings[index++]);
2986                     found |= 1;
2987                 }
2988                 else if ((found & 2) == 0 && isFontVariant(strings[index])) {
2989                     css.addInternalCSSValue(attr, CSS.Attribute.FONT_VARIANT,
2990                                             strings[index++]);
2991                     found |= 2;
2992                 }
2993                 else if ((found & 4) == 0 && isFontWeight(strings[index])) {
2994                     css.addInternalCSSValue(attr, CSS.Attribute.FONT_WEIGHT,
2995                                             strings[index++]);
2996                     found |= 4;
2997                 }
2998                 else if (strings[index].equals("normal")) {
2999                     index++;
3000                 }
3001                 else {
3002                     break;
3003                 }
3004             }
3005             if ((found & 1) == 0) {
3006                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_STYLE,
3007                                         "normal");
3008             }
3009             if ((found & 2) == 0) {
3010                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_VARIANT,
3011                                         "normal");
3012             }
3013             if ((found & 4) == 0) {
3014                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_WEIGHT,
3015                                         "normal");
3016             }
3017 
3018             // string at index should be the font-size
3019             if (index < count) {
3020                 String fontSize = strings[index];
3021                 int slashIndex = fontSize.indexOf('/');
3022 
3023                 if (slashIndex != -1) {
3024                     fontSize = fontSize.substring(0, slashIndex);
3025                     strings[index] = strings[index].substring(slashIndex);
3026                 }
3027                 else {
3028                     index++;
3029                 }
3030                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_SIZE,
3031                                         fontSize);
3032             }
3033             else {
3034                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_SIZE,
3035                                         "medium");
3036             }
3037 
3038             // Check for line height
3039             if (index < count && strings[index].startsWith("/")) {
3040                 String lineHeight = null;
3041                 if (strings[index].equals("/")) {
3042                     if (++index < count) {
3043                         lineHeight = strings[index++];
3044                     }
3045                 }
3046                 else {
3047                     lineHeight = strings[index++].substring(1);
3048                 }
3049                 // line height
3050                 if (lineHeight != null) {
3051                     css.addInternalCSSValue(attr, CSS.Attribute.LINE_HEIGHT,
3052                                             lineHeight);
3053                 }
3054                 else {
3055                     css.addInternalCSSValue(attr, CSS.Attribute.LINE_HEIGHT,
3056                                             "normal");
3057                 }
3058             }
3059             else {
3060                 css.addInternalCSSValue(attr, CSS.Attribute.LINE_HEIGHT,
3061                                         "normal");
3062             }
3063 
3064             // remainder of strings are font-family
3065             if (index < count) {
3066                 String family = strings[index++];
3067 
3068                 while (index < count) {
3069                     family += " " + strings[index++];
3070                 }
3071                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_FAMILY,
3072                                         family);
3073             }
3074             else {
3075                 css.addInternalCSSValue(attr, CSS.Attribute.FONT_FAMILY,
3076                                         Font.SANS_SERIF);
3077             }
3078         }
3079 
3080         private static boolean isFontStyle(String string) {
3081             return (string.equals("italic") ||
3082                     string.equals("oblique"));
3083         }
3084 
3085         private static boolean isFontVariant(String string) {
3086             return (string.equals("small-caps"));
3087         }
3088 
3089         private static boolean isFontWeight(String string) {
3090             if (string.equals("bold") || string.equals("bolder") ||
3091                 string.equals("italic") || string.equals("lighter")) {
3092                 return true;
3093             }
3094             // test for 100-900
3095             return (string.length() == 3 &&
3096                     string.charAt(0) >= '1' && string.charAt(0) <= '9' &&
3097                     string.charAt(1) == '0' && string.charAt(2) == '0');
3098         }
3099 
3100     }
3101 
3102 
3103     /**
3104      * Parses the background property into its intrinsic values.
3105      */
3106     static class ShorthandBackgroundParser {
3107         /**
3108          * Parses the shorthand font string {@code value}, placing the
3109          * result in {@code attr}.
3110          */
3111         static void parseShorthandBackground(CSS css, String value,
3112                                              MutableAttributeSet attr) {
3113             String[] strings = parseStrings(value);
3114             int count = strings.length;
3115             int index = 0;
3116             // bitmask: 0 for image, 1 repeat, 2 attachment, 3 position,
3117             //          4 color
3118             short found = 0;
3119 
3120             while (index < count) {
3121                 String string = strings[index++];
3122                 if ((found & 1) == 0 && isImage(string)) {
3123                     css.addInternalCSSValue(attr, CSS.Attribute.
3124                                             BACKGROUND_IMAGE, string);
3125                     found |= 1;
3126                 }
3127                 else if ((found & 2) == 0 && isRepeat(string)) {
3128                     css.addInternalCSSValue(attr, CSS.Attribute.
3129                                             BACKGROUND_REPEAT, string);
3130                     found |= 2;
3131                 }
3132                 else if ((found & 4) == 0 && isAttachment(string)) {
3133                     css.addInternalCSSValue(attr, CSS.Attribute.
3134                                             BACKGROUND_ATTACHMENT, string);
3135                     found |= 4;
3136                 }
3137                 else if ((found & 8) == 0 && isPosition(string)) {
3138                     if (index < count && isPosition(strings[index])) {
3139                         css.addInternalCSSValue(attr, CSS.Attribute.
3140                                                 BACKGROUND_POSITION,
3141                                                 string + " " +
3142                                                 strings[index++]);
3143                     }
3144                     else {
3145                         css.addInternalCSSValue(attr, CSS.Attribute.
3146                                                 BACKGROUND_POSITION, string);
3147                     }
3148                     found |= 8;
3149                 }
3150                 else if ((found & 16) == 0 && isColor(string)) {
3151                     css.addInternalCSSValue(attr, CSS.Attribute.
3152                                             BACKGROUND_COLOR, string);
3153                     found |= 16;
3154                 }
3155             }
3156             if ((found & 1) == 0) {
3157                 css.addInternalCSSValue(attr, CSS.Attribute.BACKGROUND_IMAGE,
3158                                         null);
3159             }
3160             if ((found & 2) == 0) {
3161                 css.addInternalCSSValue(attr, CSS.Attribute.BACKGROUND_REPEAT,
3162                                         "repeat");
3163             }
3164             if ((found & 4) == 0) {
3165                 css.addInternalCSSValue(attr, CSS.Attribute.
3166                                         BACKGROUND_ATTACHMENT, "scroll");
3167             }
3168             if ((found & 8) == 0) {
3169                 css.addInternalCSSValue(attr, CSS.Attribute.
3170                                         BACKGROUND_POSITION, null);
3171             }
3172             // Currently, there is no good way to express this.
3173             /*
3174             if ((found & 16) == 0) {
3175                 css.addInternalCSSValue(attr, CSS.Attribute.BACKGROUND_COLOR,
3176                                         null);
3177             }
3178             */
3179         }
3180 
3181         static boolean isImage(String string) {
3182             return (string.startsWith("url(") && string.endsWith(")"));
3183         }
3184 
3185         static boolean isRepeat(String string) {
3186             return (string.equals("repeat-x") || string.equals("repeat-y") ||
3187                     string.equals("repeat") || string.equals("no-repeat"));
3188         }
3189 
3190         static boolean isAttachment(String string) {
3191             return (string.equals("fixed") || string.equals("scroll"));
3192         }
3193 
3194         static boolean isPosition(String string) {
3195             return (string.equals("top") || string.equals("bottom") ||
3196                     string.equals("left") || string.equals("right") ||
3197                     string.equals("center") ||
3198                     (string.length() > 0 &&
3199                      Character.isDigit(string.charAt(0))));
3200         }
3201 
3202         static boolean isColor(String string) {
3203             return (CSS.stringToColor(string) != null);
3204         }
3205     }
3206 
3207 
3208     /**
3209      * Used to parser margin and padding.
3210      */
3211     static class ShorthandMarginParser {
3212         /**
3213          * Parses the shorthand margin/padding/border string
3214          * {@code value}, placing the result in {@code attr}.
3215          * {@code names} give the 4 instrinsic property names.
3216          */
3217         static void parseShorthandMargin(CSS css, String value,
3218                                          MutableAttributeSet attr,
3219                                          CSS.Attribute[] names) {
3220             String[] strings = parseStrings(value);
3221             int count = strings.length;
3222             int index = 0;
3223             switch (count) {
3224             case 0:
3225                 // empty string
3226                 return;
3227             case 1:
3228                 // Identifies all values.
3229                 for (int counter = 0; counter < 4; counter++) {
3230                     css.addInternalCSSValue(attr, names[counter], strings[0]);
3231                 }
3232                 break;
3233             case 2:
3234                 // 0 & 2 = strings[0], 1 & 3 = strings[1]
3235                 css.addInternalCSSValue(attr, names[0], strings[0]);
3236                 css.addInternalCSSValue(attr, names[2], strings[0]);
3237                 css.addInternalCSSValue(attr, names[1], strings[1]);
3238                 css.addInternalCSSValue(attr, names[3], strings[1]);
3239                 break;
3240             case 3:
3241                 css.addInternalCSSValue(attr, names[0], strings[0]);
3242                 css.addInternalCSSValue(attr, names[1], strings[1]);
3243                 css.addInternalCSSValue(attr, names[2], strings[2]);
3244                 css.addInternalCSSValue(attr, names[3], strings[1]);
3245                 break;
3246             default:
3247                 for (int counter = 0; counter < 4; counter++) {
3248                     css.addInternalCSSValue(attr, names[counter],
3249                                             strings[counter]);
3250                 }
3251                 break;
3252             }
3253         }
3254     }
3255 
3256     static class ShorthandBorderParser {
3257         static Attribute[] keys = {
3258             Attribute.BORDER_TOP, Attribute.BORDER_RIGHT,
3259             Attribute.BORDER_BOTTOM, Attribute.BORDER_LEFT,
3260         };
3261 
3262         static void parseShorthandBorder(MutableAttributeSet attributes,
3263                                             CSS.Attribute key, String value) {
3264             Object[] parts = new Object[CSSBorder.PARSERS.length];
3265             String[] strings = parseStrings(value);
3266             for (String s : strings) {
3267                 boolean valid = false;
3268                 for (int i = 0; i < parts.length; i++) {
3269                     Object v = CSSBorder.PARSERS[i].parseCssValue(s);
3270                     if (v != null) {
3271                         if (parts[i] == null) {
3272                             parts[i] = v;
3273                             valid = true;
3274                         }
3275                         break;
3276                     }
3277                 }
3278                 if (!valid) {
3279                     // Part is non-parseable or occurred more than once.
3280                     return;
3281                 }
3282             }
3283 
3284             // Unspecified parts get default values.
3285             for (int i = 0; i < parts.length; i++) {
3286                 if (parts[i] == null) {
3287                     parts[i] = CSSBorder.DEFAULTS[i];
3288                 }
3289             }
3290 
3291             // Dispatch collected values to individual properties.
3292             for (int i = 0; i < keys.length; i++) {
3293                 if ((key == Attribute.BORDER) || (key == keys[i])) {
3294                     for (int k = 0; k < parts.length; k++) {
3295                         attributes.addAttribute(
3296                                         CSSBorder.ATTRIBUTES[k][i], parts[k]);
3297                     }
3298                 }
3299             }
3300         }
3301     }
3302 
3303     /**
3304      * Calculate the requirements needed to tile the requirements
3305      * given by the iterator that would be tiled.  The calculation
3306      * takes into consideration margin and border spacing.
3307      */
3308     static SizeRequirements calculateTiledRequirements(LayoutIterator iter, SizeRequirements r) {
3309         long minimum = 0;
3310         long maximum = 0;
3311         long preferred = 0;
3312         int lastMargin = 0;
3313         int totalSpacing = 0;
3314         int n = iter.getCount();
3315         for (int i = 0; i < n; i++) {
3316             iter.setIndex(i);
3317             int margin0 = lastMargin;
3318             int margin1 = (int) iter.getLeadingCollapseSpan();
3319             totalSpacing += Math.max(margin0, margin1);
3320             preferred += (int) iter.getPreferredSpan(0);
3321             minimum += iter.getMinimumSpan(0);
3322             maximum += iter.getMaximumSpan(0);
3323 
3324             lastMargin = (int) iter.getTrailingCollapseSpan();
3325         }
3326         totalSpacing += lastMargin;
3327         totalSpacing += 2 * iter.getBorderWidth();
3328 
3329         // adjust for the spacing area
3330         minimum += totalSpacing;
3331         preferred += totalSpacing;
3332         maximum += totalSpacing;
3333 
3334         // set return value
3335         if (r == null) {
3336             r = new SizeRequirements();
3337         }
3338         r.minimum = (minimum > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)minimum;
3339         r.preferred = (preferred > Integer.MAX_VALUE) ? Integer.MAX_VALUE :(int) preferred;
3340         r.maximum = (maximum > Integer.MAX_VALUE) ? Integer.MAX_VALUE :(int) maximum;
3341         return r;
3342     }
3343 
3344     /**
3345      * Calculate a tiled layout for the given iterator.
3346      * This should be done collapsing the neighboring
3347      * margins to be a total of the maximum of the two
3348      * neighboring margin areas as described in the CSS spec.
3349      */
3350     static void calculateTiledLayout(LayoutIterator iter, int targetSpan) {
3351 
3352         /*
3353          * first pass, calculate the preferred sizes, adjustments needed because
3354          * of margin collapsing, and the flexibility to adjust the sizes.
3355          */
3356         long preferred = 0;
3357         long currentPreferred;
3358         int lastMargin = 0;
3359         int totalSpacing = 0;
3360         int n = iter.getCount();
3361         int adjustmentWeightsCount = LayoutIterator.WorstAdjustmentWeight + 1;
3362         //max gain we can get adjusting elements with adjustmentWeight <= i
3363         long gain[] = new long[adjustmentWeightsCount];
3364         //max loss we can get adjusting elements with adjustmentWeight <= i
3365         long loss[] = new long[adjustmentWeightsCount];
3366 
3367         for (int i = 0; i < adjustmentWeightsCount; i++) {
3368             gain[i] = loss[i] = 0;
3369         }
3370         for (int i = 0; i < n; i++) {
3371             iter.setIndex(i);
3372             int margin0 = lastMargin;
3373             int margin1 = (int) iter.getLeadingCollapseSpan();
3374 
3375             iter.setOffset(Math.max(margin0, margin1));
3376             totalSpacing += iter.getOffset();
3377 
3378             currentPreferred = (long)iter.getPreferredSpan(targetSpan);
3379             iter.setSpan((int) currentPreferred);
3380             preferred += currentPreferred;
3381             gain[iter.getAdjustmentWeight()] +=
3382                 (long)iter.getMaximumSpan(targetSpan) - currentPreferred;
3383             loss[iter.getAdjustmentWeight()] +=
3384                 currentPreferred - (long)iter.getMinimumSpan(targetSpan);
3385             lastMargin = (int) iter.getTrailingCollapseSpan();
3386         }
3387         totalSpacing += lastMargin;
3388         totalSpacing += 2 * iter.getBorderWidth();
3389 
3390         for (int i = 1; i < adjustmentWeightsCount; i++) {
3391             gain[i] += gain[i - 1];
3392             loss[i] += loss[i - 1];
3393         }
3394 
3395         /*
3396          * Second pass, expand or contract by as much as possible to reach
3397          * the target span.  This takes the margin collapsing into account
3398          * prior to adjusting the span.
3399          */
3400 
3401         // determine the adjustment to be made
3402         int allocated = targetSpan - totalSpacing;
3403         long desiredAdjustment = allocated - preferred;
3404         long adjustmentsArray[] = (desiredAdjustment > 0) ? gain : loss;
3405         desiredAdjustment = Math.abs(desiredAdjustment);
3406         int adjustmentLevel = 0;
3407         for (;adjustmentLevel <= LayoutIterator.WorstAdjustmentWeight;
3408              adjustmentLevel++) {
3409             // adjustmentsArray[] is sorted. I do not bother about
3410             // binary search though
3411             if (adjustmentsArray[adjustmentLevel] >= desiredAdjustment) {
3412                 break;
3413             }
3414         }
3415         float adjustmentFactor = 0.0f;
3416         if (adjustmentLevel <= LayoutIterator.WorstAdjustmentWeight) {
3417             desiredAdjustment -= (adjustmentLevel > 0) ?
3418                 adjustmentsArray[adjustmentLevel - 1] : 0;
3419             if (desiredAdjustment != 0) {
3420                 float maximumAdjustment =
3421                     adjustmentsArray[adjustmentLevel] -
3422                     ((adjustmentLevel > 0) ?
3423                      adjustmentsArray[adjustmentLevel - 1] : 0
3424                      );
3425                 adjustmentFactor = desiredAdjustment / maximumAdjustment;
3426             }
3427         }
3428         // make the adjustments
3429         int totalOffset = (int)iter.getBorderWidth();
3430         for (int i = 0; i < n; i++) {
3431             iter.setIndex(i);
3432             iter.setOffset( iter.getOffset() + totalOffset);
3433             if (iter.getAdjustmentWeight() < adjustmentLevel) {
3434                 iter.setSpan((int)
3435                              ((allocated > preferred) ?
3436                               Math.floor(iter.getMaximumSpan(targetSpan)) :
3437                               Math.ceil(iter.getMinimumSpan(targetSpan))
3438                               )
3439                              );
3440             } else if (iter.getAdjustmentWeight() == adjustmentLevel) {
3441                 int availableSpan = (allocated > preferred) ?
3442                     (int) iter.getMaximumSpan(targetSpan) - iter.getSpan() :
3443                     iter.getSpan() - (int) iter.getMinimumSpan(targetSpan);
3444                 int adj = (int)Math.floor(adjustmentFactor * availableSpan);
3445                 iter.setSpan(iter.getSpan() +
3446                              ((allocated > preferred) ? adj : -adj));
3447             }
3448             totalOffset = (int) Math.min((long) iter.getOffset() +
3449                                          (long) iter.getSpan(),
3450                                          Integer.MAX_VALUE);
3451         }
3452 
3453         // while rounding we could lose several pixels.
3454         int roundError = targetSpan - totalOffset -
3455             (int)iter.getTrailingCollapseSpan() -
3456             (int)iter.getBorderWidth();
3457         int adj = (roundError > 0) ? 1 : -1;
3458         roundError *= adj;
3459 
3460         boolean canAdjust = true;
3461         while (roundError > 0 && canAdjust) {
3462             // check for infinite loop
3463             canAdjust = false;
3464             int offsetAdjust = 0;
3465             // try to distribute roundError. one pixel per cell
3466             for (int i = 0; i < n; i++) {
3467                 iter.setIndex(i);
3468                 iter.setOffset(iter.getOffset() + offsetAdjust);
3469                 int curSpan = iter.getSpan();
3470                 if (roundError > 0) {
3471                     int boundGap = (adj > 0) ?
3472                         (int)Math.floor(iter.getMaximumSpan(targetSpan)) - curSpan :
3473                         curSpan - (int)Math.ceil(iter.getMinimumSpan(targetSpan));
3474                     if (boundGap >= 1) {
3475                         canAdjust = true;
3476                         iter.setSpan(curSpan + adj);
3477                         offsetAdjust += adj;
3478                         roundError--;
3479                     }
3480                 }
3481             }
3482         }
3483     }
3484 
3485     /**
3486      * An iterator to express the requirements to use when computing
3487      * layout.
3488      */
3489     interface LayoutIterator {
3490 
3491         void setOffset(int offs);
3492 
3493         int getOffset();
3494 
3495         void setSpan(int span);
3496 
3497         int getSpan();
3498 
3499         int getCount();
3500 
3501         void setIndex(int i);
3502 
3503         float getMinimumSpan(float parentSpan);
3504 
3505         float getPreferredSpan(float parentSpan);
3506 
3507         float getMaximumSpan(float parentSpan);
3508 
3509         int getAdjustmentWeight(); //0 is the best weight WorstAdjustmentWeight is a worst one
3510 
3511         //float getAlignment();
3512 
3513         float getBorderWidth();
3514 
3515         float getLeadingCollapseSpan();
3516 
3517         float getTrailingCollapseSpan();
3518         public static final int WorstAdjustmentWeight = 2;
3519     }
3520 
3521     //
3522     // Serialization support
3523     //
3524 
3525     private void writeObject(java.io.ObjectOutputStream s)
3526         throws IOException
3527     {
3528         s.defaultWriteObject();
3529 
3530         // Determine what values in valueConvertor need to be written out.
3531         Enumeration<?> keys = valueConvertor.keys();
3532         s.writeInt(valueConvertor.size());
3533         if (keys != null) {
3534             while (keys.hasMoreElements()) {
3535                 Object key = keys.nextElement();
3536                 Object value = valueConvertor.get(key);
3537                 if (!(key instanceof Serializable) &&
3538                     (key = StyleContext.getStaticAttributeKey(key)) == null) {
3539                     // Should we throw an exception here?
3540                     key = null;
3541                     value = null;
3542                 }
3543                 else if (!(value instanceof Serializable) &&
3544                     (value = StyleContext.getStaticAttributeKey(value)) == null){
3545                     // Should we throw an exception here?
3546                     key = null;
3547                     value = null;
3548                 }
3549                 s.writeObject(key);
3550                 s.writeObject(value);
3551             }
3552         }
3553     }
3554 
3555     private void readObject(ObjectInputStream s)
3556       throws ClassNotFoundException, IOException
3557     {
3558         ObjectInputStream.GetField f = s.readFields();
3559         int newBaseFontSize = f.get("baseFontSize", 0);
3560         setBaseFontSize(newBaseFontSize);
3561 
3562         // Reconstruct the hashtable.
3563         int numValues = s.readInt();
3564         valueConvertor = new Hashtable<>(Math.max(1, numValues));
3565         while (numValues-- > 0) {
3566             Object key = s.readObject();
3567             Object value = s.readObject();
3568             Object staticKey = StyleContext.getStaticAttribute(key);
3569             if (staticKey != null) {
3570                 key = staticKey;
3571             }
3572             Object staticValue = StyleContext.getStaticAttribute(value);
3573             if (staticValue != null) {
3574                 value = staticValue;
3575             }
3576             if (key != null && value != null) {
3577                 valueConvertor.put(key, value);
3578             }
3579         }
3580     }
3581 
3582 
3583     /*
3584      * we need StyleSheet for resolving lenght units. (see
3585      * isW3CLengthUnits)
3586      * we can not pass stylesheet for handling relative sizes. (do not
3587      * think changing public API is necessary)
3588      * CSS is not likely to be accessed from more then one thread.
3589      * Having local storage for StyleSheet for resolving relative
3590      * sizes is safe
3591      *
3592      * idk 08/30/2004
3593      */
3594     private StyleSheet getStyleSheet(StyleSheet ss) {
3595         if (ss != null) {
3596             styleSheet = ss;
3597         }
3598         return styleSheet;
3599     }
3600     //
3601     // Instance variables
3602     //
3603 
3604     /** Maps from CSS key to CssValue. */
3605     private transient Hashtable<Object, Object> valueConvertor;
3606 
3607     /** Size used for relative units. */
3608     private int baseFontSize;
3609 
3610     private transient StyleSheet styleSheet = null;
3611 
3612     static int baseFontSizeIndex = 3;
3613 }