1 /*
2 * Copyright 1997-2005 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25 package javax.swing.text.html;
26
27 import sun.swing.SwingUtilities2;
28 import java.util;
29 import java.awt;
30 import java.io;
31 import java.net;
32 import javax.swing.Icon;
33 import javax.swing.ImageIcon;
34 import javax.swing.border;
35 import javax.swing.event.ChangeListener;
36 import javax.swing.text;
37
38 /**
39 * Support for defining the visual characteristics of
40 * HTML views being rendered. The StyleSheet is used to
41 * translate the HTML model into visual characteristics.
42 * This enables views to be customized by a look-and-feel,
43 * multiple views over the same model can be rendered
44 * differently, etc. This can be thought of as a CSS
45 * rule repository. The key for CSS attributes is an
46 * object of type CSS.Attribute. The type of the value
47 * is up to the StyleSheet implementation, but the
48 * <code>toString</code> method is required
49 * to return a string representation of CSS value.
50 * <p>
51 * The primary entry point for HTML View implementations
52 * to get their attributes is the
53 * <a href="#getViewAttributes">getViewAttributes</a>
54 * method. This should be implemented to establish the
55 * desired policy used to associate attributes with the view.
56 * Each HTMLEditorKit (i.e. and therefore each associated
57 * JEditorPane) can have its own StyleSheet, but by default one
58 * sheet will be shared by all of the HTMLEditorKit instances.
59 * HTMLDocument instance can also have a StyleSheet, which
60 * holds the document-specific CSS specifications.
61 * <p>
62 * In order for Views to store less state and therefore be
63 * more lightweight, the StyleSheet can act as a factory for
64 * painters that handle some of the rendering tasks. This allows
65 * implementations to determine what they want to cache
66 * and have the sharing potentially at the level that a
67 * selector is common to multiple views. Since the StyleSheet
68 * may be used by views over multiple documents and typically
69 * the HTML attributes don't effect the selector being used,
70 * the potential for sharing is significant.
71 * <p>
72 * The rules are stored as named styles, and other information
73 * is stored to translate the context of an element to a
74 * rule quickly. The following code fragment will display
75 * the named styles, and therefore the CSS rules contained.
76 * <code><pre>
77 *
78 * import java.util.*;
79 * import javax.swing.text.*;
80 * import javax.swing.text.html.*;
81 *
82 * public class ShowStyles {
83 *
84 * public static void main(String[] args) {
85 * HTMLEditorKit kit = new HTMLEditorKit();
86 * HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
87 * StyleSheet styles = doc.getStyleSheet();
88 *
89 * Enumeration rules = styles.getStyleNames();
90 * while (rules.hasMoreElements()) {
91 * String name = (String) rules.nextElement();
92 * Style rule = styles.getStyle(name);
93 * System.out.println(rule.toString());
94 * }
95 * System.exit(0);
96 * }
97 * }
98 *
99 * </pre></code>
100 * <p>
101 * The semantics for when a CSS style should overide visual attributes
102 * defined by an element are not well defined. For example, the html
103 * <code><body bgcolor=red></code> makes the body have a red
104 * background. But if the html file also contains the CSS rule
105 * <code>body { background: blue }</code> it becomes less clear as to
106 * what color the background of the body should be. The current
107 * implemention gives visual attributes defined in the element the
108 * highest precedence, that is they are always checked before any styles.
109 * Therefore, in the previous example the background would have a
110 * red color as the body element defines the background color to be red.
111 * <p>
112 * As already mentioned this supports CSS. We don't support the full CSS
113 * spec. Refer to the javadoc of the CSS class to see what properties
114 * we support. The two major CSS parsing related
115 * concepts we do not currently
116 * support are pseudo selectors, such as <code>A:link { color: red }</code>,
117 * and the <code>important</code> modifier.
118 * <p>
119 * <font color="red">Note: This implementation is currently
120 * incomplete. It can be replaced with alternative implementations
121 * that are complete. Future versions of this class will provide
122 * better CSS support.</font>
123 *
124 * @author Timothy Prinzing
125 * @author Sunita Mani
126 * @author Sara Swanson
127 * @author Jill Nakata
128 */
129 public class StyleSheet extends StyleContext {
130 // As the javadoc states, this class maintains a mapping between
131 // a CSS selector (such as p.bar) and a Style.
132 // This consists of a number of parts:
133 // . Each selector is broken down into its constituent simple selectors,
134 // and stored in an inverted graph, for example:
135 // p { color: red } ol p { font-size: 10pt } ul p { font-size: 12pt }
136 // results in the graph:
137 // root
138 // |
139 // p
140 // / \
141 // ol ul
142 // each node (an instance of SelectorMapping) has an associated
143 // specificity and potentially a Style.
144 // . Every rule that is asked for (either by way of getRule(String) or
145 // getRule(HTML.Tag, Element)) results in a unique instance of
146 // ResolvedStyle. ResolvedStyles contain the AttributeSets from the
147 // SelectorMapping.
148 // . When a new rule is created it is inserted into the graph, and
149 // the AttributeSets of each ResolvedStyles are updated appropriately.
150 // . This class creates special AttributeSets, LargeConversionSet and
151 // SmallConversionSet, that maintain a mapping between StyleConstants
152 // and CSS so that developers that wish to use the StyleConstants
153 // methods can do so.
154 // . When one of the AttributeSets is mutated by way of a
155 // StyleConstants key, all the associated CSS keys are removed. This is
156 // done so that the two representations don't get out of sync. For
157 // example, if the developer adds StyleConsants.BOLD, FALSE to an
158 // AttributeSet that contains HTML.Tag.B, the HTML.Tag.B entry will
159 // be removed.
160
161 /**
162 * Construct a StyleSheet
163 */
164 public StyleSheet() {
165 super();
166 selectorMapping = new SelectorMapping(0);
167 resolvedStyles = new Hashtable();
168 if (css == null) {
169 css = new CSS();
170 }
171 }
172
173 /**
174 * Fetches the style to use to render the given type
175 * of HTML tag. The element given is representing
176 * the tag and can be used to determine the nesting
177 * for situations where the attributes will differ
178 * if nesting inside of elements.
179 *
180 * @param t the type to translate to visual attributes
181 * @param e the element representing the tag; the element
182 * can be used to determine the nesting for situations where
183 * the attributes will differ if nested inside of other
184 * elements
185 * @return the set of CSS attributes to use to render
186 * the tag
187 */
188 public Style getRule(HTML.Tag t, Element e) {
189 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
190
191 try {
192 // Build an array of all the parent elements.
193 Vector searchContext = sb.getVector();
194
195 for (Element p = e; p != null; p = p.getParentElement()) {
196 searchContext.addElement(p);
197 }
198
199 // Build a fully qualified selector.
200 int n = searchContext.size();
201 StringBuffer cacheLookup = sb.getStringBuffer();
202 AttributeSet attr;
203 String eName;
204 Object name;
205
206 // >= 1 as the HTML.Tag for the 0th element is passed in.
207 for (int counter = n - 1; counter >= 1; counter--) {
208 e = (Element)searchContext.elementAt(counter);
209 attr = e.getAttributes();
210 name = attr.getAttribute(StyleConstants.NameAttribute);
211 eName = name.toString();
212 cacheLookup.append(eName);
213 if (attr != null) {
214 if (attr.isDefined(HTML.Attribute.ID)) {
215 cacheLookup.append('#');
216 cacheLookup.append(attr.getAttribute
217 (HTML.Attribute.ID));
218 }
219 else if (attr.isDefined(HTML.Attribute.CLASS)) {
220 cacheLookup.append('.');
221 cacheLookup.append(attr.getAttribute
222 (HTML.Attribute.CLASS));
223 }
224 }
225 cacheLookup.append(' ');
226 }
227 cacheLookup.append(t.toString());
228 e = (Element)searchContext.elementAt(0);
229 attr = e.getAttributes();
230 if (e.isLeaf()) {
231 // For leafs, we use the second tier attributes.
232 Object testAttr = attr.getAttribute(t);
233 if (testAttr instanceof AttributeSet) {
234 attr = (AttributeSet)testAttr;
235 }
236 else {
237 attr = null;
238 }
239 }
240 if (attr != null) {
241 if (attr.isDefined(HTML.Attribute.ID)) {
242 cacheLookup.append('#');
243 cacheLookup.append(attr.getAttribute(HTML.Attribute.ID));
244 }
245 else if (attr.isDefined(HTML.Attribute.CLASS)) {
246 cacheLookup.append('.');
247 cacheLookup.append(attr.getAttribute
248 (HTML.Attribute.CLASS));
249 }
250 }
251
252 Style style = getResolvedStyle(cacheLookup.toString(),
253 searchContext, t);
254 return style;
255 }
256 finally {
257 SearchBuffer.releaseSearchBuffer(sb);
258 }
259 }
260
261 /**
262 * Fetches the rule that best matches the selector given
263 * in string form. Where <code>selector</code> is a space separated
264 * String of the element names. For example, <code>selector</code>
265 * might be 'html body tr td''<p>
266 * The attributes of the returned Style will change
267 * as rules are added and removed. That is if you to ask for a rule
268 * with a selector "table p" and a new rule was added with a selector
269 * of "p" the returned Style would include the new attributes from
270 * the rule "p".
271 */
272 public Style getRule(String selector) {
273 selector = cleanSelectorString(selector);
274 if (selector != null) {
275 Style style = getResolvedStyle(selector);
276 return style;
277 }
278 return null;
279 }
280
281 /**
282 * Adds a set of rules to the sheet. The rules are expected to
283 * be in valid CSS format. Typically this would be called as
284 * a result of parsing a <style> tag.
285 */
286 public void addRule(String rule) {
287 if (rule != null) {
288 //tweaks to control display properties
289 //see BasicEditorPaneUI
290 final String baseUnitsDisable = "BASE_SIZE_DISABLE";
291 final String baseUnits = "BASE_SIZE ";
292 final String w3cLengthUnitsEnable = "W3C_LENGTH_UNITS_ENABLE";
293 final String w3cLengthUnitsDisable = "W3C_LENGTH_UNITS_DISABLE";
294 if (rule == baseUnitsDisable) {
295 sizeMap = sizeMapDefault;
296 } else if (rule.startsWith(baseUnits)) {
297 rebaseSizeMap(Integer.
298 parseInt(rule.substring(baseUnits.length())));
299 } else if (rule == w3cLengthUnitsEnable) {
300 w3cLengthUnits = true;
301 } else if (rule == w3cLengthUnitsDisable) {
302 w3cLengthUnits = false;
303 } else {
304 CssParser parser = new CssParser();
305 try {
306 parser.parse(getBase(), new StringReader(rule), false, false);
307 } catch (IOException ioe) { }
308 }
309 }
310 }
311
312 /**
313 * Translates a CSS declaration to an AttributeSet that represents
314 * the CSS declaration. Typically this would be called as a
315 * result of encountering an HTML style attribute.
316 */
317 public AttributeSet getDeclaration(String decl) {
318 if (decl == null) {
319 return SimpleAttributeSet.EMPTY;
320 }
321 CssParser parser = new CssParser();
322 return parser.parseDeclaration(decl);
323 }
324
325 /**
326 * Loads a set of rules that have been specified in terms of
327 * CSS1 grammar. If there are collisions with existing rules,
328 * the newly specified rule will win.
329 *
330 * @param in the stream to read the CSS grammar from
331 * @param ref the reference URL. This value represents the
332 * location of the stream and may be null. All relative
333 * URLs specified in the stream will be based upon this
334 * parameter.
335 */
336 public void loadRules(Reader in, URL ref) throws IOException {
337 CssParser parser = new CssParser();
338 parser.parse(ref, in, false, false);
339 }
340
341 /**
342 * Fetches a set of attributes to use in the view for
343 * displaying. This is basically a set of attributes that
344 * can be used for View.getAttributes.
345 */
346 public AttributeSet getViewAttributes(View v) {
347 return new ViewAttributeSet(v);
348 }
349
350 /**
351 * Removes a named style previously added to the document.
352 *
353 * @param nm the name of the style to remove
354 */
355 public void removeStyle(String nm) {
356 Style aStyle = getStyle(nm);
357
358 if (aStyle != null) {
359 String selector = cleanSelectorString(nm);
360 String[] selectors = getSimpleSelectors(selector);
361 synchronized(this) {
362 SelectorMapping mapping = getRootSelectorMapping();
363 for (int i = selectors.length - 1; i >= 0; i--) {
364 mapping = mapping.getChildSelectorMapping(selectors[i],
365 true);
366 }
367 Style rule = mapping.getStyle();
368 if (rule != null) {
369 mapping.setStyle(null);
370 if (resolvedStyles.size() > 0) {
371 Enumeration values = resolvedStyles.elements();
372 while (values.hasMoreElements()) {
373 ResolvedStyle style = (ResolvedStyle)values.
374 nextElement();
375 style.removeStyle(rule);
376 }
377 }
378 }
379 }
380 }
381 super.removeStyle(nm);
382 }
383
384 /**
385 * Adds the rules from the StyleSheet <code>ss</code> to those of
386 * the receiver. <code>ss's</code> rules will override the rules of
387 * any previously added style sheets. An added StyleSheet will never
388 * override the rules of the receiving style sheet.
389 *
390 * @since 1.3
391 */
392 public void addStyleSheet(StyleSheet ss) {
393 synchronized(this) {
394 if (linkedStyleSheets == null) {
395 linkedStyleSheets = new Vector();
396 }
397 if (!linkedStyleSheets.contains(ss)) {
398 int index = 0;
399 if (ss instanceof javax.swing.plaf.UIResource
400 && linkedStyleSheets.size() > 1) {
401 index = linkedStyleSheets.size() - 1;
402 }
403 linkedStyleSheets.insertElementAt(ss, index);
404 linkStyleSheetAt(ss, index);
405 }
406 }
407 }
408
409 /**
410 * Removes the StyleSheet <code>ss</code> from those of the receiver.
411 *
412 * @since 1.3
413 */
414 public void removeStyleSheet(StyleSheet ss) {
415 synchronized(this) {
416 if (linkedStyleSheets != null) {
417 int index = linkedStyleSheets.indexOf(ss);
418 if (index != -1) {
419 linkedStyleSheets.removeElementAt(index);
420 unlinkStyleSheet(ss, index);
421 if (index == 0 && linkedStyleSheets.size() == 0) {
422 linkedStyleSheets = null;
423 }
424 }
425 }
426 }
427 }
428
429 //
430 // The following is used to import style sheets.
431 //
432
433 /**
434 * Returns an array of the linked StyleSheets. Will return null
435 * if there are no linked StyleSheets.
436 *
437 * @since 1.3
438 */
439 public StyleSheet[] getStyleSheets() {
440 StyleSheet[] retValue;
441
442 synchronized(this) {
443 if (linkedStyleSheets != null) {
444 retValue = new StyleSheet[linkedStyleSheets.size()];
445 linkedStyleSheets.copyInto(retValue);
446 }
447 else {
448 retValue = null;
449 }
450 }
451 return retValue;
452 }
453
454 /**
455 * Imports a style sheet from <code>url</code>. The resulting rules
456 * are directly added to the receiver. If you do not want the rules
457 * to become part of the receiver, create a new StyleSheet and use
458 * addStyleSheet to link it in.
459 *
460 * @since 1.3
461 */
462 public void importStyleSheet(URL url) {
463 try {
464 InputStream is;
465
466 is = url.openStream();
467 Reader r = new BufferedReader(new InputStreamReader(is));
468 CssParser parser = new CssParser();
469 parser.parse(url, r, false, true);
470 r.close();
471 is.close();
472 } catch (Throwable e) {
473 // on error we simply have no styles... the html
474 // will look mighty wrong but still function.
475 }
476 }
477
478 /**
479 * Sets the base. All import statements that are relative, will be
480 * relative to <code>base</code>.
481 *
482 * @since 1.3
483 */
484 public void setBase(URL base) {
485 this.base = base;
486 }
487
488 /**
489 * Returns the base.
490 *
491 * @since 1.3
492 */
493 public URL getBase() {
494 return base;
495 }
496
497 /**
498 * Adds a CSS attribute to the given set.
499 *
500 * @since 1.3
501 */
502 public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key,
503 String value) {
504 css.addInternalCSSValue(attr, key, value);
505 }
506
507 /**
508 * Adds a CSS attribute to the given set.
509 *
510 * @since 1.3
511 */
512 public boolean addCSSAttributeFromHTML(MutableAttributeSet attr,
513 CSS.Attribute key, String value) {
514 Object iValue = css.getCssValue(key, value);
515 if (iValue != null) {
516 attr.addAttribute(key, iValue);
517 return true;
518 }
519 return false;
520 }
521
522 // ---- Conversion functionality ---------------------------------
523
524 /**
525 * Converts a set of HTML attributes to an equivalent
526 * set of CSS attributes.
527 *
528 * @param htmlAttrSet AttributeSet containing the HTML attributes.
529 */
530 public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) {
531 AttributeSet cssAttrSet = css.translateHTMLToCSS(htmlAttrSet);
532
533 MutableAttributeSet cssStyleSet = addStyle(null, null);
534 cssStyleSet.addAttributes(cssAttrSet);
535
536 return cssStyleSet;
537 }
538
539 /**
540 * Adds an attribute to the given set, and returns
541 * the new representative set. This is reimplemented to
542 * convert StyleConstant attributes to CSS prior to forwarding
543 * to the superclass behavior. The StyleConstants attribute
544 * has no corresponding CSS entry, the StyleConstants attribute
545 * is stored (but will likely be unused).
546 *
547 * @param old the old attribute set
548 * @param key the non-null attribute key
549 * @param value the attribute value
550 * @return the updated attribute set
551 * @see MutableAttributeSet#addAttribute
552 */
553 public AttributeSet addAttribute(AttributeSet old, Object key,
554 Object value) {
555 if (css == null) {
556 // supers constructor will call this before returning,
557 // and we need to make sure CSS is non null.
558 css = new CSS();
559 }
560 if (key instanceof StyleConstants) {
561 HTML.Tag tag = HTML.getTagForStyleConstantsKey(
562 (StyleConstants)key);
563
564 if (tag != null && old.isDefined(tag)) {
565 old = removeAttribute(old, tag);
566 }
567
568 Object cssValue = css.styleConstantsValueToCSSValue
569 ((StyleConstants)key, value);
570 if (cssValue != null) {
571 Object cssKey = css.styleConstantsKeyToCSSKey
572 ((StyleConstants)key);
573 if (cssKey != null) {
574 return super.addAttribute(old, cssKey, cssValue);
575 }
576 }
577 }
578 return super.addAttribute(old, key, value);
579 }
580
581 /**
582 * Adds a set of attributes to the element. If any of these attributes
583 * are StyleConstants attributes, they will be converted to CSS prior
584 * to forwarding to the superclass behavior.
585 *
586 * @param old the old attribute set
587 * @param attr the attributes to add
588 * @return the updated attribute set
589 * @see MutableAttributeSet#addAttribute
590 */
591 public AttributeSet addAttributes(AttributeSet old, AttributeSet attr) {
592 if (!(attr instanceof HTMLDocument.TaggedAttributeSet)) {
593 old = removeHTMLTags(old, attr);
594 }
595 return super.addAttributes(old, convertAttributeSet(attr));
596 }
597
598 /**
599 * Removes an attribute from the set. If the attribute is a StyleConstants
600 * attribute, the request will be converted to a CSS attribute prior to
601 * forwarding to the superclass behavior.
602 *
603 * @param old the old set of attributes
604 * @param key the non-null attribute name
605 * @return the updated attribute set
606 * @see MutableAttributeSet#removeAttribute
607 */
608 public AttributeSet removeAttribute(AttributeSet old, Object key) {
609 if (key instanceof StyleConstants) {
610 HTML.Tag tag = HTML.getTagForStyleConstantsKey(
611 (StyleConstants)key);
612 if (tag != null) {
613 old = super.removeAttribute(old, tag);
614 }
615
616 Object cssKey = css.styleConstantsKeyToCSSKey((StyleConstants)key);
617 if (cssKey != null) {
618 return super.removeAttribute(old, cssKey);
619 }
620 }
621 return super.removeAttribute(old, key);
622 }
623
624 /**
625 * Removes a set of attributes for the element. If any of the attributes
626 * is a StyleConstants attribute, the request will be converted to a CSS
627 * attribute prior to forwarding to the superclass behavior.
628 *
629 * @param old the old attribute set
630 * @param names the attribute names
631 * @return the updated attribute set
632 * @see MutableAttributeSet#removeAttributes
633 */
634 public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) {
635 // PENDING: Should really be doing something similar to
636 // removeHTMLTags here, but it is rather expensive to have to
637 // clone names
638 return super.removeAttributes(old, names);
639 }
640
641 /**
642 * Removes a set of attributes. If any of the attributes
643 * is a StyleConstants attribute, the request will be converted to a CSS
644 * attribute prior to forwarding to the superclass behavior.
645 *
646 * @param old the old attribute set
647 * @param attrs the attributes
648 * @return the updated attribute set
649 * @see MutableAttributeSet#removeAttributes
650 */
651 public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs) {
652 if (old != attrs) {
653 old = removeHTMLTags(old, attrs);
654 }
655 return super.removeAttributes(old, convertAttributeSet(attrs));
656 }
657
658 /**
659 * Creates a compact set of attributes that might be shared.
660 * This is a hook for subclasses that want to alter the
661 * behavior of SmallAttributeSet. This can be reimplemented
662 * to return an AttributeSet that provides some sort of
663 * attribute conversion.
664 *
665 * @param a The set of attributes to be represented in the
666 * the compact form.
667 */
668 protected SmallAttributeSet createSmallAttributeSet(AttributeSet a) {
669 return new SmallConversionSet(a);
670 }
671
672 /**
673 * Creates a large set of attributes that should trade off
674 * space for time. This set will not be shared. This is
675 * a hook for subclasses that want to alter the behavior
676 * of the larger attribute storage format (which is
677 * SimpleAttributeSet by default). This can be reimplemented
678 * to return a MutableAttributeSet that provides some sort of
679 * attribute conversion.
680 *
681 * @param a The set of attributes to be represented in the
682 * the larger form.
683 */
684 protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) {
685 return new LargeConversionSet(a);
686 }
687
688 /**
689 * For any StyleConstants key in attr that has an associated HTML.Tag,
690 * it is removed from old. The resulting AttributeSet is then returned.
691 */
692 private AttributeSet removeHTMLTags(AttributeSet old, AttributeSet attr) {
693 if (!(attr instanceof LargeConversionSet) &&
694 !(attr instanceof SmallConversionSet)) {
695 Enumeration names = attr.getAttributeNames();
696
697 while (names.hasMoreElements()) {
698 Object key = names.nextElement();
699
700 if (key instanceof StyleConstants) {
701 HTML.Tag tag = HTML.getTagForStyleConstantsKey(
702 (StyleConstants)key);
703
704 if (tag != null && old.isDefined(tag)) {
705 old = super.removeAttribute(old, tag);
706 }
707 }
708 }
709 }
710 return old;
711 }
712
713 /**
714 * Converts a set of attributes (if necessary) so that
715 * any attributes that were specified as StyleConstants
716 * attributes and have a CSS mapping, will be converted
717 * to CSS attributes.
718 */
719 AttributeSet convertAttributeSet(AttributeSet a) {
720 if ((a instanceof LargeConversionSet) ||
721 (a instanceof SmallConversionSet)) {
722 // known to be converted.
723 return a;
724 }
725 // in most cases, there are no StyleConstants attributes
726 // so we iterate the collection of keys to avoid creating
727 // a new set.
728 Enumeration names = a.getAttributeNames();
729 while (names.hasMoreElements()) {
730 Object name = names.nextElement();
731 if (name instanceof StyleConstants) {
732 // we really need to do a conversion, iterate again
733 // building a new set.
734 MutableAttributeSet converted = new LargeConversionSet();
735 Enumeration keys = a.getAttributeNames();
736 while (keys.hasMoreElements()) {
737 Object key = keys.nextElement();
738 Object cssValue = null;
739 if (key instanceof StyleConstants) {
740 // convert the StyleConstants attribute if possible
741 Object cssKey = css.styleConstantsKeyToCSSKey
742 ((StyleConstants)key);
743 if (cssKey != null) {
744 Object value = a.getAttribute(key);
745 cssValue = css.styleConstantsValueToCSSValue
746 ((StyleConstants)key, value);
747 if (cssValue != null) {
748 converted.addAttribute(cssKey, cssValue);
749 }
750 }
751 }
752 if (cssValue == null) {
753 converted.addAttribute(key, a.getAttribute(key));
754 }
755 }
756 return converted;
757 }
758 }
759 return a;
760 }
761
762 /**
763 * Large set of attributes that does conversion of requests
764 * for attributes of type StyleConstants.
765 */
766 class LargeConversionSet extends SimpleAttributeSet {
767
768 /**
769 * Creates a new attribute set based on a supplied set of attributes.
770 *
771 * @param source the set of attributes
772 */
773 public LargeConversionSet(AttributeSet source) {
774 super(source);
775 }
776
777 public LargeConversionSet() {
778 super();
779 }
780
781 /**
782 * Checks whether a given attribute is defined.
783 *
784 * @param key the attribute key
785 * @return true if the attribute is defined
786 * @see AttributeSet#isDefined
787 */
788 public boolean isDefined(Object key) {
789 if (key instanceof StyleConstants) {
790 Object cssKey = css.styleConstantsKeyToCSSKey
791 ((StyleConstants)key);
792 if (cssKey != null) {
793 return super.isDefined(cssKey);
794 }
795 }
796 return super.isDefined(key);
797 }
798
799 /**
800 * Gets the value of an attribute.
801 *
802 * @param key the attribute name
803 * @return the attribute value
804 * @see AttributeSet#getAttribute
805 */
806 public Object getAttribute(Object key) {
807 if (key instanceof StyleConstants) {
808 Object cssKey = css.styleConstantsKeyToCSSKey
809 ((StyleConstants)key);
810 if (cssKey != null) {
811 Object value = super.getAttribute(cssKey);
812 if (value != null) {
813 return css.cssValueToStyleConstantsValue
814 ((StyleConstants)key, value);
815 }
816 }
817 }
818 return super.getAttribute(key);
819 }
820 }
821
822 /**
823 * Small set of attributes that does conversion of requests
824 * for attributes of type StyleConstants.
825 */
826 class SmallConversionSet extends SmallAttributeSet {
827
828 /**
829 * Creates a new attribute set based on a supplied set of attributes.
830 *
831 * @param source the set of attributes
832 */
833 public SmallConversionSet(AttributeSet attrs) {
834 super(attrs);
835 }
836
837 /**
838 * Checks whether a given attribute is defined.
839 *
840 * @param key the attribute key
841 * @return true if the attribute is defined
842 * @see AttributeSet#isDefined
843 */
844 public boolean isDefined(Object key) {
845 if (key instanceof StyleConstants) {
846 Object cssKey = css.styleConstantsKeyToCSSKey
847 ((StyleConstants)key);
848 if (cssKey != null) {
849 return super.isDefined(cssKey);
850 }
851 }
852 return super.isDefined(key);
853 }
854
855 /**
856 * Gets the value of an attribute.
857 *
858 * @param key the attribute name
859 * @return the attribute value
860 * @see AttributeSet#getAttribute
861 */
862 public Object getAttribute(Object key) {
863 if (key instanceof StyleConstants) {
864 Object cssKey = css.styleConstantsKeyToCSSKey
865 ((StyleConstants)key);
866 if (cssKey != null) {
867 Object value = super.getAttribute(cssKey);
868 if (value != null) {
869 return css.cssValueToStyleConstantsValue
870 ((StyleConstants)key, value);
871 }
872 }
873 }
874 return super.getAttribute(key);
875 }
876 }
877
878 // ---- Resource handling ----------------------------------------
879
880 /**
881 * Fetches the font to use for the given set of attributes.
882 */
883 public Font getFont(AttributeSet a) {
884 return css.getFont(this, a, 12, this);
885 }
886
887 /**
888 * Takes a set of attributes and turn it into a foreground color
889 * specification. This might be used to specify things
890 * like brighter, more hue, etc.
891 *
892 * @param a the set of attributes
893 * @return the color
894 */
895 public Color getForeground(AttributeSet a) {
896 Color c = css.getColor(a, CSS.Attribute.COLOR);
897 if (c == null) {
898 return Color.black;
899 }
900 return c;
901 }
902
903 /**
904 * Takes a set of attributes and turn it into a background color
905 * specification. This might be used to specify things
906 * like brighter, more hue, etc.
907 *
908 * @param a the set of attributes
909 * @return the color
910 */
911 public Color getBackground(AttributeSet a) {
912 return css.getColor(a, CSS.Attribute.BACKGROUND_COLOR);
913 }
914
915 /**
916 * Fetches the box formatter to use for the given set
917 * of CSS attributes.
918 */
919 public BoxPainter getBoxPainter(AttributeSet a) {
920 return new BoxPainter(a, css, this);
921 }
922
923 /**
924 * Fetches the list formatter to use for the given set
925 * of CSS attributes.
926 */
927 public ListPainter getListPainter(AttributeSet a) {
928 return new ListPainter(a, this);
929 }
930
931 /**
932 * Sets the base font size, with valid values between 1 and 7.
933 */
934 public void setBaseFontSize(int sz) {
935 css.setBaseFontSize(sz);
936 }
937
938 /**
939 * Sets the base font size from the passed in String. The string
940 * can either identify a specific font size, with legal values between
941 * 1 and 7, or identifiy a relative font size such as +1 or -2.
942 */
943 public void setBaseFontSize(String size) {
944 css.setBaseFontSize(size);
945 }
946
947 public static int getIndexOfSize(float pt) {
948 return CSS.getIndexOfSize(pt, sizeMapDefault);
949 }
950
951 /**
952 * Returns the point size, given a size index.
953 */
954 public float getPointSize(int index) {
955 return css.getPointSize(index, this);
956 }
957
958 /**
959 * Given a string such as "+2", "-2", or "2",
960 * returns a point size value.
961 */
962 public float getPointSize(String size) {
963 return css.getPointSize(size, this);
964 }
965
966 /**
967 * Converts a color string such as "RED" or "#NNNNNN" to a Color.
968 * Note: This will only convert the HTML3.2 color strings
969 * or a string of length 7;
970 * otherwise, it will return null.
971 */
972 public Color stringToColor(String string) {
973 return CSS.stringToColor(string);
974 }
975
976 /**
977 * Returns the ImageIcon to draw in the background for
978 * <code>attr</code>.
979 */
980 ImageIcon getBackgroundImage(AttributeSet attr) {
981 Object value = attr.getAttribute(CSS.Attribute.BACKGROUND_IMAGE);
982
983 if (value != null) {
984 return ((CSS.BackgroundImage)value).getImage(getBase());
985 }
986 return null;
987 }
988
989 /**
990 * Adds a rule into the StyleSheet.
991 *
992 * @param selector the selector to use for the rule.
993 * This will be a set of simple selectors, and must
994 * be a length of 1 or greater.
995 * @param declaration the set of CSS attributes that
996 * make up the rule.
997 */
998 void addRule(String[] selector, AttributeSet declaration,
999 boolean isLinked) {
1000 int n = selector.length;
1001 StringBuffer sb = new StringBuffer();
1002 sb.append(selector[0]);
1003 for (int counter = 1; counter < n; counter++) {
1004 sb.append(' ');
1005 sb.append(selector[counter]);
1006 }
1007 String selectorName = sb.toString();
1008 Style rule = getStyle(selectorName);
1009 if (rule == null) {
1010 // Notice how the rule is first created, and it not part of
1011 // the synchronized block. It is done like this as creating
1012 // a new rule will fire a ChangeEvent. We do not want to be
1013 // holding the lock when calling to other objects, it can
1014 // result in deadlock.
1015 Style altRule = addStyle(selectorName, null);
1016 synchronized(this) {
1017 SelectorMapping mapping = getRootSelectorMapping();
1018 for (int i = n - 1; i >= 0; i--) {
1019 mapping = mapping.getChildSelectorMapping
1020 (selector[i], true);
1021 }
1022 rule = mapping.getStyle();
1023 if (rule == null) {
1024 rule = altRule;
1025 mapping.setStyle(rule);
1026 refreshResolvedRules(selectorName, selector, rule,
1027 mapping.getSpecificity());
1028 }
1029 }
1030 }
1031 if (isLinked) {
1032 rule = getLinkedStyle(rule);
1033 }
1034 rule.addAttributes(declaration);
1035 }
1036
1037 //
1038 // The following gaggle of methods is used in maintaing the rules from
1039 // the sheet.
1040 //
1041
1042 /**
1043 * Updates the attributes of the rules to reference any related
1044 * rules in <code>ss</code>.
1045 */
1046 private synchronized void linkStyleSheetAt(StyleSheet ss, int index) {
1047 if (resolvedStyles.size() > 0) {
1048 Enumeration values = resolvedStyles.elements();
1049 while (values.hasMoreElements()) {
1050 ResolvedStyle rule = (ResolvedStyle)values.nextElement();
1051 rule.insertExtendedStyleAt(ss.getRule(rule.getName()),
1052 index);
1053 }
1054 }
1055 }
1056
1057 /**
1058 * Removes references to the rules in <code>ss</code>.
1059 * <code>index</code> gives the index the StyleSheet was at, that is
1060 * how many StyleSheets had been added before it.
1061 */
1062 private synchronized void unlinkStyleSheet(StyleSheet ss, int index) {
1063 if (resolvedStyles.size() > 0) {
1064 Enumeration values = resolvedStyles.elements();
1065 while (values.hasMoreElements()) {
1066 ResolvedStyle rule = (ResolvedStyle)values.nextElement();
1067 rule.removeExtendedStyleAt(index);
1068 }
1069 }
1070 }
1071
1072 /**
1073 * Returns the simple selectors that comprise selector.
1074 */
1075 /* protected */
1076 String[] getSimpleSelectors(String selector) {
1077 selector = cleanSelectorString(selector);
1078 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1079 Vector selectors = sb.getVector();
1080 int lastIndex = 0;
1081 int length = selector.length();
1082 while (lastIndex != -1) {
1083 int newIndex = selector.indexOf(' ', lastIndex);
1084 if (newIndex != -1) {
1085 selectors.addElement(selector.substring(lastIndex, newIndex));
1086 if (++newIndex == length) {
1087 lastIndex = -1;
1088 }
1089 else {
1090 lastIndex = newIndex;
1091 }
1092 }
1093 else {
1094 selectors.addElement(selector.substring(lastIndex));
1095 lastIndex = -1;
1096 }
1097 }
1098 String[] retValue = new String[selectors.size()];
1099 selectors.copyInto(retValue);
1100 SearchBuffer.releaseSearchBuffer(sb);
1101 return retValue;
1102 }
1103
1104 /**
1105 * Returns a string that only has one space between simple selectors,
1106 * which may be the passed in String.
1107 */
1108 /*protected*/ String cleanSelectorString(String selector) {
1109 boolean lastWasSpace = true;
1110 for (int counter = 0, maxCounter = selector.length();
1111 counter < maxCounter; counter++) {
1112 switch(selector.charAt(counter)) {
1113 case ' ':
1114 if (lastWasSpace) {
1115 return _cleanSelectorString(selector);
1116 }
1117 lastWasSpace = true;
1118 break;
1119 case '\n':
1120 case '\r':
1121 case '\t':
1122 return _cleanSelectorString(selector);
1123 default:
1124 lastWasSpace = false;
1125 }
1126 }
1127 if (lastWasSpace) {
1128 return _cleanSelectorString(selector);
1129 }
1130 // It was fine.
1131 return selector;
1132 }
1133
1134 /**
1135 * Returns a new String that contains only one space between non
1136 * white space characters.
1137 */
1138 private String _cleanSelectorString(String selector) {
1139 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1140 StringBuffer buff = sb.getStringBuffer();
1141 boolean lastWasSpace = true;
1142 int lastIndex = 0;
1143 char[] chars = selector.toCharArray();
1144 int numChars = chars.length;
1145 String retValue = null;
1146 try {
1147 for (int counter = 0; counter < numChars; counter++) {
1148 switch(chars[counter]) {
1149 case ' ':
1150 if (!lastWasSpace) {
1151 lastWasSpace = true;
1152 if (lastIndex < counter) {
1153 buff.append(chars, lastIndex,
1154 1 + counter - lastIndex);
1155 }
1156 }
1157 lastIndex = counter + 1;
1158 break;
1159 case '\n':
1160 case '\r':
1161 case '\t':
1162 if (!lastWasSpace) {
1163 lastWasSpace = true;
1164 if (lastIndex < counter) {
1165 buff.append(chars, lastIndex,
1166 counter - lastIndex);
1167 buff.append(' ');
1168 }
1169 }
1170 lastIndex = counter + 1;
1171 break;
1172 default:
1173 lastWasSpace = false;
1174 break;
1175 }
1176 }
1177 if (lastWasSpace && buff.length() > 0) {
1178 // Remove last space.
1179 buff.setLength(buff.length() - 1);
1180 }
1181 else if (lastIndex < numChars) {
1182 buff.append(chars, lastIndex, numChars - lastIndex);
1183 }
1184 retValue = buff.toString();
1185 }
1186 finally {
1187 SearchBuffer.releaseSearchBuffer(sb);
1188 }
1189 return retValue;
1190 }
1191
1192 /**
1193 * Returns the root selector mapping that all selectors are relative
1194 * to. This is an inverted graph of the selectors.
1195 */
1196 private SelectorMapping getRootSelectorMapping() {
1197 return selectorMapping;
1198 }
1199
1200 /**
1201 * Returns the specificity of the passed in String. It assumes the
1202 * passed in string doesn't contain junk, that is each selector is
1203 * separated by a space and each selector at most contains one . or one
1204 * #. A simple selector has a weight of 1, an id selector has a weight
1205 * of 100, and a class selector has a weight of 10000.
1206 */
1207 /*protected*/ static int getSpecificity(String selector) {
1208 int specificity = 0;
1209 boolean lastWasSpace = true;
1210
1211 for (int counter = 0, maxCounter = selector.length();
1212 counter < maxCounter; counter++) {
1213 switch(selector.charAt(counter)) {
1214 case '.':
1215 specificity += 100;
1216 break;
1217 case '#':
1218 specificity += 10000;
1219 break;
1220 case ' ':
1221 lastWasSpace = true;
1222 break;
1223 default:
1224 if (lastWasSpace) {
1225 lastWasSpace = false;
1226 specificity += 1;
1227 }
1228 }
1229 }
1230 return specificity;
1231 }
1232
1233 /**
1234 * Returns the style that linked attributes should be added to. This
1235 * will create the style if necessary.
1236 */
1237 private Style getLinkedStyle(Style localStyle) {
1238 // NOTE: This is not synchronized, and the caller of this does
1239 // not synchronize. There is the chance for one of the callers to
1240 // overwrite the existing resolved parent, but it is quite rare.
1241 // The reason this is left like this is because setResolveParent
1242 // will fire a ChangeEvent. It is really, REALLY bad for us to
1243 // hold a lock when calling outside of us, it may cause a deadlock.
1244 Style retStyle = (Style)localStyle.getResolveParent();
1245 if (retStyle == null) {
1246 retStyle = addStyle(null, null);
1247 localStyle.setResolveParent(retStyle);
1248 }
1249 return retStyle;
1250 }
1251
1252 /**
1253 * Returns the resolved style for <code>selector</code>. This will
1254 * create the resolved style, if necessary.
1255 */
1256 private synchronized Style getResolvedStyle(String selector,
1257 Vector elements,
1258 HTML.Tag t) {
1259 Style retStyle = (Style)resolvedStyles.get(selector);
1260 if (retStyle == null) {
1261 retStyle = createResolvedStyle(selector, elements, t);
1262 }
1263 return retStyle;
1264 }
1265
1266 /**
1267 * Returns the resolved style for <code>selector</code>. This will
1268 * create the resolved style, if necessary.
1269 */
1270 private synchronized Style getResolvedStyle(String selector) {
1271 Style retStyle = (Style)resolvedStyles.get(selector);
1272 if (retStyle == null) {
1273 retStyle = createResolvedStyle(selector);
1274 }
1275 return retStyle;
1276 }
1277
1278 /**
1279 * Adds <code>mapping</code> to <code>elements</code>. It is added
1280 * such that <code>elements</code> will remain ordered by
1281 * specificity.
1282 */
1283 private void addSortedStyle(SelectorMapping mapping, Vector elements) {
1284 int size = elements.size();
1285
1286 if (size > 0) {
1287 int specificity = mapping.getSpecificity();
1288
1289 for (int counter = 0; counter < size; counter++) {
1290 if (specificity >= ((SelectorMapping)elements.elementAt
1291 (counter)).getSpecificity()) {
1292 elements.insertElementAt(mapping, counter);
1293 return;
1294 }
1295 }
1296 }
1297 elements.addElement(mapping);
1298 }
1299
1300 /**
1301 * Adds <code>parentMapping</code> to <code>styles</code>, and
1302 * recursively calls this method if <code>parentMapping</code> has
1303 * any child mappings for any of the Elements in <code>elements</code>.
1304 */
1305 private synchronized void getStyles(SelectorMapping parentMapping,
1306 Vector styles,
1307 String[] tags, String[] ids, String[] classes,
1308 int index, int numElements,
1309 Hashtable alreadyChecked) {
1310 // Avoid desending the same mapping twice.
1311 if (alreadyChecked.contains(parentMapping)) {
1312 return;
1313 }
1314 alreadyChecked.put(parentMapping, parentMapping);
1315 Style style = parentMapping.getStyle();
1316 if (style != null) {
1317 addSortedStyle(parentMapping, styles);
1318 }
1319 for (int counter = index; counter < numElements; counter++) {
1320 String tagString = tags[counter];
1321 if (tagString != null) {
1322 SelectorMapping childMapping = parentMapping.
1323 getChildSelectorMapping(tagString, false);
1324 if (childMapping != null) {
1325 getStyles(childMapping, styles, tags, ids, classes,
1326 counter + 1, numElements, alreadyChecked);
1327 }
1328 if (classes[counter] != null) {
1329 String className = classes[counter];
1330 childMapping = parentMapping.getChildSelectorMapping(
1331 tagString + "." + className, false);
1332 if (childMapping != null) {
1333 getStyles(childMapping, styles, tags, ids, classes,
1334 counter + 1, numElements, alreadyChecked);
1335 }
1336 childMapping = parentMapping.getChildSelectorMapping(
1337 "." + className, false);
1338 if (childMapping != null) {
1339 getStyles(childMapping, styles, tags, ids, classes,
1340 counter + 1, numElements, alreadyChecked);
1341 }
1342 }
1343 if (ids[counter] != null) {
1344 String idName = ids[counter];
1345 childMapping = parentMapping.getChildSelectorMapping(
1346 tagString + "#" + idName, false);
1347 if (childMapping != null) {
1348 getStyles(childMapping, styles, tags, ids, classes,
1349 counter + 1, numElements, alreadyChecked);
1350 }
1351 childMapping = parentMapping.getChildSelectorMapping(
1352 "#" + idName, false);
1353 if (childMapping != null) {
1354 getStyles(childMapping, styles, tags, ids, classes,
1355 counter + 1, numElements, alreadyChecked);
1356 }
1357 }
1358 }
1359 }
1360 }
1361
1362 /**
1363 * Creates and returns a Style containing all the rules that match
1364 * <code>selector</code>.
1365 */
1366 private synchronized Style createResolvedStyle(String selector,
1367 String[] tags,
1368 String[] ids, String[] classes) {
1369 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1370 Vector tempVector = sb.getVector();
1371 Hashtable tempHashtable = sb.getHashtable();
1372 // Determine all the Styles that are appropriate, placing them
1373 // in tempVector
1374 try {
1375 SelectorMapping mapping = getRootSelectorMapping();
1376 int numElements = tags.length;
1377 String tagString = tags[0];
1378 SelectorMapping childMapping = mapping.getChildSelectorMapping(
1379 tagString, false);
1380 if (childMapping != null) {
1381 getStyles(childMapping, tempVector, tags, ids, classes, 1,
1382 numElements, tempHashtable);
1383 }
1384 if (classes[0] != null) {
1385 String className = classes[0];
1386 childMapping = mapping.getChildSelectorMapping(
1387 tagString + "." + className, false);
1388 if (childMapping != null) {
1389 getStyles(childMapping, tempVector, tags, ids, classes, 1,
1390 numElements, tempHashtable);
1391 }
1392 childMapping = mapping.getChildSelectorMapping(
1393 "." + className, false);
1394 if (childMapping != null) {
1395 getStyles(childMapping, tempVector, tags, ids, classes,
1396 1, numElements, tempHashtable);
1397 }
1398 }
1399 if (ids[0] != null) {
1400 String idName = ids[0];
1401 childMapping = mapping.getChildSelectorMapping(
1402 tagString + "#" + idName, false);
1403 if (childMapping != null) {
1404 getStyles(childMapping, tempVector, tags, ids, classes,
1405 1, numElements, tempHashtable);
1406 }
1407 childMapping = mapping.getChildSelectorMapping(
1408 "#" + idName, false);
1409 if (childMapping != null) {
1410 getStyles(childMapping, tempVector, tags, ids, classes,
1411 1, numElements, tempHashtable);
1412 }
1413 }
1414 // Create a new Style that will delegate to all the matching
1415 // Styles.
1416 int numLinkedSS = (linkedStyleSheets != null) ?
1417 linkedStyleSheets.size() : 0;
1418 int numStyles = tempVector.size();
1419 AttributeSet[] attrs = new AttributeSet[numStyles + numLinkedSS];
1420 for (int counter = 0; counter < numStyles; counter++) {
1421 attrs[counter] = ((SelectorMapping)tempVector.
1422 elementAt(counter)).getStyle();
1423 }
1424 // Get the AttributeSet from linked style sheets.
1425 for (int counter = 0; counter < numLinkedSS; counter++) {
1426 AttributeSet attr = ((StyleSheet)linkedStyleSheets.
1427 elementAt(counter)).getRule(selector);
1428 if (attr == null) {
1429 attrs[counter + numStyles] = SimpleAttributeSet.EMPTY;
1430 }
1431 else {
1432 attrs[counter + numStyles] = attr;
1433 }
1434 }
1435 ResolvedStyle retStyle = new ResolvedStyle(selector, attrs,
1436 numStyles);
1437 resolvedStyles.put(selector, retStyle);
1438 return retStyle;
1439 }
1440 finally {
1441 SearchBuffer.releaseSearchBuffer(sb);
1442 }
1443 }
1444
1445 /**
1446 * Creates and returns a Style containing all the rules that
1447 * matches <code>selector</code>.
1448 *
1449 * @param elements a Vector of all the Elements
1450 * the style is being asked for. The
1451 * first Element is the deepest Element, with the last Element
1452 * representing the root.
1453 * @param t the Tag to use for
1454 * the first Element in <code>elements</code>
1455 */
1456 private Style createResolvedStyle(String selector, Vector elements,
1457 HTML.Tag t) {
1458 int numElements = elements.size();
1459 // Build three arrays, one for tags, one for class's, and one for
1460 // id's
1461 String tags[] = new String[numElements];
1462 String ids[] = new String[numElements];
1463 String classes[] = new String[numElements];
1464 for (int counter = 0; counter < numElements; counter++) {
1465 Element e = (Element)elements.elementAt(counter);
1466 AttributeSet attr = e.getAttributes();
1467 if (counter == 0 && e.isLeaf()) {
1468 // For leafs, we use the second tier attributes.
1469 Object testAttr = attr.getAttribute(t);
1470 if (testAttr instanceof AttributeSet) {
1471 attr = (AttributeSet)testAttr;
1472 }
1473 else {
1474 attr = null;
1475 }
1476 }
1477 if (attr != null) {
1478 HTML.Tag tag = (HTML.Tag)attr.getAttribute(StyleConstants.
1479 NameAttribute);
1480 if (tag != null) {
1481 tags[counter] = tag.toString();
1482 }
1483 else {
1484 tags[counter] = null;
1485 }
1486 if (attr.isDefined(HTML.Attribute.CLASS)) {
1487 classes[counter] = attr.getAttribute
1488 (HTML.Attribute.CLASS).toString();
1489 }
1490 else {
1491 classes[counter] = null;
1492 }
1493 if (attr.isDefined(HTML.Attribute.ID)) {
1494 ids[counter] = attr.getAttribute(HTML.Attribute.ID).
1495 toString();
1496 }
1497 else {
1498 ids[counter] = null;
1499 }
1500 }
1501 else {
1502 tags[counter] = ids[counter] = classes[counter] = null;
1503 }
1504 }
1505 tags[0] = t.toString();
1506 return createResolvedStyle(selector, tags, ids, classes);
1507 }
1508
1509 /**
1510 * Creates and returns a Style containing all the rules that match
1511 * <code>selector</code>. It is assumed that each simple selector
1512 * in <code>selector</code> is separated by a space.
1513 */
1514 private Style createResolvedStyle(String selector) {
1515 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1516 // Will contain the tags, ids, and classes, in that order.
1517 Vector elements = sb.getVector();
1518 try {
1519 boolean done;
1520 int dotIndex = 0;
1521 int spaceIndex = 0;
1522 int poundIndex = 0;
1523 int lastIndex = 0;
1524 int length = selector.length();
1525 while (lastIndex < length) {
1526 if (dotIndex == lastIndex) {
1527 dotIndex = selector.indexOf('.', lastIndex);
1528 }
1529 if (poundIndex == lastIndex) {
1530 poundIndex = selector.indexOf('#', lastIndex);
1531 }
1532 spaceIndex = selector.indexOf(' ', lastIndex);
1533 if (spaceIndex == -1) {
1534 spaceIndex = length;
1535 }
1536 if (dotIndex != -1 && poundIndex != -1 &&
1537 dotIndex < spaceIndex && poundIndex < spaceIndex) {
1538 if (poundIndex < dotIndex) {
1539 // #.
1540 if (lastIndex == poundIndex) {
1541 elements.addElement("");
1542 }
1543 else {
1544 elements.addElement(selector.substring(lastIndex,
1545 poundIndex));
1546 }
1547 if ((dotIndex + 1) < spaceIndex) {
1548 elements.addElement(selector.substring
1549 (dotIndex + 1, spaceIndex));
1550 }
1551 else {
1552 elements.addElement(null);
1553 }
1554 if ((poundIndex + 1) == dotIndex) {
1555 elements.addElement(null);
1556 }
1557 else {
1558 elements.addElement(selector.substring
1559 (poundIndex + 1, dotIndex));
1560 }
1561 }
1562 else if(poundIndex < spaceIndex) {
1563 // .#
1564 if (lastIndex == dotIndex) {
1565 elements.addElement("");
1566 }
1567 else {
1568 elements.addElement(selector.substring(lastIndex,
1569 dotIndex));
1570 }
1571 if ((dotIndex + 1) < poundIndex) {
1572 elements.addElement(selector.substring
1573 (dotIndex + 1, poundIndex));
1574 }
1575 else {
1576 elements.addElement(null);
1577 }
1578 if ((poundIndex + 1) == spaceIndex) {
1579 elements.addElement(null);
1580 }
1581 else {
1582 elements.addElement(selector.substring
1583 (poundIndex + 1, spaceIndex));
1584 }
1585 }
1586 dotIndex = poundIndex = spaceIndex + 1;
1587 }
1588 else if (dotIndex != -1 && dotIndex < spaceIndex) {
1589 // .
1590 if (dotIndex == lastIndex) {
1591 elements.addElement("");
1592 }
1593 else {
1594 elements.addElement(selector.substring(lastIndex,
1595 dotIndex));
1596 }
1597 if ((dotIndex + 1) == spaceIndex) {
1598 elements.addElement(null);
1599 }
1600 else {
1601 elements.addElement(selector.substring(dotIndex + 1,
1602 spaceIndex));
1603 }
1604 elements.addElement(null);
1605 dotIndex = spaceIndex + 1;
1606 }
1607 else if (poundIndex != -1 && poundIndex < spaceIndex) {
1608 // #
1609 if (poundIndex == lastIndex) {
1610 elements.addElement("");
1611 }
1612 else {
1613 elements.addElement(selector.substring(lastIndex,
1614 poundIndex));
1615 }
1616 elements.addElement(null);
1617 if ((poundIndex + 1) == spaceIndex) {
1618 elements.addElement(null);
1619 }
1620 else {
1621 elements.addElement(selector.substring(poundIndex + 1,
1622 spaceIndex));
1623 }
1624 poundIndex = spaceIndex + 1;
1625 }
1626 else {
1627 // id
1628 elements.addElement(selector.substring(lastIndex,
1629 spaceIndex));
1630 elements.addElement(null);
1631 elements.addElement(null);
1632 }
1633 lastIndex = spaceIndex + 1;
1634 }
1635 // Create the tag, id, and class arrays.
1636 int total = elements.size();
1637 int numTags = total / 3;
1638 String[] tags = new String[numTags];
1639 String[] ids = new String[numTags];
1640 String[] classes = new String[numTags];
1641 for (int index = 0, eIndex = total - 3; index < numTags;
1642 index++, eIndex -= 3) {
1643 tags[index] = (String)elements.elementAt(eIndex);
1644 classes[index] = (String)elements.elementAt(eIndex + 1);
1645 ids[index] = (String)elements.elementAt(eIndex + 2);
1646 }
1647 return createResolvedStyle(selector, tags, ids, classes);
1648 }
1649 finally {
1650 SearchBuffer.releaseSearchBuffer(sb);
1651 }
1652 }
1653
1654 /**
1655 * Should be invoked when a new rule is added that did not previously
1656 * exist. Goes through and refreshes the necessary resolved
1657 * rules.
1658 */
1659 private synchronized void refreshResolvedRules(String selectorName,
1660 String[] selector,
1661 Style newStyle,
1662 int specificity) {
1663 if (resolvedStyles.size() > 0) {
1664 Enumeration values = resolvedStyles.elements();
1665 while (values.hasMoreElements()) {
1666 ResolvedStyle style = (ResolvedStyle)values.nextElement();
1667 if (style.matches(selectorName)) {
1668 style.insertStyle(newStyle, specificity);
1669 }
1670 }
1671 }
1672 }
1673
1674
1675 /**
1676 * A temporary class used to hold a Vector, a StringBuffer and a
1677 * Hashtable. This is used to avoid allocing a lot of garbage when
1678 * searching for rules. Use the static method obtainSearchBuffer and
1679 * releaseSearchBuffer to get a SearchBuffer, and release it when
1680 * done.
1681 */
1682 private static class SearchBuffer {
1683 /** A stack containing instances of SearchBuffer. Used in getting
1684 * rules. */
1685 static Stack searchBuffers = new Stack();
1686 // A set of temporary variables that can be used in whatever way.
1687 Vector vector = null;
1688 StringBuffer stringBuffer = null;
1689 Hashtable hashtable = null;
1690
1691 /**
1692 * Returns an instance of SearchBuffer. Be sure and issue
1693 * a releaseSearchBuffer when done with it.
1694 */
1695 static SearchBuffer obtainSearchBuffer() {
1696 SearchBuffer sb;
1697 try {
1698 if(!searchBuffers.empty()) {
1699 sb = (SearchBuffer)searchBuffers.pop();
1700 } else {
1701 sb = new SearchBuffer();
1702 }
1703 } catch (EmptyStackException ese) {
1704 sb = new SearchBuffer();
1705 }
1706 return sb;
1707 }
1708
1709 /**
1710 * Adds <code>sb</code> to the stack of SearchBuffers that can
1711 * be used.
1712 */
1713 static void releaseSearchBuffer(SearchBuffer sb) {
1714 sb.empty();
1715 searchBuffers.push(sb);
1716 }
1717
1718 StringBuffer getStringBuffer() {
1719 if (stringBuffer == null) {
1720 stringBuffer = new StringBuffer();
1721 }
1722 return stringBuffer;
1723 }
1724
1725 Vector getVector() {
1726 if (vector == null) {
1727 vector = new Vector();
1728 }
1729 return vector;
1730 }
1731
1732 Hashtable getHashtable() {
1733 if (hashtable == null) {
1734 hashtable = new Hashtable();
1735 }
1736 return hashtable;
1737 }
1738
1739 void empty() {
1740 if (stringBuffer != null) {
1741 stringBuffer.setLength(0);
1742 }
1743 if (vector != null) {
1744 vector.removeAllElements();
1745 }
1746 if (hashtable != null) {
1747 hashtable.clear();
1748 }
1749 }
1750 }
1751
1752
1753 static final Border noBorder = new EmptyBorder(0,0,0,0);
1754
1755 /**
1756 * Class to carry out some of the duties of
1757 * CSS formatting. Implementations of this
1758 * class enable views to present the CSS formatting
1759 * while not knowing anything about how the CSS values
1760 * are being cached.
1761 * <p>
1762 * As a delegate of Views, this object is responsible for
1763 * the insets of a View and making sure the background
1764 * is maintained according to the CSS attributes.
1765 */
1766 public static class BoxPainter implements Serializable {
1767
1768 BoxPainter(AttributeSet a, CSS css, StyleSheet ss) {
1769 this.ss = ss;
1770 this.css = css;
1771 border = getBorder(a);
1772 binsets = border.getBorderInsets(null);
1773 topMargin = getLength(CSS.Attribute.MARGIN_TOP, a);
1774 bottomMargin = getLength(CSS.Attribute.MARGIN_BOTTOM, a);
1775 leftMargin = getLength(CSS.Attribute.MARGIN_LEFT, a);
1776 rightMargin = getLength(CSS.Attribute.MARGIN_RIGHT, a);
1777 bg = ss.getBackground(a);
1778 if (ss.getBackgroundImage(a) != null) {
1779 bgPainter = new BackgroundImagePainter(a, css, ss);
1780 }
1781 }
1782
1783 /**
1784 * Fetches a border to render for the given attributes.
1785 * PENDING(prinz) This is pretty badly hacked at the
1786 * moment.
1787 */
1788 Border getBorder(AttributeSet a) {
1789 return new CSSBorder(a);
1790 }
1791
1792 /**
1793 * Fetches the color to use for borders. This will either be
1794 * the value specified by the border-color attribute (which
1795 * is not inherited), or it will default to the color attribute
1796 * (which is inherited).
1797 */
1798 Color getBorderColor(AttributeSet a) {
1799 Color color = css.getColor(a, CSS.Attribute.BORDER_COLOR);
1800 if (color == null) {
1801 color = css.getColor(a, CSS.Attribute.COLOR);
1802 if (color == null) {
1803 return Color.black;
1804 }
1805 }
1806 return color;
1807 }
1808
1809 /**
1810 * Fetches the inset needed on a given side to
1811 * account for the margin, border, and padding.
1812 *
1813 * @param side The size of the box to fetch the
1814 * inset for. This can be View.TOP,
1815 * View.LEFT, View.BOTTOM, or View.RIGHT.
1816 * @param v the view making the request. This is
1817 * used to get the AttributeSet, and may be used to
1818 * resolve percentage arguments.
1819 * @exception IllegalArgumentException for an invalid direction
1820 */
1821 public float getInset(int side, View v) {
1822 AttributeSet a = v.getAttributes();
1823 float inset = 0;
1824 switch(side) {
1825 case View.LEFT:
1826 inset += getOrientationMargin(HorizontalMargin.LEFT,
1827 leftMargin, a, isLeftToRight(v));
1828 inset += binsets.left;
1829 inset += getLength(CSS.Attribute.PADDING_LEFT, a);
1830 break;
1831 case View.RIGHT:
1832 inset += getOrientationMargin(HorizontalMargin.RIGHT,
1833 rightMargin, a, isLeftToRight(v));
1834 inset += binsets.right;
1835 inset += getLength(CSS.Attribute.PADDING_RIGHT, a);
1836 break;
1837 case View.TOP:
1838 inset += topMargin;
1839 inset += binsets.top;
1840 inset += getLength(CSS.Attribute.PADDING_TOP, a);
1841 break;
1842 case View.BOTTOM:
1843 inset += bottomMargin;
1844 inset += binsets.bottom;
1845 inset += getLength(CSS.Attribute.PADDING_BOTTOM, a);
1846 break;
1847 default:
1848 throw new IllegalArgumentException("Invalid side: " + side);
1849 }
1850 return inset;
1851 }
1852
1853 /**
1854 * Paints the CSS box according to the attributes
1855 * given. This should paint the border, padding,
1856 * and background.
1857 *
1858 * @param g the rendering surface.
1859 * @param x the x coordinate of the allocated area to
1860 * render into.
1861 * @param y the y coordinate of the allocated area to
1862 * render into.
1863 * @param w the width of the allocated area to render into.
1864 * @param h the height of the allocated area to render into.
1865 * @param v the view making the request. This is
1866 * used to get the AttributeSet, and may be used to
1867 * resolve percentage arguments.
1868 */
1869 public void paint(Graphics g, float x, float y, float w, float h, View v) {
1870 // PENDING(prinz) implement real rendering... which would
1871 // do full set of border and background capabilities.
1872 // remove margin
1873
1874 float dx = 0;
1875 float dy = 0;
1876 float dw = 0;
1877 float dh = 0;
1878 AttributeSet a = v.getAttributes();
1879 boolean isLeftToRight = isLeftToRight(v);
1880 float localLeftMargin = getOrientationMargin(HorizontalMargin.LEFT,
1881 leftMargin,
1882 a, isLeftToRight);
1883 float localRightMargin = getOrientationMargin(HorizontalMargin.RIGHT,
1884 rightMargin,
1885 a, isLeftToRight);
1886 if (!(v instanceof HTMLEditorKit.HTMLFactory.BodyBlockView)) {
1887 dx = localLeftMargin;
1888 dy = topMargin;
1889 dw = -(localLeftMargin + localRightMargin);
1890 dh = -(topMargin + bottomMargin);
1891 }
1892 if (bg != null) {
1893 g.setColor(bg);
1894 g.fillRect((int) (x + dx),
1895 (int) (y + dy),
1896 (int) (w + dw),
1897 (int) (h + dh));
1898 }
1899 if (bgPainter != null) {
1900 bgPainter.paint(g, x + dx, y + dy, w + dw, h + dh, v);
1901 }
1902 x += localLeftMargin;
1903 y += topMargin;
1904 w -= localLeftMargin + localRightMargin;
1905 h -= topMargin + bottomMargin;
1906 if (border instanceof BevelBorder) {
1907 //BevelBorder does not support border width
1908 int bw = (int) getLength(CSS.Attribute.BORDER_TOP_WIDTH, a);
1909 for (int i = bw - 1; i >= 0; i--) {
1910 border.paintBorder(null, g, (int) x + i, (int) y + i,
1911 (int) w - 2 * i, (int) h - 2 * i);
1912 }
1913 } else {
1914 border.paintBorder(null, g, (int) x, (int) y, (int) w, (int) h);
1915 }
1916 }
1917
1918 float getLength(CSS.Attribute key, AttributeSet a) {
1919 return css.getLength(a, key, ss);
1920 }
1921
1922 static boolean isLeftToRight(View v) {
1923 boolean ret = true;
1924 if (isOrientationAware(v)) {
1925 Container container = null;
1926 if (v != null && (container = v.getContainer()) != null) {
1927 ret = container.getComponentOrientation().isLeftToRight();
1928 }
1929 }
1930 return ret;
1931 }
1932
1933 /*
1934 * only certain tags are concerned about orientation
1935 * <dir>, <menu>, <ul>, <ol>
1936 * for all others we return true. It is implemented this way
1937 * for performance purposes
1938 */
1939 static boolean isOrientationAware(View v) {
1940 boolean ret = false;
1941 AttributeSet attr = null;
1942 Object obj = null;
1943 if (v != null
1944 && (attr = v.getElement().getAttributes()) != null
1945 && (obj = attr.getAttribute(StyleConstants.NameAttribute)) instanceof HTML.Tag
1946 && (obj == HTML.Tag.DIR
1947 || obj == HTML.Tag.MENU
1948 || obj == HTML.Tag.UL
1949 || obj == HTML.Tag.OL)) {
1950 ret = true;
1951 }
1952
1953 return ret;
1954 }
1955
1956 static enum HorizontalMargin { LEFT, RIGHT };
1957
1958 /**
1959 * for <dir>, <menu>, <ul> etc.
1960 * margins are Left-To-Right/Right-To-Left depended.
1961 * see 5088268 for more details
1962 * margin-(left|right)-(ltr|rtl) were introduced to describe it
1963 * if margin-(left|right) is present we are to use it.
1964 *
1965 * @param side The horizontal side to fetch margin for
1966 * This can be HorizontalMargin.LEFT or HorizontalMargin.RIGHT
1967 * @param cssMargin margin from css
1968 * @param a AttributeSet for the View we getting margin for
1969 * @param isLeftToRight
1970 * @return orientation depended margin
1971 */
1972 float getOrientationMargin(HorizontalMargin side, float cssMargin,
1973 AttributeSet a, boolean isLeftToRight) {
1974 float margin = cssMargin;
1975 float orientationMargin = cssMargin;
1976 Object cssMarginValue = null;
1977 switch (side) {
1978 case RIGHT:
1979 {
1980 orientationMargin = (isLeftToRight) ?
1981 getLength(CSS.Attribute.MARGIN_RIGHT_LTR, a) :
1982 getLength(CSS.Attribute.MARGIN_RIGHT_RTL, a);
1983 cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_RIGHT);
1984 }
1985 break;
1986 case LEFT :
1987 {
1988 orientationMargin = (isLeftToRight) ?
1989 getLength(CSS.Attribute.MARGIN_LEFT_LTR, a) :
1990 getLength(CSS.Attribute.MARGIN_LEFT_RTL, a);
1991 cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_LEFT);
1992 }
1993 break;
1994 }
1995
1996 if (cssMarginValue == null
1997 && orientationMargin != Integer.MIN_VALUE) {
1998 margin = orientationMargin;
1999 }
2000 return margin;
2001 }
2002
2003 float topMargin;
2004 float bottomMargin;
2005 float leftMargin;
2006 float rightMargin;
2007 // Bitmask, used to indicate what margins are relative:
2008 // bit 0 for top, 1 for bottom, 2 for left and 3 for right.
2009 short marginFlags;
2010 Border border;
2011 Insets binsets;
2012 CSS css;
2013 StyleSheet ss;
2014 Color bg;
2015 BackgroundImagePainter bgPainter;
2016 }
2017
2018 /**
2019 * Class to carry out some of the duties of CSS list
2020 * formatting. Implementations of this
2021 * class enable views to present the CSS formatting
2022 * while not knowing anything about how the CSS values
2023 * are being cached.
2024 */
2025 public static class ListPainter implements Serializable {
2026
2027 ListPainter(AttributeSet attr, StyleSheet ss) {
2028 this.ss = ss;
2029 /* Get the image to use as a list bullet */
2030 String imgstr = (String)attr.getAttribute(CSS.Attribute.
2031 LIST_STYLE_IMAGE);
2032 type = null;
2033 if (imgstr != null && !imgstr.equals("none")) {
2034 String tmpstr = null;
2035 try {
2036 StringTokenizer st = new StringTokenizer(imgstr, "()");
2037 if (st.hasMoreTokens())
2038 tmpstr = st.nextToken();
2039 if (st.hasMoreTokens())
2040 tmpstr = st.nextToken();
2041 URL u = new URL(tmpstr);
2042 img = new ImageIcon(u);
2043 } catch (MalformedURLException e) {
2044 if (tmpstr != null && ss != null && ss.getBase() != null) {
2045 try {
2046 URL u = new URL(ss.getBase(), tmpstr);
2047 img = new ImageIcon(u);
2048 } catch (MalformedURLException murle) {
2049 img = null;
2050 }
2051 }
2052 else {
2053 img = null;
2054 }
2055 }
2056 }
2057
2058 /* Get the type of bullet to use in the list */
2059 if (img == null) {
2060 type = (CSS.Value)attr.getAttribute(CSS.Attribute.
2061 LIST_STYLE_TYPE);
2062 }
2063 start = 1;
2064
2065 paintRect = new Rectangle();
2066 }
2067
2068 /**
2069 * Returns a string that represents the value
2070 * of the HTML.Attribute.TYPE attribute.
2071 * If this attributes is not defined, then
2072 * then the type defaults to "disc" unless
2073 * the tag is on Ordered list. In the case
2074 * of the latter, the default type is "decimal".
2075 */
2076 private CSS.Value getChildType(View childView) {
2077 CSS.Value childtype = (CSS.Value)childView.getAttributes().
2078 getAttribute(CSS.Attribute.LIST_STYLE_TYPE);
2079
2080 if (childtype == null) {
2081 if (type == null) {
2082 // Parent view.
2083 View v = childView.getParent();
2084 HTMLDocument doc = (HTMLDocument)v.getDocument();
2085 if (doc.matchNameAttribute(v.getElement().getAttributes(),
2086 HTML.Tag.OL)) {
2087 childtype = CSS.Value.DECIMAL;
2088 } else {
2089 childtype = CSS.Value.DISC;
2090 }
2091 } else {
2092 childtype = type;
2093 }
2094 }
2095 return childtype;
2096 }
2097
2098 /**
2099 * Obtains the starting index from <code>parent</code>.
2100 */
2101 private void getStart(View parent) {
2102 checkedForStart = true;
2103 Element element = parent.getElement();
2104 if (element != null) {
2105 AttributeSet attr = element.getAttributes();
2106 Object startValue;
2107 if (attr != null && attr.isDefined(HTML.Attribute.START) &&
2108 (startValue = attr.getAttribute
2109 (HTML.Attribute.START)) != null &&
2110 (startValue instanceof String)) {
2111
2112 try {
2113 start = Integer.parseInt((String)startValue);
2114 }
2115 catch (NumberFormatException nfe) {}
2116 }
2117 }
2118 }
2119
2120 /**
2121 * Returns an integer that should be used to render the child at
2122 * <code>childIndex</code> with. The retValue will usually be
2123 * <code>childIndex</code> + 1, unless <code>parentView</code>
2124 * has some Views that do not represent LI's, or one of the views
2125 * has a HTML.Attribute.START specified.
2126 */
2127 private int getRenderIndex(View parentView, int childIndex) {
2128 if (!checkedForStart) {
2129 getStart(parentView);
2130 }
2131 int retIndex = childIndex;
2132 for (int counter = childIndex; counter >= 0; counter--) {
2133 AttributeSet as = parentView.getElement().getElement(counter).
2134 getAttributes();
2135 if (as.getAttribute(StyleConstants.NameAttribute) !=
2136 HTML.Tag.LI) {
2137 retIndex--;
2138 } else if (as.isDefined(HTML.Attribute.VALUE)) {
2139 Object value = as.getAttribute(HTML.Attribute.VALUE);
2140 if (value != null &&
2141 (value instanceof String)) {
2142 try {
2143 int iValue = Integer.parseInt((String)value);
2144 return retIndex - counter + iValue;
2145 }
2146 catch (NumberFormatException nfe) {}
2147 }
2148 }
2149 }
2150 return retIndex + start;
2151 }
2152
2153 /**
2154 * Paints the CSS list decoration according to the
2155 * attributes given.
2156 *
2157 * @param g the rendering surface.
2158 * @param x the x coordinate of the list item allocation
2159 * @param y the y coordinate of the list item allocation
2160 * @param w the width of the list item allocation
2161 * @param h the height of the list item allocation
2162 * @param v the allocated area to paint into.
2163 * @param item which list item is being painted. This
2164 * is a number greater than or equal to 0.
2165 */
2166 public void paint(Graphics g, float x, float y, float w, float h, View v, int item) {
2167 View cv = v.getView(item);
2168 Object name = cv.getElement().getAttributes().getAttribute
2169 (StyleConstants.NameAttribute);
2170 // Only draw something if the View is a list item. This won't
2171 // be the case for comments.
2172 if (!(name instanceof HTML.Tag) ||
2173 name != HTML.Tag.LI) {
2174 return;
2175 }
2176 // deside on what side draw bullets, etc.
2177 isLeftToRight =
2178 cv.getContainer().getComponentOrientation().isLeftToRight();
2179
2180 // How the list indicator is aligned is not specified, it is
2181 // left up to the UA. IE and NS differ on this behavior.
2182 // This is closer to NS where we align to the first line of text.
2183 // If the child is not text we draw the indicator at the
2184 // origin (0).
2185 float align = 0;
2186 if (cv.getViewCount() > 0) {
2187 View pView = cv.getView(0);
2188 Object cName = pView.getElement().getAttributes().
2189 getAttribute(StyleConstants.NameAttribute);
2190 if ((cName == HTML.Tag.P || cName == HTML.Tag.IMPLIED) &&
2191 pView.getViewCount() > 0) {
2192 paintRect.setBounds((int)x, (int)y, (int)w, (int)h);
2193 Shape shape = cv.getChildAllocation(0, paintRect);
2194 if (shape != null && (shape = pView.getView(0).
2195 getChildAllocation(0, shape)) != null) {
2196 Rectangle rect = (shape instanceof Rectangle) ?
2197 (Rectangle)shape : shape.getBounds();
2198
2199 align = pView.getView(0).getAlignment(View.Y_AXIS);
2200 y = rect.y;
2201 h = rect.height;
2202 }
2203 }
2204 }
2205
2206 // set the color of a decoration
2207 if (ss != null) {
2208 g.setColor(ss.getForeground(cv.getAttributes()));
2209 } else {
2210 g.setColor(Color.black);
2211 }
2212
2213 if (img != null) {
2214 drawIcon(g, (int) x, (int) y, (int) w, (int) h, align,
2215 v.getContainer());
2216 return;
2217 }
2218 CSS.Value childtype = getChildType(cv);
2219 Font font = ((StyledDocument)cv.getDocument()).
2220 getFont(cv.getAttributes());
2221 if (font != null) {
2222 g.setFont(font);
2223 }
2224 if (childtype == CSS.Value.SQUARE || childtype == CSS.Value.CIRCLE
2225 || childtype == CSS.Value.DISC) {
2226 drawShape(g, childtype, (int) x, (int) y,
2227 (int) w, (int) h, align);
2228 } else if (childtype == CSS.Value.DECIMAL) {
2229 drawLetter(g, '1', (int) x, (int) y, (int) w, (int) h, align,
2230 getRenderIndex(v, item));
2231 } else if (childtype == CSS.Value.LOWER_ALPHA) {
2232 drawLetter(g, 'a', (int) x, (int) y, (int) w, (int) h, align,
2233 getRenderIndex(v, item));
2234 } else if (childtype == CSS.Value.UPPER_ALPHA) {
2235 drawLetter(g, 'A', (int) x, (int) y, (int) w, (int) h, align,
2236 getRenderIndex(v, item));
2237 } else if (childtype == CSS.Value.LOWER_ROMAN) {
2238 drawLetter(g, 'i', (int) x, (int) y, (int) w, (int) h, align,
2239 getRenderIndex(v, item));
2240 } else if (childtype == CSS.Value.UPPER_ROMAN) {
2241 drawLetter(g, 'I', (int) x, (int) y, (int) w, (int) h, align,
2242 getRenderIndex(v, item));
2243 }
2244 }
2245
2246 /**
2247 * Draws the bullet icon specified by the list-style-image argument.
2248 *
2249 * @param g the graphics context
2250 * @param ax x coordinate to place the bullet
2251 * @param ay y coordinate to place the bullet
2252 * @param aw width of the container the bullet is placed in
2253 * @param ah height of the container the bullet is placed in
2254 * @param align preferred alignment factor for the child view
2255 */
2256 void drawIcon(Graphics g, int ax, int ay, int aw, int ah,
2257 float align, Component c) {
2258 // Align to bottom of icon.
2259 int gap = isLeftToRight ? - (img.getIconWidth() + bulletgap) :
2260 (aw + bulletgap);
2261 int x = ax + gap;
2262 int y = Math.max(ay, ay + (int)(align * ah) -img.getIconHeight());
2263
2264 img.paintIcon(c, g, x, y);
2265 }
2266
2267 /**
2268 * Draws the graphical bullet item specified by the type argument.
2269 *
2270 * @param g the graphics context
2271 * @param type type of bullet to draw (circle, square, disc)
2272 * @param ax x coordinate to place the bullet
2273 * @param ay y coordinate to place the bullet
2274 * @param aw width of the container the bullet is placed in
2275 * @param ah height of the container the bullet is placed in
2276 * @param align preferred alignment factor for the child view
2277 */
2278 void drawShape(Graphics g, CSS.Value type, int ax, int ay, int aw,
2279 int ah, float align) {
2280 // Align to bottom of shape.
2281 int gap = isLeftToRight ? - (bulletgap + 8) : (aw + bulletgap);
2282 int x = ax + gap;
2283 int y = Math.max(ay, ay + (int)(align * ah) - 8);
2284
2285 if (type == CSS.Value.SQUARE) {
2286 g.drawRect(x, y, 8, 8);
2287 } else if (type == CSS.Value.CIRCLE) {
2288 g.drawOval(x, y, 8, 8);
2289 } else {
2290 g.fillOval(x, y, 8, 8);
2291 }
2292 }
2293
2294 /**
2295 * Draws the letter or number for an ordered list.
2296 *
2297 * @param g the graphics context
2298 * @param letter type of ordered list to draw
2299 * @param ax x coordinate to place the bullet
2300 * @param ay y coordinate to place the bullet
2301 * @param aw width of the container the bullet is placed in
2302 * @param ah height of the container the bullet is placed in
2303 * @param index position of the list item in the list
2304 */
2305 void drawLetter(Graphics g, char letter, int ax, int ay, int aw,
2306 int ah, float align, int index) {
2307 String str = formatItemNum(index, letter);
2308 str = isLeftToRight ? str + "." : "." + str;
2309 FontMetrics fm = SwingUtilities2.getFontMetrics(null, g);
2310 int stringwidth = SwingUtilities2.stringWidth(null, fm, str);
2311 int gap = isLeftToRight ? - (stringwidth + bulletgap) :
2312 (aw + bulletgap);
2313 int x = ax + gap;
2314 int y = Math.max(ay + fm.getAscent(), ay + (int)(ah * align));
2315 SwingUtilities2.drawString(null, g, str, x, y);
2316 }
2317
2318 /**
2319 * Converts the item number into the ordered list number
2320 * (i.e. 1 2 3, i ii iii, a b c, etc.
2321 *
2322 * @param itemNum number to format
2323 * @param type type of ordered list
2324 */
2325 String formatItemNum(int itemNum, char type) {
2326 String numStyle = "1";
2327
2328 boolean uppercase = false;
2329
2330 String formattedNum;
2331
2332 switch (type) {
2333 case '1':
2334 default:
2335 formattedNum = String.valueOf(itemNum);
2336 break;
2337
2338 case 'A':
2339 uppercase = true;
2340 // fall through
2341 case 'a':
2342 formattedNum = formatAlphaNumerals(itemNum);
2343 break;
2344
2345 case 'I':
2346 uppercase = true;
2347 // fall through
2348 case 'i':
2349 formattedNum = formatRomanNumerals(itemNum);
2350 }
2351
2352