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