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