1. /*
  2. * @(#)StyleSheet.java 1.84 04/07/23
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.text.html;
  8. import com.sun.java.swing.SwingUtilities2;
  9. import java.util.*;
  10. import java.awt.*;
  11. import java.io.*;
  12. import java.net.*;
  13. import javax.swing.Icon;
  14. import javax.swing.ImageIcon;
  15. import javax.swing.border.*;
  16. import javax.swing.event.ChangeListener;
  17. import javax.swing.text.*;
  18. /**
  19. * Support for defining the visual characteristics of
  20. * HTML views being rendered. The StyleSheet is used to
  21. * translate the HTML model into visual characteristics.
  22. * This enables views to be customized by a look-and-feel,
  23. * multiple views over the same model can be rendered
  24. * differently, etc. This can be thought of as a CSS
  25. * rule repository. The key for CSS attributes is an
  26. * object of type CSS.Attribute. The type of the value
  27. * is up to the StyleSheet implementation, but the
  28. * <code>toString</code> method is required
  29. * to return a string representation of CSS value.
  30. * <p>
  31. * The primary entry point for HTML View implementations
  32. * to get their attributes is the
  33. * <a href="#getViewAttributes">getViewAttributes</a>
  34. * method. This should be implemented to establish the
  35. * desired policy used to associate attributes with the view.
  36. * Each HTMLEditorKit (i.e. and therefore each associated
  37. * JEditorPane) can have its own StyleSheet, but by default one
  38. * sheet will be shared by all of the HTMLEditorKit instances.
  39. * HTMLDocument instance can also have a StyleSheet, which
  40. * holds the document-specific CSS specifications.
  41. * <p>
  42. * In order for Views to store less state and therefore be
  43. * more lightweight, the StyleSheet can act as a factory for
  44. * painters that handle some of the rendering tasks. This allows
  45. * implementations to determine what they want to cache
  46. * and have the sharing potentially at the level that a
  47. * selector is common to multiple views. Since the StyleSheet
  48. * may be used by views over multiple documents and typically
  49. * the HTML attributes don't effect the selector being used,
  50. * the potential for sharing is significant.
  51. * <p>
  52. * The rules are stored as named styles, and other information
  53. * is stored to translate the context of an element to a
  54. * rule quickly. The following code fragment will display
  55. * the named styles, and therefore the CSS rules contained.
  56. * <code><pre>
  57. *  
  58. *   import java.util.*;
  59. *   import javax.swing.text.*;
  60. *   import javax.swing.text.html.*;
  61. *  
  62. *   public class ShowStyles {
  63. *  
  64. *   public static void main(String[] args) {
  65. *   HTMLEditorKit kit = new HTMLEditorKit();
  66. *   HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
  67. *   StyleSheet styles = doc.getStyleSheet();
  68. *  
  69. *   Enumeration rules = styles.getStyleNames();
  70. *   while (rules.hasMoreElements()) {
  71. *   String name = (String) rules.nextElement();
  72. *   Style rule = styles.getStyle(name);
  73. *   System.out.println(rule.toString());
  74. *   }
  75. *   System.exit(0);
  76. *   }
  77. *   }
  78. *  
  79. * </pre></code>
  80. * <p>
  81. * The semantics for when a CSS style should overide visual attributes
  82. * defined by an element are not well defined. For example, the html
  83. * <code><body bgcolor=red></code> makes the body have a red
  84. * background. But if the html file also contains the CSS rule
  85. * <code>body { background: blue }</code> it becomes less clear as to
  86. * what color the background of the body should be. The current
  87. * implemention gives visual attributes defined in the element the
  88. * highest precedence, that is they are always checked before any styles.
  89. * Therefore, in the previous example the background would have a
  90. * red color as the body element defines the background color to be red.
  91. * <p>
  92. * As already mentioned this supports CSS. We don't support the full CSS
  93. * spec. Refer to the javadoc of the CSS class to see what properties
  94. * we support. The two major CSS parsing related
  95. * concepts we do not currently
  96. * support are pseudo selectors, such as <code>A:link { color: red }</code>,
  97. * and the <code>important</code> modifier.
  98. * <p>
  99. * <font color="red">Note: This implementation is currently
  100. * incomplete. It can be replaced with alternative implementations
  101. * that are complete. Future versions of this class will provide
  102. * better CSS support.</font>
  103. *
  104. * @author Timothy Prinzing
  105. * @author Sunita Mani
  106. * @author Sara Swanson
  107. * @author Jill Nakata
  108. * @version 1.84 07/23/04
  109. */
  110. public class StyleSheet extends StyleContext {
  111. // As the javadoc states, this class maintains a mapping between
  112. // a CSS selector (such as p.bar) and a Style.
  113. // This consists of a number of parts:
  114. // . Each selector is broken down into its constituent simple selectors,
  115. // and stored in an inverted graph, for example:
  116. // p { color: red } ol p { font-size: 10pt } ul p { font-size: 12pt }
  117. // results in the graph:
  118. // root
  119. // |
  120. // p
  121. // / \
  122. // ol ul
  123. // each node (an instance of SelectorMapping) has an associated
  124. // specificity and potentially a Style.
  125. // . Every rule that is asked for (either by way of getRule(String) or
  126. // getRule(HTML.Tag, Element)) results in a unique instance of
  127. // ResolvedStyle. ResolvedStyles contain the AttributeSets from the
  128. // SelectorMapping.
  129. // . When a new rule is created it is inserted into the graph, and
  130. // the AttributeSets of each ResolvedStyles are updated appropriately.
  131. // . This class creates special AttributeSets, LargeConversionSet and
  132. // SmallConversionSet, that maintain a mapping between StyleConstants
  133. // and CSS so that developers that wish to use the StyleConstants
  134. // methods can do so.
  135. // . When one of the AttributeSets is mutated by way of a
  136. // StyleConstants key, all the associated CSS keys are removed. This is
  137. // done so that the two representations don't get out of sync. For
  138. // example, if the developer adds StyleConsants.BOLD, FALSE to an
  139. // AttributeSet that contains HTML.Tag.B, the HTML.Tag.B entry will
  140. // be removed.
  141. /**
  142. * Construct a StyleSheet
  143. */
  144. public StyleSheet() {
  145. super();
  146. selectorMapping = new SelectorMapping(0);
  147. resolvedStyles = new Hashtable();
  148. if (css == null) {
  149. css = new CSS();
  150. }
  151. }
  152. /**
  153. * Fetches the style to use to render the given type
  154. * of HTML tag. The element given is representing
  155. * the tag and can be used to determine the nesting
  156. * for situations where the attributes will differ
  157. * if nesting inside of elements.
  158. *
  159. * @param t the type to translate to visual attributes
  160. * @param e the element representing the tag; the element
  161. * can be used to determine the nesting for situations where
  162. * the attributes will differ if nested inside of other
  163. * elements
  164. * @return the set of CSS attributes to use to render
  165. * the tag
  166. */
  167. public Style getRule(HTML.Tag t, Element e) {
  168. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  169. try {
  170. // Build an array of all the parent elements.
  171. Vector searchContext = sb.getVector();
  172. for (Element p = e; p != null; p = p.getParentElement()) {
  173. searchContext.addElement(p);
  174. }
  175. // Build a fully qualified selector.
  176. int n = searchContext.size();
  177. StringBuffer cacheLookup = sb.getStringBuffer();
  178. AttributeSet attr;
  179. String eName;
  180. Object name;
  181. // >= 1 as the HTML.Tag for the 0th element is passed in.
  182. for (int counter = n - 1; counter >= 1; counter--) {
  183. e = (Element)searchContext.elementAt(counter);
  184. attr = e.getAttributes();
  185. name = attr.getAttribute(StyleConstants.NameAttribute);
  186. eName = name.toString();
  187. cacheLookup.append(eName);
  188. if (attr != null) {
  189. if (attr.isDefined(HTML.Attribute.ID)) {
  190. cacheLookup.append('#');
  191. cacheLookup.append(attr.getAttribute
  192. (HTML.Attribute.ID));
  193. }
  194. else if (attr.isDefined(HTML.Attribute.CLASS)) {
  195. cacheLookup.append('.');
  196. cacheLookup.append(attr.getAttribute
  197. (HTML.Attribute.CLASS));
  198. }
  199. }
  200. cacheLookup.append(' ');
  201. }
  202. cacheLookup.append(t.toString());
  203. e = (Element)searchContext.elementAt(0);
  204. attr = e.getAttributes();
  205. if (e.isLeaf()) {
  206. // For leafs, we use the second tier attributes.
  207. Object testAttr = attr.getAttribute(t);
  208. if (testAttr instanceof AttributeSet) {
  209. attr = (AttributeSet)testAttr;
  210. }
  211. else {
  212. attr = null;
  213. }
  214. }
  215. if (attr != null) {
  216. if (attr.isDefined(HTML.Attribute.ID)) {
  217. cacheLookup.append('#');
  218. cacheLookup.append(attr.getAttribute(HTML.Attribute.ID));
  219. }
  220. else if (attr.isDefined(HTML.Attribute.CLASS)) {
  221. cacheLookup.append('.');
  222. cacheLookup.append(attr.getAttribute
  223. (HTML.Attribute.CLASS));
  224. }
  225. }
  226. Style style = getResolvedStyle(cacheLookup.toString(),
  227. searchContext, t);
  228. return style;
  229. }
  230. finally {
  231. SearchBuffer.releaseSearchBuffer(sb);
  232. }
  233. }
  234. /**
  235. * Fetches the rule that best matches the selector given
  236. * in string form. Where <code>selector</code> is a space separated
  237. * String of the element names. For example, <code>selector</code>
  238. * might be 'html body tr td''<p>
  239. * The attributes of the returned Style will change
  240. * as rules are added and removed. That is if you to ask for a rule
  241. * with a selector "table p" and a new rule was added with a selector
  242. * of "p" the returned Style would include the new attributes from
  243. * the rule "p".
  244. */
  245. public Style getRule(String selector) {
  246. selector = cleanSelectorString(selector);
  247. if (selector != null) {
  248. Style style = getResolvedStyle(selector);
  249. return style;
  250. }
  251. return null;
  252. }
  253. /**
  254. * Adds a set of rules to the sheet. The rules are expected to
  255. * be in valid CSS format. Typically this would be called as
  256. * a result of parsing a <style> tag.
  257. */
  258. public void addRule(String rule) {
  259. if (rule != null) {
  260. //tweaks to control display properties
  261. //see BasicEditorPaneUI
  262. final String baseUnitsDisable = "BASE_SIZE_DISABLE";
  263. final String baseUnits = "BASE_SIZE ";
  264. final String w3cLengthUnitsEnable = "W3C_LENGTH_UNITS_ENABLE";
  265. final String w3cLengthUnitsDisable = "W3C_LENGTH_UNITS_DISABLE";
  266. if (rule == baseUnitsDisable) {
  267. sizeMap = sizeMapDefault;
  268. } else if (rule.startsWith(baseUnits)) {
  269. rebaseSizeMap(Integer.
  270. parseInt(rule.substring(baseUnits.length())));
  271. } else if (rule == w3cLengthUnitsEnable) {
  272. w3cLengthUnits = true;
  273. } else if (rule == w3cLengthUnitsDisable) {
  274. w3cLengthUnits = false;
  275. } else {
  276. CssParser parser = new CssParser();
  277. try {
  278. parser.parse(getBase(), new StringReader(rule), false, false);
  279. } catch (IOException ioe) { }
  280. }
  281. }
  282. }
  283. /**
  284. * Translates a CSS declaration to an AttributeSet that represents
  285. * the CSS declaration. Typically this would be called as a
  286. * result of encountering an HTML style attribute.
  287. */
  288. public AttributeSet getDeclaration(String decl) {
  289. if (decl == null) {
  290. return SimpleAttributeSet.EMPTY;
  291. }
  292. CssParser parser = new CssParser();
  293. return parser.parseDeclaration(decl);
  294. }
  295. /**
  296. * Loads a set of rules that have been specified in terms of
  297. * CSS1 grammar. If there are collisions with existing rules,
  298. * the newly specified rule will win.
  299. *
  300. * @param in the stream to read the CSS grammar from
  301. * @param ref the reference URL. This value represents the
  302. * location of the stream and may be null. All relative
  303. * URLs specified in the stream will be based upon this
  304. * parameter.
  305. */
  306. public void loadRules(Reader in, URL ref) throws IOException {
  307. CssParser parser = new CssParser();
  308. parser.parse(ref, in, false, false);
  309. }
  310. /**
  311. * Fetches a set of attributes to use in the view for
  312. * displaying. This is basically a set of attributes that
  313. * can be used for View.getAttributes.
  314. */
  315. public AttributeSet getViewAttributes(View v) {
  316. return new ViewAttributeSet(v);
  317. }
  318. /**
  319. * Removes a named style previously added to the document.
  320. *
  321. * @param nm the name of the style to remove
  322. */
  323. public void removeStyle(String nm) {
  324. Style aStyle = getStyle(nm);
  325. if (aStyle != null) {
  326. String selector = cleanSelectorString(nm);
  327. String[] selectors = getSimpleSelectors(selector);
  328. synchronized(this) {
  329. SelectorMapping mapping = getRootSelectorMapping();
  330. for (int i = selectors.length - 1; i >= 0; i--) {
  331. mapping = mapping.getChildSelectorMapping(selectors[i],
  332. true);
  333. }
  334. Style rule = mapping.getStyle();
  335. if (rule != null) {
  336. mapping.setStyle(null);
  337. if (resolvedStyles.size() > 0) {
  338. Enumeration values = resolvedStyles.elements();
  339. while (values.hasMoreElements()) {
  340. ResolvedStyle style = (ResolvedStyle)values.
  341. nextElement();
  342. style.removeStyle(rule);
  343. }
  344. }
  345. }
  346. }
  347. }
  348. super.removeStyle(nm);
  349. }
  350. /**
  351. * Adds the rules from the StyleSheet <code>ss</code> to those of
  352. * the receiver. <code>ss's</code> rules will override the rules of
  353. * any previously added style sheets. An added StyleSheet will never
  354. * override the rules of the receiving style sheet.
  355. *
  356. * @since 1.3
  357. */
  358. public void addStyleSheet(StyleSheet ss) {
  359. synchronized(this) {
  360. if (linkedStyleSheets == null) {
  361. linkedStyleSheets = new Vector();
  362. }
  363. if (!linkedStyleSheets.contains(ss)) {
  364. int index = 0;
  365. if (ss instanceof javax.swing.plaf.UIResource
  366. && linkedStyleSheets.size() > 1) {
  367. index = linkedStyleSheets.size() - 1;
  368. }
  369. linkedStyleSheets.insertElementAt(ss, index);
  370. linkStyleSheetAt(ss, index);
  371. }
  372. }
  373. }
  374. /**
  375. * Removes the StyleSheet <code>ss</code> from those of the receiver.
  376. *
  377. * @since 1.3
  378. */
  379. public void removeStyleSheet(StyleSheet ss) {
  380. synchronized(this) {
  381. if (linkedStyleSheets != null) {
  382. int index = linkedStyleSheets.indexOf(ss);
  383. if (index != -1) {
  384. linkedStyleSheets.removeElementAt(index);
  385. unlinkStyleSheet(ss, index);
  386. if (index == 0 && linkedStyleSheets.size() == 0) {
  387. linkedStyleSheets = null;
  388. }
  389. }
  390. }
  391. }
  392. }
  393. //
  394. // The following is used to import style sheets.
  395. //
  396. /**
  397. * Returns an array of the linked StyleSheets. Will return null
  398. * if there are no linked StyleSheets.
  399. *
  400. * @since 1.3
  401. */
  402. public StyleSheet[] getStyleSheets() {
  403. StyleSheet[] retValue;
  404. synchronized(this) {
  405. if (linkedStyleSheets != null) {
  406. retValue = new StyleSheet[linkedStyleSheets.size()];
  407. linkedStyleSheets.copyInto(retValue);
  408. }
  409. else {
  410. retValue = null;
  411. }
  412. }
  413. return retValue;
  414. }
  415. /**
  416. * Imports a style sheet from <code>url</code>. The resulting rules
  417. * are directly added to the receiver. If you do not want the rules
  418. * to become part of the receiver, create a new StyleSheet and use
  419. * addStyleSheet to link it in.
  420. *
  421. * @since 1.3
  422. */
  423. public void importStyleSheet(URL url) {
  424. try {
  425. InputStream is;
  426. is = url.openStream();
  427. Reader r = new BufferedReader(new InputStreamReader(is));
  428. CssParser parser = new CssParser();
  429. parser.parse(url, r, false, true);
  430. r.close();
  431. is.close();
  432. } catch (Throwable e) {
  433. // on error we simply have no styles... the html
  434. // will look mighty wrong but still function.
  435. }
  436. }
  437. /**
  438. * Sets the base. All import statements that are relative, will be
  439. * relative to <code>base</code>.
  440. *
  441. * @since 1.3
  442. */
  443. public void setBase(URL base) {
  444. this.base = base;
  445. }
  446. /**
  447. * Returns the base.
  448. *
  449. * @since 1.3
  450. */
  451. public URL getBase() {
  452. return base;
  453. }
  454. /**
  455. * Adds a CSS attribute to the given set.
  456. *
  457. * @since 1.3
  458. */
  459. public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key,
  460. String value) {
  461. css.addInternalCSSValue(attr, key, value);
  462. }
  463. /**
  464. * Adds a CSS attribute to the given set.
  465. *
  466. * @since 1.3
  467. */
  468. public boolean addCSSAttributeFromHTML(MutableAttributeSet attr,
  469. CSS.Attribute key, String value) {
  470. Object iValue = css.getCssValue(key, value);
  471. if (iValue != null) {
  472. attr.addAttribute(key, iValue);
  473. return true;
  474. }
  475. return false;
  476. }
  477. // ---- Conversion functionality ---------------------------------
  478. /**
  479. * Converts a set of HTML attributes to an equivalent
  480. * set of CSS attributes.
  481. *
  482. * @param htmlAttrSet AttributeSet containing the HTML attributes.
  483. */
  484. public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) {
  485. AttributeSet cssAttrSet = css.translateHTMLToCSS(htmlAttrSet);
  486. MutableAttributeSet cssStyleSet = addStyle(null, null);
  487. cssStyleSet.addAttributes(cssAttrSet);
  488. return cssStyleSet;
  489. }
  490. /**
  491. * Adds an attribute to the given set, and returns
  492. * the new representative set. This is reimplemented to
  493. * convert StyleConstant attributes to CSS prior to forwarding
  494. * to the superclass behavior. The StyleConstants attribute
  495. * has no corresponding CSS entry, the StyleConstants attribute
  496. * is stored (but will likely be unused).
  497. *
  498. * @param old the old attribute set
  499. * @param key the non-null attribute key
  500. * @param value the attribute value
  501. * @return the updated attribute set
  502. * @see MutableAttributeSet#addAttribute
  503. */
  504. public AttributeSet addAttribute(AttributeSet old, Object key,
  505. Object value) {
  506. if (css == null) {
  507. // supers constructor will call this before returning,
  508. // and we need to make sure CSS is non null.
  509. css = new CSS();
  510. }
  511. if (key instanceof StyleConstants) {
  512. HTML.Tag tag = HTML.getTagForStyleConstantsKey(
  513. (StyleConstants)key);
  514. if (tag != null && old.isDefined(tag)) {
  515. old = removeAttribute(old, tag);
  516. }
  517. Object cssValue = css.styleConstantsValueToCSSValue
  518. ((StyleConstants)key, value);
  519. if (cssValue != null) {
  520. Object cssKey = css.styleConstantsKeyToCSSKey
  521. ((StyleConstants)key);
  522. if (cssKey != null) {
  523. return super.addAttribute(old, cssKey, cssValue);
  524. }
  525. }
  526. }
  527. return super.addAttribute(old, key, value);
  528. }
  529. /**
  530. * Adds a set of attributes to the element. If any of these attributes
  531. * are StyleConstants attributes, they will be converted to CSS prior
  532. * to forwarding to the superclass behavior.
  533. *
  534. * @param old the old attribute set
  535. * @param attr the attributes to add
  536. * @return the updated attribute set
  537. * @see MutableAttributeSet#addAttribute
  538. */
  539. public AttributeSet addAttributes(AttributeSet old, AttributeSet attr) {
  540. if (!(attr instanceof HTMLDocument.TaggedAttributeSet)) {
  541. old = removeHTMLTags(old, attr);
  542. }
  543. return super.addAttributes(old, convertAttributeSet(attr));
  544. }
  545. /**
  546. * Removes an attribute from the set. If the attribute is a StyleConstants
  547. * attribute, the request will be converted to a CSS attribute prior to
  548. * forwarding to the superclass behavior.
  549. *
  550. * @param old the old set of attributes
  551. * @param key the non-null attribute name
  552. * @return the updated attribute set
  553. * @see MutableAttributeSet#removeAttribute
  554. */
  555. public AttributeSet removeAttribute(AttributeSet old, Object key) {
  556. if (key instanceof StyleConstants) {
  557. HTML.Tag tag = HTML.getTagForStyleConstantsKey(
  558. (StyleConstants)key);
  559. if (tag != null) {
  560. old = super.removeAttribute(old, tag);
  561. }
  562. Object cssKey = css.styleConstantsKeyToCSSKey((StyleConstants)key);
  563. if (cssKey != null) {
  564. return super.removeAttribute(old, cssKey);
  565. }
  566. }
  567. return super.removeAttribute(old, key);
  568. }
  569. /**
  570. * Removes a set of attributes for the element. If any of the attributes
  571. * is a StyleConstants attribute, the request will be converted to a CSS
  572. * attribute prior to forwarding to the superclass behavior.
  573. *
  574. * @param old the old attribute set
  575. * @param names the attribute names
  576. * @return the updated attribute set
  577. * @see MutableAttributeSet#removeAttributes
  578. */
  579. public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) {
  580. // PENDING: Should really be doing something similar to
  581. // removeHTMLTags here, but it is rather expensive to have to
  582. // clone names
  583. return super.removeAttributes(old, names);
  584. }
  585. /**
  586. * Removes a set of attributes. If any of the attributes
  587. * is a StyleConstants attribute, the request will be converted to a CSS
  588. * attribute prior to forwarding to the superclass behavior.
  589. *
  590. * @param old the old attribute set
  591. * @param attrs the attributes
  592. * @return the updated attribute set
  593. * @see MutableAttributeSet#removeAttributes
  594. */
  595. public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs) {
  596. if (old != attrs) {
  597. old = removeHTMLTags(old, attrs);
  598. }
  599. return super.removeAttributes(old, convertAttributeSet(attrs));
  600. }
  601. /**
  602. * Creates a compact set of attributes that might be shared.
  603. * This is a hook for subclasses that want to alter the
  604. * behavior of SmallAttributeSet. This can be reimplemented
  605. * to return an AttributeSet that provides some sort of
  606. * attribute conversion.
  607. *
  608. * @param a The set of attributes to be represented in the
  609. * the compact form.
  610. */
  611. protected SmallAttributeSet createSmallAttributeSet(AttributeSet a) {
  612. return new SmallConversionSet(a);
  613. }
  614. /**
  615. * Creates a large set of attributes that should trade off
  616. * space for time. This set will not be shared. This is
  617. * a hook for subclasses that want to alter the behavior
  618. * of the larger attribute storage format (which is
  619. * SimpleAttributeSet by default). This can be reimplemented
  620. * to return a MutableAttributeSet that provides some sort of
  621. * attribute conversion.
  622. *
  623. * @param a The set of attributes to be represented in the
  624. * the larger form.
  625. */
  626. protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) {
  627. return new LargeConversionSet(a);
  628. }
  629. /**
  630. * For any StyleConstants key in attr that has an associated HTML.Tag,
  631. * it is removed from old. The resulting AttributeSet is then returned.
  632. */
  633. private AttributeSet removeHTMLTags(AttributeSet old, AttributeSet attr) {
  634. if (!(attr instanceof LargeConversionSet) &&
  635. !(attr instanceof SmallConversionSet)) {
  636. Enumeration names = attr.getAttributeNames();
  637. while (names.hasMoreElements()) {
  638. Object key = names.nextElement();
  639. if (key instanceof StyleConstants) {
  640. HTML.Tag tag = HTML.getTagForStyleConstantsKey(
  641. (StyleConstants)key);
  642. if (tag != null && old.isDefined(tag)) {
  643. old = super.removeAttribute(old, tag);
  644. }
  645. }
  646. }
  647. }
  648. return old;
  649. }
  650. /**
  651. * Converts a set of attributes (if necessary) so that
  652. * any attributes that were specified as StyleConstants
  653. * attributes and have a CSS mapping, will be converted
  654. * to CSS attributes.
  655. */
  656. AttributeSet convertAttributeSet(AttributeSet a) {
  657. if ((a instanceof LargeConversionSet) ||
  658. (a instanceof SmallConversionSet)) {
  659. // known to be converted.
  660. return a;
  661. }
  662. // in most cases, there are no StyleConstants attributes
  663. // so we iterate the collection of keys to avoid creating
  664. // a new set.
  665. Enumeration names = a.getAttributeNames();
  666. while (names.hasMoreElements()) {
  667. Object name = names.nextElement();
  668. if (name instanceof StyleConstants) {
  669. // we really need to do a conversion, iterate again
  670. // building a new set.
  671. MutableAttributeSet converted = new LargeConversionSet();
  672. Enumeration keys = a.getAttributeNames();
  673. while (keys.hasMoreElements()) {
  674. Object key = keys.nextElement();
  675. Object cssValue = null;
  676. if (key instanceof StyleConstants) {
  677. // convert the StyleConstants attribute if possible
  678. Object cssKey = css.styleConstantsKeyToCSSKey
  679. ((StyleConstants)key);
  680. if (cssKey != null) {
  681. Object value = a.getAttribute(key);
  682. cssValue = css.styleConstantsValueToCSSValue
  683. ((StyleConstants)key, value);
  684. if (cssValue != null) {
  685. converted.addAttribute(cssKey, cssValue);
  686. }
  687. }
  688. }
  689. if (cssValue == null) {
  690. converted.addAttribute(key, a.getAttribute(key));
  691. }
  692. }
  693. return converted;
  694. }
  695. }
  696. return a;
  697. }
  698. /**
  699. * Large set of attributes that does conversion of requests
  700. * for attributes of type StyleConstants.
  701. */
  702. class LargeConversionSet extends SimpleAttributeSet {
  703. /**
  704. * Creates a new attribute set based on a supplied set of attributes.
  705. *
  706. * @param source the set of attributes
  707. */
  708. public LargeConversionSet(AttributeSet source) {
  709. super(source);
  710. }
  711. public LargeConversionSet() {
  712. super();
  713. }
  714. /**
  715. * Checks whether a given attribute is defined.
  716. *
  717. * @param key the attribute key
  718. * @return true if the attribute is defined
  719. * @see AttributeSet#isDefined
  720. */
  721. public boolean isDefined(Object key) {
  722. if (key instanceof StyleConstants) {
  723. Object cssKey = css.styleConstantsKeyToCSSKey
  724. ((StyleConstants)key);
  725. if (cssKey != null) {
  726. return super.isDefined(cssKey);
  727. }
  728. }
  729. return super.isDefined(key);
  730. }
  731. /**
  732. * Gets the value of an attribute.
  733. *
  734. * @param key the attribute name
  735. * @return the attribute value
  736. * @see AttributeSet#getAttribute
  737. */
  738. public Object getAttribute(Object key) {
  739. if (key instanceof StyleConstants) {
  740. Object cssKey = css.styleConstantsKeyToCSSKey
  741. ((StyleConstants)key);
  742. if (cssKey != null) {
  743. Object value = super.getAttribute(cssKey);
  744. if (value != null) {
  745. return css.cssValueToStyleConstantsValue
  746. ((StyleConstants)key, value);
  747. }
  748. }
  749. }
  750. return super.getAttribute(key);
  751. }
  752. }
  753. /**
  754. * Small set of attributes that does conversion of requests
  755. * for attributes of type StyleConstants.
  756. */
  757. class SmallConversionSet extends SmallAttributeSet {
  758. /**
  759. * Creates a new attribute set based on a supplied set of attributes.
  760. *
  761. * @param source the set of attributes
  762. */
  763. public SmallConversionSet(AttributeSet attrs) {
  764. super(attrs);
  765. }
  766. /**
  767. * Checks whether a given attribute is defined.
  768. *
  769. * @param key the attribute key
  770. * @return true if the attribute is defined
  771. * @see AttributeSet#isDefined
  772. */
  773. public boolean isDefined(Object key) {
  774. if (key instanceof StyleConstants) {
  775. Object cssKey = css.styleConstantsKeyToCSSKey
  776. ((StyleConstants)key);
  777. if (cssKey != null) {
  778. return super.isDefined(cssKey);
  779. }
  780. }
  781. return super.isDefined(key);
  782. }
  783. /**
  784. * Gets the value of an attribute.
  785. *
  786. * @param key the attribute name
  787. * @return the attribute value
  788. * @see AttributeSet#getAttribute
  789. */
  790. public Object getAttribute(Object key) {
  791. if (key instanceof StyleConstants) {
  792. Object cssKey = css.styleConstantsKeyToCSSKey
  793. ((StyleConstants)key);
  794. if (cssKey != null) {
  795. Object value = super.getAttribute(cssKey);
  796. if (value != null) {
  797. return css.cssValueToStyleConstantsValue
  798. ((StyleConstants)key, value);
  799. }
  800. }
  801. }
  802. return super.getAttribute(key);
  803. }
  804. }
  805. // ---- Resource handling ----------------------------------------
  806. /**
  807. * Fetches the font to use for the given set of attributes.
  808. */
  809. public Font getFont(AttributeSet a) {
  810. return css.getFont(this, a, 12, this);
  811. }
  812. /**
  813. * Takes a set of attributes and turn it into a foreground color
  814. * specification. This might be used to specify things
  815. * like brighter, more hue, etc.
  816. *
  817. * @param a the set of attributes
  818. * @return the color
  819. */
  820. public Color getForeground(AttributeSet a) {
  821. Color c = css.getColor(a, CSS.Attribute.COLOR);
  822. if (c == null) {
  823. return Color.black;
  824. }
  825. return c;
  826. }
  827. /**
  828. * Takes a set of attributes and turn it into a background color
  829. * specification. This might be used to specify things
  830. * like brighter, more hue, etc.
  831. *
  832. * @param a the set of attributes
  833. * @return the color
  834. */
  835. public Color getBackground(AttributeSet a) {
  836. return css.getColor(a, CSS.Attribute.BACKGROUND_COLOR);
  837. }
  838. /**
  839. * Fetches the box formatter to use for the given set
  840. * of CSS attributes.
  841. */
  842. public BoxPainter getBoxPainter(AttributeSet a) {
  843. return new BoxPainter(a, css, this);
  844. }
  845. /**
  846. * Fetches the list formatter to use for the given set
  847. * of CSS attributes.
  848. */
  849. public ListPainter getListPainter(AttributeSet a) {
  850. return new ListPainter(a, this);
  851. }
  852. /**
  853. * Sets the base font size, with valid values between 1 and 7.
  854. */
  855. public void setBaseFontSize(int sz) {
  856. css.setBaseFontSize(sz);
  857. }
  858. /**
  859. * Sets the base font size from the passed in String. The string
  860. * can either identify a specific font size, with legal values between
  861. * 1 and 7, or identifiy a relative font size such as +1 or -2.
  862. */
  863. public void setBaseFontSize(String size) {
  864. css.setBaseFontSize(size);
  865. }
  866. public static int getIndexOfSize(float pt) {
  867. return CSS.getIndexOfSize(pt, sizeMapDefault);
  868. }
  869. /**
  870. * Returns the point size, given a size index.
  871. */
  872. public float getPointSize(int index) {
  873. return css.getPointSize(index, this);
  874. }
  875. /**
  876. * Given a string such as "+2", "-2", or "2",
  877. * returns a point size value.
  878. */
  879. public float getPointSize(String size) {
  880. return css.getPointSize(size, this);
  881. }
  882. /**
  883. * Converts a color string such as "RED" or "#NNNNNN" to a Color.
  884. * Note: This will only convert the HTML3.2 color strings
  885. * or a string of length 7;
  886. * otherwise, it will return null.
  887. */
  888. public Color stringToColor(String string) {
  889. return CSS.stringToColor(string);
  890. }
  891. /**
  892. * Returns the ImageIcon to draw in the background for
  893. * <code>attr</code>.
  894. */
  895. ImageIcon getBackgroundImage(AttributeSet attr) {
  896. Object value = attr.getAttribute(CSS.Attribute.BACKGROUND_IMAGE);
  897. if (value != null) {
  898. return ((CSS.BackgroundImage)value).getImage(getBase());
  899. }
  900. return null;
  901. }
  902. /**
  903. * Adds a rule into the StyleSheet.
  904. *
  905. * @param selector the selector to use for the rule.
  906. * This will be a set of simple selectors, and must
  907. * be a length of 1 or greater.
  908. * @param declaration the set of CSS attributes that
  909. * make up the rule.
  910. */
  911. void addRule(String[] selector, AttributeSet declaration,
  912. boolean isLinked) {
  913. int n = selector.length;
  914. StringBuffer sb = new StringBuffer();
  915. sb.append(selector[0]);
  916. for (int counter = 1; counter < n; counter++) {
  917. sb.append(' ');
  918. sb.append(selector[counter]);
  919. }
  920. String selectorName = sb.toString();
  921. Style rule = getStyle(selectorName);
  922. if (rule == null) {
  923. // Notice how the rule is first created, and it not part of
  924. // the synchronized block. It is done like this as creating
  925. // a new rule will fire a ChangeEvent. We do not want to be
  926. // holding the lock when calling to other objects, it can
  927. // result in deadlock.
  928. Style altRule = addStyle(selectorName, null);
  929. synchronized(this) {
  930. SelectorMapping mapping = getRootSelectorMapping();
  931. for (int i = n - 1; i >= 0; i--) {
  932. mapping = mapping.getChildSelectorMapping
  933. (selector[i], true);
  934. }
  935. rule = mapping.getStyle();
  936. if (rule == null) {
  937. rule = altRule;
  938. mapping.setStyle(rule);
  939. refreshResolvedRules(selectorName, selector, rule,
  940. mapping.getSpecificity());
  941. }
  942. }
  943. }
  944. if (isLinked) {
  945. rule = getLinkedStyle(rule);
  946. }
  947. rule.addAttributes(declaration);
  948. }
  949. //
  950. // The following gaggle of methods is used in maintaing the rules from
  951. // the sheet.
  952. //
  953. /**
  954. * Updates the attributes of the rules to reference any related
  955. * rules in <code>ss</code>.
  956. */
  957. private synchronized void linkStyleSheetAt(StyleSheet ss, int index) {
  958. if (resolvedStyles.size() > 0) {
  959. Enumeration values = resolvedStyles.elements();
  960. while (values.hasMoreElements()) {
  961. ResolvedStyle rule = (ResolvedStyle)values.nextElement();
  962. rule.insertExtendedStyleAt(ss.getRule(rule.getName()),
  963. index);
  964. }
  965. }
  966. }
  967. /**
  968. * Removes references to the rules in <code>ss</code>.
  969. * <code>index</code> gives the index the StyleSheet was at, that is
  970. * how many StyleSheets had been added before it.
  971. */
  972. private synchronized void unlinkStyleSheet(StyleSheet ss, int index) {
  973. if (resolvedStyles.size() > 0) {
  974. Enumeration values = resolvedStyles.elements();
  975. while (values.hasMoreElements()) {
  976. ResolvedStyle rule = (ResolvedStyle)values.nextElement();
  977. rule.removeExtendedStyleAt(index);
  978. }
  979. }
  980. }
  981. /**
  982. * Returns the simple selectors that comprise selector.
  983. */
  984. /* protected */
  985. String[] getSimpleSelectors(String selector) {
  986. selector = cleanSelectorString(selector);
  987. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  988. Vector selectors = sb.getVector();
  989. int lastIndex = 0;
  990. int length = selector.length();
  991. while (lastIndex != -1) {
  992. int newIndex = selector.indexOf(' ', lastIndex);
  993. if (newIndex != -1) {
  994. selectors.addElement(selector.substring(lastIndex, newIndex));
  995. if (++newIndex == length) {
  996. lastIndex = -1;
  997. }
  998. else {
  999. lastIndex = newIndex;
  1000. }
  1001. }
  1002. else {
  1003. selectors.addElement(selector.substring(lastIndex));
  1004. lastIndex = -1;
  1005. }
  1006. }
  1007. String[] retValue = new String[selectors.size()];
  1008. selectors.copyInto(retValue);
  1009. SearchBuffer.releaseSearchBuffer(sb);
  1010. return retValue;
  1011. }
  1012. /**
  1013. * Returns a string that only has one space between simple selectors,
  1014. * which may be the passed in String.
  1015. */
  1016. /*protected*/ String cleanSelectorString(String selector) {
  1017. boolean lastWasSpace = true;
  1018. for (int counter = 0, maxCounter = selector.length();
  1019. counter < maxCounter; counter++) {
  1020. switch(selector.charAt(counter)) {
  1021. case ' ':
  1022. if (lastWasSpace) {
  1023. return _cleanSelectorString(selector);
  1024. }
  1025. lastWasSpace = true;
  1026. break;
  1027. case '\n':
  1028. case '\r':
  1029. case '\t':
  1030. return _cleanSelectorString(selector);
  1031. default:
  1032. lastWasSpace = false;
  1033. }
  1034. }
  1035. if (lastWasSpace) {
  1036. return _cleanSelectorString(selector);
  1037. }
  1038. // It was fine.
  1039. return selector;
  1040. }
  1041. /**
  1042. * Returns a new String that contains only one space between non
  1043. * white space characters.
  1044. */
  1045. private String _cleanSelectorString(String selector) {
  1046. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  1047. StringBuffer buff = sb.getStringBuffer();
  1048. boolean lastWasSpace = true;
  1049. int lastIndex = 0;
  1050. char[] chars = selector.toCharArray();
  1051. int numChars = chars.length;
  1052. String retValue = null;
  1053. try {
  1054. for (int counter = 0; counter < numChars; counter++) {
  1055. switch(chars[counter]) {
  1056. case ' ':
  1057. if (!lastWasSpace) {
  1058. lastWasSpace = true;
  1059. if (lastIndex < counter) {
  1060. buff.append(chars, lastIndex,
  1061. 1 + counter - lastIndex);
  1062. }
  1063. }
  1064. lastIndex = counter + 1;
  1065. break;
  1066. case '\n':
  1067. case '\r':
  1068. case '\t':
  1069. if (!lastWasSpace) {
  1070. lastWasSpace = true;
  1071. if (lastIndex < counter) {
  1072. buff.append(chars, lastIndex,
  1073. counter - lastIndex);
  1074. buff.append(' ');
  1075. }
  1076. }
  1077. lastIndex = counter + 1;
  1078. break;
  1079. default:
  1080. lastWasSpace = false;
  1081. break;
  1082. }
  1083. }
  1084. if (lastWasSpace && buff.length() > 0) {
  1085. // Remove last space.
  1086. buff.setLength(buff.length() - 1);
  1087. }
  1088. else if (lastIndex < numChars) {
  1089. buff.append(chars, lastIndex, numChars - lastIndex);
  1090. }
  1091. retValue = buff.toString();
  1092. }
  1093. finally {
  1094. SearchBuffer.releaseSearchBuffer(sb);
  1095. }
  1096. return retValue;
  1097. }
  1098. /**
  1099. * Returns the root selector mapping that all selectors are relative
  1100. * to. This is an inverted graph of the selectors.
  1101. */
  1102. private SelectorMapping getRootSelectorMapping() {
  1103. return selectorMapping;
  1104. }
  1105. /**
  1106. * Returns the specificity of the passed in String. It assumes the
  1107. * passed in string doesn't contain junk, that is each selector is
  1108. * separated by a space and each selector at most contains one . or one
  1109. * #. A simple selector has a weight of 1, an id selector has a weight
  1110. * of 100, and a class selector has a weight of 10000.
  1111. */
  1112. /*protected*/ static int getSpecificity(String selector) {
  1113. int specificity = 0;
  1114. boolean lastWasSpace = true;
  1115. for (int counter = 0, maxCounter = selector.length();
  1116. counter < maxCounter; counter++) {
  1117. switch(selector.charAt(counter)) {
  1118. case '.':
  1119. specificity += 100;
  1120. break;
  1121. case '#':
  1122. specificity += 10000;
  1123. break;
  1124. case ' ':
  1125. lastWasSpace = true;
  1126. break;
  1127. default:
  1128. if (lastWasSpace) {
  1129. lastWasSpace = false;
  1130. specificity += 1;
  1131. }
  1132. }
  1133. }
  1134. return specificity;
  1135. }
  1136. /**
  1137. * Returns the style that linked attributes should be added to. This
  1138. * will create the style if necessary.
  1139. */
  1140. private Style getLinkedStyle(Style localStyle) {
  1141. // NOTE: This is not synchronized, and the caller of this does
  1142. // not synchronize. There is the chance for one of the callers to
  1143. // overwrite the existing resolved parent, but it is quite rare.
  1144. // The reason this is left like this is because setResolveParent
  1145. // will fire a ChangeEvent. It is really, REALLY bad for us to
  1146. // hold a lock when calling outside of us, it may cause a deadlock.
  1147. Style retStyle = (Style)localStyle.getResolveParent();
  1148. if (retStyle == null) {
  1149. retStyle = addStyle(null, null);
  1150. localStyle.setResolveParent(retStyle);
  1151. }
  1152. return retStyle;
  1153. }
  1154. /**
  1155. * Returns the resolved style for <code>selector</code>. This will
  1156. * create the resolved style, if necessary.
  1157. */
  1158. private synchronized Style getResolvedStyle(String selector,
  1159. Vector elements,
  1160. HTML.Tag t) {
  1161. Style retStyle = (Style)resolvedStyles.get(selector);
  1162. if (retStyle == null) {
  1163. retStyle = createResolvedStyle(selector, elements, t);
  1164. }
  1165. return retStyle;
  1166. }
  1167. /**
  1168. * Returns the resolved style for <code>selector</code>. This will
  1169. * create the resolved style, if necessary.
  1170. */
  1171. private synchronized Style getResolvedStyle(String selector) {
  1172. Style retStyle = (Style)resolvedStyles.get(selector);
  1173. if (retStyle == null) {
  1174. retStyle = createResolvedStyle(selector);
  1175. }
  1176. return retStyle;
  1177. }
  1178. /**
  1179. * Adds <code>mapping</code> to <code>elements</code>. It is added
  1180. * such that <code>elements</code> will remain ordered by
  1181. * specificity.
  1182. */
  1183. private void addSortedStyle(SelectorMapping mapping, Vector elements) {
  1184. int size = elements.size();
  1185. if (size > 0) {
  1186. int specificity = mapping.getSpecificity();
  1187. for (int counter = 0; counter < size; counter++) {
  1188. if (specificity >= ((SelectorMapping)elements.elementAt
  1189. (counter)).getSpecificity()) {
  1190. elements.insertElementAt(mapping, counter);
  1191. return;
  1192. }
  1193. }
  1194. }
  1195. elements.addElement(mapping);
  1196. }
  1197. /**
  1198. * Adds <code>parentMapping</code> to <code>styles</code>, and
  1199. * recursively calls this method if <code>parentMapping</code> has
  1200. * any child mappings for any of the Elements in <code>elements</code>.
  1201. */
  1202. private synchronized void getStyles(SelectorMapping parentMapping,
  1203. Vector styles,
  1204. String[] tags, String[] ids, String[] classes,
  1205. int index, int numElements,
  1206. Hashtable alreadyChecked) {
  1207. // Avoid desending the same mapping twice.
  1208. if (alreadyChecked.contains(parentMapping)) {
  1209. return;
  1210. }
  1211. alreadyChecked.put(parentMapping, parentMapping);
  1212. Style style = parentMapping.getStyle();
  1213. if (style != null) {
  1214. addSortedStyle(parentMapping, styles);
  1215. }
  1216. for (int counter = index; counter < numElements; counter++) {
  1217. String tagString = tags[counter];
  1218. if (tagString != null) {
  1219. SelectorMapping childMapping = parentMapping.
  1220. getChildSelectorMapping(tagString, false);
  1221. if (childMapping != null) {
  1222. getStyles(childMapping, styles, tags, ids, classes,
  1223. counter + 1, numElements, alreadyChecked);
  1224. }
  1225. if (classes[counter] != null) {
  1226. String className = classes[counter];
  1227. childMapping = parentMapping.getChildSelectorMapping(
  1228. tagString + "." + className, false);
  1229. if (childMapping != null) {
  1230. getStyles(childMapping, styles, tags, ids, classes,
  1231. counter + 1, numElements, alreadyChecked);
  1232. }
  1233. childMapping = parentMapping.getChildSelectorMapping(
  1234. "." + className, false);
  1235. if (childMapping != null) {
  1236. getStyles(childMapping, styles, tags, ids, classes,
  1237. counter + 1, numElements, alreadyChecked);
  1238. }
  1239. }
  1240. if (ids[counter] != null) {
  1241. String idName = ids[counter];
  1242. childMapping = parentMapping.getChildSelectorMapping(
  1243. tagString + "#" + idName, false);
  1244. if (childMapping != null) {
  1245. getStyles(childMapping, styles, tags, ids, classes,
  1246. counter + 1, numElements, alreadyChecked);
  1247. }
  1248. childMapping = parentMapping.getChildSelectorMapping(
  1249. "#" + idName, false);
  1250. if (childMapping != null) {
  1251. getStyles(childMapping, styles, tags, ids, classes,
  1252. counter + 1, numElements, alreadyChecked);
  1253. }
  1254. }
  1255. }
  1256. }
  1257. }
  1258. /**
  1259. * Creates and returns a Style containing all the rules that match
  1260. * <code>selector</code>.
  1261. */
  1262. private synchronized Style createResolvedStyle(String selector,
  1263. String[] tags,
  1264. String[] ids, String[] classes) {
  1265. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  1266. Vector tempVector = sb.getVector();
  1267. Hashtable tempHashtable = sb.getHashtable();
  1268. // Determine all the Styles that are appropriate, placing them
  1269. // in tempVector
  1270. try {
  1271. SelectorMapping mapping = getRootSelectorMapping();
  1272. int numElements = tags.length;
  1273. String tagString = tags[0];
  1274. SelectorMapping childMapping = mapping.getChildSelectorMapping(
  1275. tagString, false);
  1276. if (childMapping != null) {
  1277. getStyles(childMapping, tempVector, tags, ids, classes, 1,
  1278. numElements, tempHashtable);
  1279. }
  1280. if (classes[0] != null) {
  1281. String className = classes[0];
  1282. childMapping = mapping.getChildSelectorMapping(
  1283. tagString + "." + className, false);
  1284. if (childMapping != null) {
  1285. getStyles(childMapping, tempVector, tags, ids, classes, 1,
  1286. numElements, tempHashtable);
  1287. }
  1288. childMapping = mapping.getChildSelectorMapping(
  1289. "." + className, false);
  1290. if (childMapping != null) {
  1291. getStyles(childMapping, tempVector, tags, ids, classes,
  1292. 1, numElements, tempHashtable);
  1293. }
  1294. }
  1295. if (ids[0] != null) {
  1296. String idName = ids[0];
  1297. childMapping = mapping.getChildSelectorMapping(
  1298. tagString + "#" + idName, false);
  1299. if (childMapping != null) {
  1300. getStyles(childMapping, tempVector, tags, ids, classes,
  1301. 1, numElements, tempHashtable);
  1302. }
  1303. childMapping = mapping.getChildSelectorMapping(
  1304. "#" + idName, false);
  1305. if (childMapping != null) {
  1306. getStyles(childMapping, tempVector, tags, ids, classes,
  1307. 1, numElements, tempHashtable);
  1308. }
  1309. }
  1310. // Create a new Style that will delegate to all the matching
  1311. // Styles.
  1312. int numLinkedSS = (linkedStyleSheets != null) ?
  1313. linkedStyleSheets.size() : 0;
  1314. int numStyles = tempVector.size();
  1315. AttributeSet[] attrs = new AttributeSet[numStyles + numLinkedSS];
  1316. for (int counter = 0; counter < numStyles; counter++) {
  1317. attrs[counter] = ((SelectorMapping)tempVector.
  1318. elementAt(counter)).getStyle();
  1319. }
  1320. // Get the AttributeSet from linked style sheets.
  1321. for (int counter = 0; counter < numLinkedSS; counter++) {
  1322. AttributeSet attr = ((StyleSheet)linkedStyleSheets.
  1323. elementAt(counter)).getRule(selector);
  1324. if (attr == null) {
  1325. attrs[counter + numStyles] = SimpleAttributeSet.EMPTY;
  1326. }
  1327. else {
  1328. attrs[counter + numStyles] = attr;
  1329. }
  1330. }
  1331. ResolvedStyle retStyle = new ResolvedStyle(selector, attrs,
  1332. numStyles);
  1333. resolvedStyles.put(selector, retStyle);
  1334. return retStyle;
  1335. }
  1336. finally {
  1337. SearchBuffer.releaseSearchBuffer(sb);
  1338. }
  1339. }
  1340. /**
  1341. * Creates and returns a Style containing all the rules that
  1342. * matches <code>selector</code>.
  1343. *
  1344. * @param elements a Vector of all the Elements
  1345. * the style is being asked for. The
  1346. * first Element is the deepest Element, with the last Element
  1347. * representing the root.
  1348. * @param t the Tag to use for
  1349. * the first Element in <code>elements</code>
  1350. */
  1351. private Style createResolvedStyle(String selector, Vector elements,
  1352. HTML.Tag t) {
  1353. int numElements = elements.size();
  1354. // Build three arrays, one for tags, one for class's, and one for
  1355. // id's
  1356. String tags[] = new String[numElements];
  1357. String ids[] = new String[numElements];
  1358. String classes[] = new String[numElements];
  1359. for (int counter = 0; counter < numElements; counter++) {
  1360. Element e = (Element)elements.elementAt(counter);
  1361. AttributeSet attr = e.getAttributes();
  1362. if (counter == 0 && e.isLeaf()) {
  1363. // For leafs, we use the second tier attributes.
  1364. Object testAttr = attr.getAttribute(t);
  1365. if (testAttr instanceof AttributeSet) {
  1366. attr = (AttributeSet)testAttr;
  1367. }
  1368. else {
  1369. attr = null;
  1370. }
  1371. }
  1372. if (attr != null) {
  1373. HTML.Tag tag = (HTML.Tag)attr.getAttribute(StyleConstants.
  1374. NameAttribute);
  1375. if (tag != null) {
  1376. tags[counter] = tag.toString();
  1377. }
  1378. else {
  1379. tags[counter] = null;
  1380. }
  1381. if (attr.isDefined(HTML.Attribute.CLASS)) {
  1382. classes[counter] = attr.getAttribute
  1383. (HTML.Attribute.CLASS).toString();
  1384. }
  1385. else {
  1386. classes[counter] = null;
  1387. }
  1388. if (attr.isDefined(HTML.Attribute.ID)) {
  1389. ids[counter] = attr.getAttribute(HTML.Attribute.ID).
  1390. toString();
  1391. }
  1392. else {
  1393. ids[counter] = null;
  1394. }
  1395. }
  1396. else {
  1397. tags[counter] = ids[counter] = classes[counter] = null;
  1398. }
  1399. }
  1400. tags[0] = t.toString();
  1401. return createResolvedStyle(selector, tags, ids, classes);
  1402. }
  1403. /**
  1404. * Creates and returns a Style containing all the rules that match
  1405. * <code>selector</code>. It is assumed that each simple selector
  1406. * in <code>selector</code> is separated by a space.
  1407. */
  1408. private Style createResolvedStyle(String selector) {
  1409. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  1410. // Will contain the tags, ids, and classes, in that order.
  1411. Vector elements = sb.getVector();
  1412. try {
  1413. boolean done;
  1414. int dotIndex = 0;
  1415. int spaceIndex = 0;
  1416. int poundIndex = 0;
  1417. int lastIndex = 0;
  1418. int length = selector.length();
  1419. while (lastIndex < length) {
  1420. if (dotIndex == lastIndex) {
  1421. dotIndex = selector.indexOf('.', lastIndex);
  1422. }
  1423. if (poundIndex == lastIndex) {
  1424. poundIndex = selector.indexOf('#', lastIndex);
  1425. }
  1426. spaceIndex = selector.indexOf(' ', lastIndex);
  1427. if (spaceIndex == -1) {
  1428. spaceIndex = length;
  1429. }
  1430. if (dotIndex != -1 && poundIndex != -1 &&
  1431. dotIndex < spaceIndex && poundIndex < spaceIndex) {
  1432. if (poundIndex < dotIndex) {
  1433. // #.
  1434. if (lastIndex == poundIndex) {
  1435. elements.addElement("");
  1436. }
  1437. else {
  1438. elements.addElement(selector.substring(lastIndex,
  1439. poundIndex));
  1440. }
  1441. if ((dotIndex + 1) < spaceIndex) {
  1442. elements.addElement(selector.substring
  1443. (dotIndex + 1, spaceIndex));
  1444. }
  1445. else {
  1446. elements.addElement(null);
  1447. }
  1448. if ((poundIndex + 1) == dotIndex) {
  1449. elements.addElement(null);
  1450. }
  1451. else {
  1452. elements.addElement(selector.substring
  1453. (poundIndex + 1, dotIndex));
  1454. }
  1455. }
  1456. else if(poundIndex < spaceIndex) {
  1457. // .#
  1458. if (lastIndex == dotIndex) {
  1459. elements.addElement("");
  1460. }
  1461. else {
  1462. elements.addElement(selector.substring(lastIndex,
  1463. dotIndex));
  1464. }
  1465. if ((dotIndex + 1) < poundIndex) {
  1466. elements.addElement(selector.substring
  1467. (dotIndex + 1, poundIndex));
  1468. }
  1469. else {
  1470. elements.addElement(null);
  1471. }
  1472. if ((poundIndex + 1) == spaceIndex) {
  1473. elements.addElement(null);
  1474. }
  1475. else {
  1476. elements.addElement(selector.substring
  1477. (poundIndex + 1, spaceIndex));
  1478. }
  1479. }
  1480. dotIndex = poundIndex = spaceIndex + 1;
  1481. }
  1482. else if (dotIndex != -1 && dotIndex < spaceIndex) {
  1483. // .
  1484. if (dotIndex == lastIndex) {
  1485. elements.addElement("");
  1486. }
  1487. else {
  1488. elements.addElement(selector.substring(lastIndex,
  1489. dotIndex));
  1490. }
  1491. if ((dotIndex + 1) == spaceIndex) {
  1492. elements.addElement(null);
  1493. }
  1494. else {
  1495. elements.addElement(selector.substring(dotIndex + 1,
  1496. spaceIndex));
  1497. }
  1498. elements.addElement(null);
  1499. dotIndex = spaceIndex + 1;
  1500. }
  1501. else if (poundIndex != -1 && poundIndex < spaceIndex) {
  1502. // #
  1503. if (poundIndex == lastIndex) {
  1504. elements.addElement("");
  1505. }
  1506. else {
  1507. elements.addElement(selector.substring(lastIndex,
  1508. poundIndex));
  1509. }
  1510. elements.addElement(null);
  1511. if ((poundIndex + 1) == spaceIndex) {
  1512. elements.addElement(null);
  1513. }
  1514. else {
  1515. elements.addElement(selector.substring(poundIndex + 1,
  1516. spaceIndex));
  1517. }
  1518. poundIndex = spaceIndex + 1;
  1519. }
  1520. else {
  1521. // id
  1522. elements.addElement(selector.substring(lastIndex,
  1523. spaceIndex));
  1524. elements.addElement(null);
  1525. elements.addElement(null);
  1526. }
  1527. lastIndex = spaceIndex + 1;
  1528. }
  1529. // Create the tag, id, and class arrays.
  1530. int total = elements.size();
  1531. int numTags = total / 3;
  1532. String[] tags = new String[numTags];
  1533. String[] ids = new String[numTags];
  1534. String[] classes = new String[numTags];
  1535. for (int index = 0, eIndex = total - 3; index < numTags;
  1536. index++, eIndex -= 3) {
  1537. tags[index] = (String)elements.elementAt(eIndex);
  1538. classes[index] = (String)elements.elementAt(eIndex + 1);
  1539. ids[index] = (String)elements.elementAt(eIndex + 2);
  1540. }
  1541. return createResolvedStyle(selector, tags, ids, classes);
  1542. }
  1543. finally {
  1544. SearchBuffer.releaseSearchBuffer(sb);
  1545. }
  1546. }
  1547. /**
  1548. * Should be invoked when a new rule is added that did not previously
  1549. * exist. Goes through and refreshes the necessary resolved
  1550. * rules.
  1551. */
  1552. private synchronized void refreshResolvedRules(String selectorName,
  1553. String[] selector,
  1554. Style newStyle,
  1555. int specificity) {
  1556. if (resolvedStyles.size() > 0) {
  1557. Enumeration values = resolvedStyles.elements();
  1558. while (values.hasMoreElements()) {
  1559. ResolvedStyle style = (ResolvedStyle)values.nextElement();
  1560. if (style.matches(selectorName)) {
  1561. style.insertStyle(newStyle, specificity);
  1562. }
  1563. }
  1564. }
  1565. }
  1566. /**
  1567. * A temporary class used to hold a Vector, a StringBuffer and a
  1568. * Hashtable. This is used to avoid allocing a lot of garbage when
  1569. * searching for rules. Use the static method obtainSearchBuffer and
  1570. * releaseSearchBuffer to get a SearchBuffer, and release it when
  1571. * done.
  1572. */
  1573. private static class SearchBuffer {
  1574. /** A stack containing instances of SearchBuffer. Used in getting
  1575. * rules. */
  1576. static Stack searchBuffers = new Stack();
  1577. // A set of temporary variables that can be used in whatever way.
  1578. Vector vector = null;
  1579. StringBuffer stringBuffer = null;
  1580. Hashtable hashtable = null;
  1581. /**
  1582. * Returns an instance of SearchBuffer. Be sure and issue
  1583. * a releaseSearchBuffer when done with it.
  1584. */
  1585. static SearchBuffer obtainSearchBuffer() {
  1586. SearchBuffer sb;
  1587. try {
  1588. if(!searchBuffers.empty()) {
  1589. sb = (SearchBuffer)searchBuffers.pop();
  1590. } else {
  1591. sb = new SearchBuffer();
  1592. }
  1593. } catch (EmptyStackException ese) {
  1594. sb = new SearchBuffer();
  1595. }
  1596. return sb;
  1597. }
  1598. /**
  1599. * Adds <code>sb</code> to the stack of SearchBuffers that can
  1600. * be used.
  1601. */
  1602. static void releaseSearchBuffer(SearchBuffer sb) {
  1603. sb.empty();
  1604. searchBuffers.push(sb);
  1605. }
  1606. StringBuffer getStringBuffer() {
  1607. if (stringBuffer == null) {
  1608. stringBuffer = new StringBuffer();
  1609. }
  1610. return stringBuffer;
  1611. }
  1612. Vector getVector() {
  1613. if (vector == null) {
  1614. vector = new Vector();
  1615. }
  1616. return vector;
  1617. }
  1618. Hashtable getHashtable() {
  1619. if (hashtable == null) {
  1620. hashtable = new Hashtable();
  1621. }
  1622. return hashtable;
  1623. }
  1624. void empty() {
  1625. if (stringBuffer != null) {
  1626. stringBuffer.setLength(0);
  1627. }
  1628. if (vector != null) {
  1629. vector.removeAllElements();
  1630. }
  1631. if (hashtable != null) {
  1632. hashtable.clear();
  1633. }
  1634. }
  1635. }
  1636. static final Border noBorder = new EmptyBorder(0,0,0,0);
  1637. /**
  1638. * Class to carry out some of the duties of
  1639. * CSS formatting. Implementations of this
  1640. * class enable views to present the CSS formatting
  1641. * while not knowing anything about how the CSS values
  1642. * are being cached.
  1643. * <p>
  1644. * As a delegate of Views, this object is responsible for
  1645. * the insets of a View and making sure the background
  1646. * is maintained according to the CSS attributes.
  1647. */
  1648. public static class BoxPainter implements Serializable {
  1649. BoxPainter(AttributeSet a, CSS css, StyleSheet ss) {
  1650. this.ss = ss;
  1651. this.css = css;
  1652. border = getBorder(a);
  1653. binsets = border.getBorderInsets(null);
  1654. topMargin = getLength(CSS.Attribute.MARGIN_TOP, a);
  1655. bottomMargin = getLength(CSS.Attribute.MARGIN_BOTTOM, a);
  1656. leftMargin = getLength(CSS.Attribute.MARGIN_LEFT, a);
  1657. rightMargin = getLength(CSS.Attribute.MARGIN_RIGHT, a);
  1658. bg = ss.getBackground(a);
  1659. if (ss.getBackgroundImage(a) != null) {
  1660. bgPainter = new BackgroundImagePainter(a, css, ss);
  1661. }
  1662. }
  1663. /**
  1664. * Fetches a border to render for the given attributes.
  1665. * PENDING(prinz) This is pretty badly hacked at the
  1666. * moment.
  1667. */
  1668. Border getBorder(AttributeSet a) {
  1669. Border b = noBorder;
  1670. Object o = a.getAttribute(CSS.Attribute.BORDER_STYLE);
  1671. if (o != null) {
  1672. String bstyle = o.toString();
  1673. int bw = (int) getLength(CSS.Attribute.BORDER_TOP_WIDTH, a);
  1674. if (bw > 0) {
  1675. if (bstyle.equals("inset")) {
  1676. Color c = getBorderColor(a);
  1677. b = new BevelBorder(BevelBorder.LOWERED, c.brighter(), c.darker());
  1678. } else if (bstyle.equals("outset")) {
  1679. Color c = getBorderColor(a);
  1680. b = new BevelBorder(BevelBorder.RAISED, c.brighter(), c.darker());
  1681. } else if (bstyle.equals("solid")) {
  1682. Color c = getBorderColor(a);
  1683. b = new LineBorder(c);
  1684. }
  1685. }
  1686. }
  1687. return b;
  1688. }
  1689. /**
  1690. * Fetches the color to use for borders. This will either be
  1691. * the value specified by the border-color attribute (which
  1692. * is not inherited), or it will default to the color attribute
  1693. * (which is inherited).
  1694. */
  1695. Color getBorderColor(AttributeSet a) {
  1696. Color color = css.getColor(a, CSS.Attribute.BORDER_COLOR);
  1697. if (color == null) {
  1698. color = css.getColor(a, CSS.Attribute.COLOR);
  1699. if (color == null) {
  1700. return Color.black;
  1701. }
  1702. }
  1703. return color;
  1704. }
  1705. /**
  1706. * Fetches the inset needed on a given side to
  1707. * account for the margin, border, and padding.
  1708. *
  1709. * @param side The size of the box to fetch the
  1710. * inset for. This can be View.TOP,
  1711. * View.LEFT, View.BOTTOM, or View.RIGHT.
  1712. * @param v the view making the request. This is
  1713. * used to get the AttributeSet, and may be used to
  1714. * resolve percentage arguments.
  1715. * @exception IllegalArgumentException for an invalid direction
  1716. */
  1717. public float getInset(int side, View v) {
  1718. AttributeSet a = v.getAttributes();
  1719. float inset = 0;
  1720. switch(side) {
  1721. case View.LEFT:
  1722. inset += leftMargin;
  1723. inset += binsets.left;
  1724. inset += getLength(CSS.Attribute.PADDING_LEFT, a);
  1725. break;
  1726. case View.RIGHT:
  1727. inset += rightMargin;
  1728. inset += binsets.right;
  1729. inset += getLength(CSS.Attribute.PADDING_RIGHT, a);
  1730. break;
  1731. case View.TOP:
  1732. inset += topMargin;
  1733. inset += binsets.top;
  1734. inset += getLength(CSS.Attribute.PADDING_TOP, a);
  1735. break;
  1736. case View.BOTTOM:
  1737. inset += bottomMargin;
  1738. inset += binsets.bottom;
  1739. inset += getLength(CSS.Attribute.PADDING_BOTTOM, a);
  1740. break;
  1741. default:
  1742. throw new IllegalArgumentException("Invalid side: " + side);
  1743. }
  1744. return inset;
  1745. }
  1746. /**
  1747. * Paints the CSS box according to the attributes
  1748. * given. This should paint the border, padding,
  1749. * and background.
  1750. *
  1751. * @param g the rendering surface.
  1752. * @param x the x coordinate of the allocated area to
  1753. * render into.
  1754. * @param y the y coordinate of the allocated area to
  1755. * render into.
  1756. * @param w the width of the allocated area to render into.
  1757. * @param h the height of the allocated area to render into.
  1758. * @param v the view making the request. This is
  1759. * used to get the AttributeSet, and may be used to
  1760. * resolve percentage arguments.
  1761. */
  1762. public void paint(Graphics g, float x, float y, float w, float h, View v) {
  1763. // PENDING(prinz) implement real rendering... which would
  1764. // do full set of border and background capabilities.
  1765. // remove margin
  1766. float dx = 0;
  1767. float dy = 0;
  1768. float dw = 0;
  1769. float dh = 0;
  1770. if (!(v instanceof HTMLEditorKit.HTMLFactory.BodyBlockView)) {
  1771. dx = leftMargin;
  1772. dy = topMargin;
  1773. dw = -(leftMargin + rightMargin);
  1774. dh = -(topMargin + bottomMargin);
  1775. }
  1776. if (bg != null) {
  1777. g.setColor(bg);
  1778. g.fillRect((int) (x + dx),
  1779. (int) (y + dy),
  1780. (int) (w + dw),
  1781. (int) (h + dh));
  1782. }
  1783. if (bgPainter != null) {
  1784. bgPainter.paint(g, x + dx, y + dy, w + dw, h + dh, v);
  1785. }
  1786. x += leftMargin;
  1787. y += topMargin;
  1788. w -= leftMargin + rightMargin;
  1789. h -= topMargin + bottomMargin;
  1790. border.paintBorder(null, g, (int) x, (int) y, (int) w, (int) h);
  1791. }
  1792. float getLength(CSS.Attribute key, AttributeSet a) {
  1793. return css.getLength(a, key, ss);
  1794. }
  1795. float topMargin;
  1796. float bottomMargin;
  1797. float leftMargin;
  1798. float rightMargin;
  1799. // Bitmask, used to indicate what margins are relative:
  1800. // bit 0 for top, 1 for bottom, 2 for left and 3 for right.
  1801. short marginFlags;
  1802. Border border;
  1803. Insets binsets;
  1804. CSS css;
  1805. StyleSheet ss;
  1806. Color bg;
  1807. BackgroundImagePainter bgPainter;
  1808. }
  1809. /**
  1810. * Class to carry out some of the duties of CSS list
  1811. * formatting. Implementations of this
  1812. * class enable views to present the CSS formatting
  1813. * while not knowing anything about how the CSS values
  1814. * are being cached.
  1815. */
  1816. public static class ListPainter implements Serializable {
  1817. ListPainter(AttributeSet attr, StyleSheet ss) {
  1818. this.ss = ss;
  1819. /* Get the image to use as a list bullet */
  1820. String imgstr = (String)attr.getAttribute(CSS.Attribute.
  1821. LIST_STYLE_IMAGE);
  1822. type = null;
  1823. if (imgstr != null && !imgstr.equals("none")) {
  1824. String tmpstr = null;
  1825. try {
  1826. StringTokenizer st = new StringTokenizer(imgstr, "()");
  1827. if (st.hasMoreTokens())
  1828. tmpstr = st.nextToken();
  1829. if (st.hasMoreTokens())
  1830. tmpstr = st.nextToken();
  1831. URL u = new URL(tmpstr);
  1832. img = new ImageIcon(u);
  1833. } catch (MalformedURLException e) {
  1834. if (tmpstr != null && ss != null && ss.getBase() != null) {
  1835. try {
  1836. URL u = new URL(ss.getBase(), tmpstr);
  1837. img = new ImageIcon(u);
  1838. } catch (MalformedURLException murle) {
  1839. img = null;
  1840. }
  1841. }
  1842. else {
  1843. img = null;
  1844. }
  1845. }
  1846. }
  1847. /* Get the type of bullet to use in the list */
  1848. if (img == null) {
  1849. type = (CSS.Value)attr.getAttribute(CSS.Attribute.
  1850. LIST_STYLE_TYPE);
  1851. }
  1852. start = 1;
  1853. paintRect = new Rectangle();
  1854. }
  1855. /**
  1856. * Returns a string that represents the value
  1857. * of the HTML.Attribute.TYPE attribute.
  1858. * If this attributes is not defined, then
  1859. * then the type defaults to "disc" unless
  1860. * the tag is on Ordered list. In the case
  1861. * of the latter, the default type is "decimal".
  1862. */
  1863. private CSS.Value getChildType(View childView) {
  1864. CSS.Value childtype = (CSS.Value)childView.getAttributes().
  1865. getAttribute(CSS.Attribute.LIST_STYLE_TYPE);
  1866. if (childtype == null) {
  1867. if (type == null) {
  1868. // Parent view.
  1869. View v = childView.getParent();
  1870. HTMLDocument doc = (HTMLDocument)v.getDocument();
  1871. if (doc.matchNameAttribute(v.getElement().getAttributes(),
  1872. HTML.Tag.OL)) {
  1873. childtype = CSS.Value.DECIMAL;
  1874. } else {
  1875. childtype = CSS.Value.DISC;
  1876. }
  1877. } else {
  1878. childtype = type;
  1879. }
  1880. }
  1881. return childtype;
  1882. }
  1883. /**
  1884. * Obtains the starting index from <code>parent</code>.
  1885. */
  1886. private void getStart(View parent) {
  1887. checkedForStart = true;
  1888. Element element = parent.getElement();
  1889. if (element != null) {
  1890. AttributeSet attr = element.getAttributes();
  1891. Object startValue;
  1892. if (attr != null && attr.isDefined(HTML.Attribute.START) &&
  1893. (startValue = attr.getAttribute
  1894. (HTML.Attribute.START)) != null &&
  1895. (startValue instanceof String)) {
  1896. try {
  1897. start = Integer.parseInt((String)startValue);
  1898. }
  1899. catch (NumberFormatException nfe) {}
  1900. }
  1901. }
  1902. }
  1903. /**
  1904. * Returns an integer that should be used to render the child at
  1905. * <code>childIndex</code> with. The retValue will usually be
  1906. * <code>childIndex</code> + 1, unless <code>parentView</code>
  1907. * has some Views that do not represent LI's, or one of the views
  1908. * has a HTML.Attribute.START specified.
  1909. */
  1910. private int getRenderIndex(View parentView, int childIndex) {
  1911. if (!checkedForStart) {
  1912. getStart(parentView);
  1913. }
  1914. int retIndex = childIndex;
  1915. for (int counter = childIndex; counter >= 0; counter--) {
  1916. AttributeSet as = parentView.getElement().getElement(counter).
  1917. getAttributes();
  1918. if (as.getAttribute(StyleConstants.NameAttribute) !=
  1919. HTML.Tag.LI) {
  1920. retIndex--;
  1921. } else if (as.isDefined(HTML.Attribute.VALUE)) {
  1922. Object value = as.getAttribute(HTML.Attribute.VALUE);
  1923. if (value != null &&
  1924. (value instanceof String)) {
  1925. try {
  1926. int iValue = Integer.parseInt((String)value);
  1927. return retIndex - counter + iValue;
  1928. }
  1929. catch (NumberFormatException nfe) {}
  1930. }
  1931. }
  1932. }
  1933. return retIndex + start;
  1934. }
  1935. /**
  1936. * Paints the CSS list decoration according to the
  1937. * attributes given.
  1938. *
  1939. * @param g the rendering surface.
  1940. * @param x the x coordinate of the list item allocation
  1941. * @param y the y coordinate of the list item allocation
  1942. * @param w the width of the list item allocation
  1943. * @param h the height of the list item allocation
  1944. * @param v the allocated area to paint into.
  1945. * @param item which list item is being painted. This
  1946. * is a number greater than or equal to 0.
  1947. */
  1948. public void paint(Graphics g, float x, float y, float w, float h, View v, int item) {
  1949. View cv = v.getView(item);
  1950. Object name = cv.getElement().getAttributes().getAttribute
  1951. (StyleConstants.NameAttribute);
  1952. // Only draw something if the View is a list item. This won't
  1953. // be the case for comments.
  1954. if (!(name instanceof HTML.Tag) ||
  1955. name != HTML.Tag.LI) {
  1956. return;
  1957. }
  1958. // deside on what side draw bullets, etc.
  1959. isLeftToRight =
  1960. cv.getContainer().getComponentOrientation().isLeftToRight();
  1961. // How the list indicator is aligned is not specified, it is
  1962. // left up to the UA. IE and NS differ on this behavior.
  1963. // This is closer to NS where we align to the first line of text.
  1964. // If the child is not text we draw the indicator at the
  1965. // origin (0).
  1966. float align = 0;
  1967. if (cv.getViewCount() > 0) {
  1968. View pView = cv.getView(0);
  1969. Object cName = pView.getElement().getAttributes().
  1970. getAttribute(StyleConstants.NameAttribute);
  1971. if ((cName == HTML.Tag.P || cName == HTML.Tag.IMPLIED) &&
  1972. pView.getViewCount() > 0) {
  1973. paintRect.setBounds((int)x, (int)y, (int)w, (int)h);
  1974. Shape shape = cv.getChildAllocation(0, paintRect);
  1975. if (shape != null && (shape = pView.getView(0).
  1976. getChildAllocation(0, shape)) != null) {
  1977. Rectangle rect = (shape instanceof Rectangle) ?
  1978. (Rectangle)shape : shape.getBounds();
  1979. align = pView.getView(0).getAlignment(View.Y_AXIS);
  1980. y = rect.y;
  1981. h = rect.height;
  1982. }
  1983. }
  1984. }
  1985. // set the color of a decoration
  1986. if (ss != null) {
  1987. g.setColor(ss.getForeground(cv.getAttributes()));
  1988. } else {
  1989. g.setColor(Color.black);
  1990. }
  1991. if (img != null) {
  1992. drawIcon(g, (int) x, (int) y, (int) w, (int) h, align,
  1993. v.getContainer());
  1994. return;
  1995. }
  1996. CSS.Value childtype = getChildType(cv);
  1997. Font font = ((StyledDocument)cv.getDocument()).
  1998. getFont(cv.getAttributes());
  1999. if (font != null) {
  2000. g.setFont(font);
  2001. }
  2002. if (childtype == CSS.Value.SQUARE || childtype == CSS.Value.CIRCLE
  2003. || childtype == CSS.Value.DISC) {
  2004. drawShape(g, childtype, (int) x, (int) y,
  2005. (int) w, (int) h, align);
  2006. } else if (childtype == CSS.Value.DECIMAL) {
  2007. drawLetter(g, '1', (int) x, (int) y, (int) w, (int) h, align,
  2008. getRenderIndex(v, item));
  2009. } else if (childtype == CSS.Value.LOWER_ALPHA) {
  2010. drawLetter(g, 'a', (int) x, (int) y, (int) w, (int) h, align,
  2011. getRenderIndex(v, item));
  2012. } else if (childtype == CSS.Value.UPPER_ALPHA) {
  2013. drawLetter(g, 'A', (int) x, (int) y, (int) w, (int) h, align,
  2014. getRenderIndex(v, item));
  2015. } else if (childtype == CSS.Value.LOWER_ROMAN) {
  2016. drawLetter(g, 'i', (int) x, (int) y, (int) w, (int) h, align,
  2017. getRenderIndex(v, item));
  2018. } else if (childtype == CSS.Value.UPPER_ROMAN) {
  2019. drawLetter(g, 'I', (int) x, (int) y, (int) w, (int) h, align,
  2020. getRenderIndex(v, item));
  2021. }
  2022. }
  2023. /**
  2024. * Draws the bullet icon specified by the list-style-image argument.
  2025. *
  2026. * @param g the graphics context
  2027. * @param ax x coordinate to place the bullet
  2028. * @param ay y coordinate to place the bullet
  2029. * @param aw width of the container the bullet is placed in
  2030. * @param ah height of the container the bullet is placed in
  2031. * @param align preferred alignment factor for the child view
  2032. */
  2033. void drawIcon(Graphics g, int ax, int ay, int aw, int ah,
  2034. float align, Component c) {
  2035. // Align to bottom of icon.
  2036. int gap = isLeftToRight ? - (img.getIconWidth() + bulletgap) :
  2037. (aw + bulletgap);
  2038. int x = ax + gap;
  2039. int y = Math.max(ay, ay + (int)(align * ah) -img.getIconHeight());
  2040. img.paintIcon(c, g, x, y);
  2041. }
  2042. /**
  2043. * Draws the graphical bullet item specified by the type argument.
  2044. *
  2045. * @param g the graphics context
  2046. * @param type type of bullet to draw (circle, square, disc)
  2047. * @param ax x coordinate to place the bullet
  2048. * @param ay y coordinate to place the bullet
  2049. * @param aw width of the container the bullet is placed in
  2050. * @param ah height of the container the bullet is placed in
  2051. * @param align preferred alignment factor for the child view
  2052. */
  2053. void drawShape(Graphics g, CSS.Value type, int ax, int ay, int aw,
  2054. int ah, float align) {
  2055. // Align to bottom of shape.
  2056. int gap = isLeftToRight ? - (bulletgap + 8) : (aw + bulletgap);
  2057. int x = ax + gap;
  2058. int y = Math.max(ay, ay + (int)(align * ah) - 8);
  2059. if (type == CSS.Value.SQUARE) {
  2060. g.drawRect(x, y, 8, 8);
  2061. } else if (type == CSS.Value.CIRCLE) {
  2062. g.drawOval(x, y, 8, 8);
  2063. } else {
  2064. g.fillOval(x, y, 8, 8);
  2065. }
  2066. }
  2067. /**
  2068. * Draws the letter or number for an ordered list.
  2069. *
  2070. * @param g the graphics context
  2071. * @param letter type of ordered list to draw
  2072. * @param ax x coordinate to place the bullet
  2073. * @param ay y coordinate to place the bullet
  2074. * @param aw width of the container the bullet is placed in
  2075. * @param ah height of the container the bullet is placed in
  2076. * @param index position of the list item in the list
  2077. */
  2078. void drawLetter(Graphics g, char letter, int ax, int ay, int aw,
  2079. int ah, float align, int index) {
  2080. String str = formatItemNum(index, letter);
  2081. str = isLeftToRight ? str + "." : "." + str;
  2082. FontMetrics fm = SwingUtilities2.getFontMetrics(null, g);
  2083. int stringwidth = SwingUtilities2.stringWidth(null, fm, str);
  2084. int gap = isLeftToRight ? - (stringwidth + bulletgap) :
  2085. (aw + bulletgap);
  2086. int x = ax + gap;
  2087. int y = Math.max(ay + fm.getAscent(), ay + (int)(ah * align));
  2088. SwingUtilities2.drawString(null, g, str, x, y);
  2089. }
  2090. /**
  2091. * Converts the item number into the ordered list number
  2092. * (i.e. 1 2 3, i ii iii, a b c, etc.
  2093. *
  2094. * @param itemNum number to format
  2095. * @param type type of ordered list
  2096. */
  2097. String formatItemNum(int itemNum, char type) {
  2098. String numStyle = "1";
  2099. boolean uppercase = false;
  2100. String formattedNum;
  2101. switch (type) {
  2102. case '1':
  2103. default:
  2104. formattedNum = String.valueOf(itemNum);
  2105. break;
  2106. case 'A':
  2107. uppercase = true;
  2108. // fall through
  2109. case 'a':
  2110. formattedNum = formatAlphaNumerals(itemNum);
  2111. break;
  2112. case 'I':
  2113. uppercase = true;
  2114. // fall through
  2115. case 'i':
  2116. formattedNum = formatRomanNumerals(itemNum);
  2117. }
  2118. if (uppercase) {
  2119. formattedNum = formattedNum.toUpperCase();
  2120. }
  2121. return formattedNum;
  2122. }
  2123. /**
  2124. * Converts the item number into an alphabetic character
  2125. *
  2126. * @param itemNum number to format
  2127. */
  2128. String formatAlphaNumerals(int itemNum) {
  2129. String result = "";
  2130. if (itemNum > 26) {
  2131. result = formatAlphaNumerals(itemNum / 26) +
  2132. formatAlphaNumerals(itemNum % 26);
  2133. } else {
  2134. // -1 because item is 1 based.
  2135. result = String.valueOf((char)('a' + itemNum - 1));
  2136. }
  2137. return result;
  2138. }
  2139. /* list of roman numerals */
  2140. static final char romanChars[][] = {
  2141. {'i', 'v'},
  2142. {'x', 'l' },
  2143. {'c', 'd' },
  2144. {'m', '?' },
  2145. };
  2146. /**
  2147. * Converts the item number into a roman numeral
  2148. *
  2149. * @param num number to format
  2150. */
  2151. String formatRomanNumerals(int num) {
  2152. return formatRomanNumerals(0, num);
  2153. }
  2154. /**
  2155. * Converts the item number into a roman numeral
  2156. *
  2157. * @param num number to format
  2158. */
  2159. String formatRomanNumerals(int level, int num) {
  2160. if (num < 10) {
  2161. return formatRomanDigit(level, num);
  2162. } else {
  2163. return formatRomanNumerals(level + 1, num / 10) +
  2164. formatRomanDigit(level, num % 10);
  2165. }
  2166. }
  2167. /**
  2168. * Converts the item number into a roman numeral
  2169. *
  2170. * @param level position
  2171. * @param num digit to format
  2172. */
  2173. String formatRomanDigit(int level, int digit) {
  2174. String result = "";
  2175. if (digit == 9) {
  2176. result = result + romanChars[level][0];
  2177. result = result + romanChars[level + 1][0];
  2178. return result;
  2179. } else if (digit == 4) {
  2180. result = result + romanChars[level][0];
  2181. result = result + romanChars[level][1];
  2182. return result;
  2183. } else if (digit >= 5) {
  2184. result = result + romanChars[level][1];
  2185. digit -= 5;
  2186. }
  2187. for (int i = 0; i < digit; i++) {
  2188. result = result + romanChars[level][0];
  2189. }
  2190. return result;
  2191. }
  2192. private Rectangle paintRect;
  2193. private boolean checkedForStart;
  2194. private int start;
  2195. private CSS.Value type;
  2196. URL imageurl;
  2197. private StyleSheet ss = null;
  2198. Icon img = null;
  2199. private int bulletgap = 5;
  2200. private boolean isLeftToRight;
  2201. }
  2202. /**
  2203. * Paints the background image.
  2204. */
  2205. static class BackgroundImagePainter implements Serializable {
  2206. ImageIcon backgroundImage;
  2207. float hPosition;
  2208. float vPosition;
  2209. // bit mask: 0 for repeat x, 1 for repeat y, 2 for horiz relative,
  2210. // 3 for vert relative
  2211. short flags;
  2212. // These are used when painting, updatePaintCoordinates updates them.
  2213. private int paintX;
  2214. private int paintY;
  2215. private int paintMaxX;
  2216. private int paintMaxY;
  2217. BackgroundImagePainter(AttributeSet a, CSS css, StyleSheet ss) {
  2218. backgroundImage = ss.getBackgroundImage(a);
  2219. // Determine the position.
  2220. CSS.BackgroundPosition pos = (CSS.BackgroundPosition)a.getAttribute
  2221. (CSS.Attribute.BACKGROUND_POSITION);
  2222. if (pos != null) {
  2223. hPosition = pos.getHorizontalPosition();
  2224. vPosition = pos.getVerticalPosition();
  2225. if (pos.isHorizontalPositionRelativeToSize()) {
  2226. flags |= 4;
  2227. }
  2228. else if (pos.isHorizontalPositionRelativeToSize()) {
  2229. hPosition *= css.getFontSize(a, 12, ss);
  2230. }
  2231. if (pos.isVerticalPositionRelativeToSize()) {
  2232. flags |= 8;
  2233. }
  2234. else if (pos.isVerticalPositionRelativeToFontSize()) {
  2235. vPosition *= css.getFontSize(a, 12, ss);
  2236. }
  2237. }
  2238. // Determine any repeating values.
  2239. CSS.Value repeats = (CSS.Value)a.getAttribute(CSS.Attribute.
  2240. BACKGROUND_REPEAT);
  2241. if (repeats == null || repeats == CSS.Value.BACKGROUND_REPEAT) {
  2242. flags |= 3;
  2243. }
  2244. else if (repeats == CSS.Value.BACKGROUND_REPEAT_X) {
  2245. flags |= 1;
  2246. }
  2247. else if (repeats == CSS.Value.BACKGROUND_REPEAT_Y) {
  2248. flags |= 2;
  2249. }
  2250. }
  2251. void paint(Graphics g, float x, float y, float w, float h, View v) {
  2252. Rectangle clip = g.getClipRect();
  2253. if (clip != null) {
  2254. // Constrain the clip so that images don't draw outside the
  2255. // legal bounds.
  2256. g.clipRect((int)x, (int)y, (int)w, (int)h);
  2257. }
  2258. if ((flags & 3) == 0) {
  2259. // no repeating
  2260. int width = backgroundImage.getIconWidth();
  2261. int height = backgroundImage.getIconWidth();
  2262. if ((flags & 4) == 4) {
  2263. paintX = (int)(x + w * hPosition -
  2264. (float)width * hPosition);
  2265. }
  2266. else {
  2267. paintX = (int)x + (int)hPosition;
  2268. }
  2269. if ((flags & 8) == 8) {
  2270. paintY = (int)(y + h * vPosition -
  2271. (float)height * vPosition);
  2272. }
  2273. else {
  2274. paintY = (int)y + (int)vPosition;
  2275. }
  2276. if (clip == null ||
  2277. !((paintX + width <= clip.x) ||
  2278. (paintY + height <= clip.y) ||
  2279. (paintX >= clip.x + clip.width) ||
  2280. (paintY >= clip.y + clip.height))) {
  2281. backgroundImage.paintIcon(null, g, paintX, paintY);
  2282. }
  2283. }
  2284. else {
  2285. int width = backgroundImage.getIconWidth();
  2286. int height = backgroundImage.getIconHeight();
  2287. if (width > 0 && height > 0) {
  2288. paintX = (int)x;
  2289. paintY = (int)y;
  2290. paintMaxX = (int)(x + w);
  2291. paintMaxY = (int)(y + h);
  2292. if (updatePaintCoordinates(clip, width, height)) {
  2293. while (paintX < paintMaxX) {
  2294. int ySpot = paintY;
  2295. while (ySpot < paintMaxY) {
  2296. backgroundImage.paintIcon(null, g, paintX,
  2297. ySpot);
  2298. ySpot += height;
  2299. }
  2300. paintX += width;
  2301. }
  2302. }
  2303. }
  2304. }
  2305. if (clip != null) {
  2306. // Reset clip.
  2307. g.setClip(clip.x, clip.y, clip.width, clip.height);
  2308. }
  2309. }
  2310. private boolean updatePaintCoordinates
  2311. (Rectangle clip, int width, int height){
  2312. if ((flags & 3) == 1) {
  2313. paintMaxY = paintY + 1;
  2314. }
  2315. else if ((flags & 3) == 2) {
  2316. paintMaxX = paintX + 1;
  2317. }
  2318. if (clip != null) {
  2319. if ((flags & 3) == 1 && ((paintY + height <= clip.y) ||
  2320. (paintY > clip.y + clip.height))) {
  2321. // not visible.
  2322. return false;
  2323. }
  2324. if ((flags & 3) == 2 && ((paintX + width <= clip.x) ||
  2325. (paintX > clip.x + clip.width))) {
  2326. // not visible.
  2327. return false;
  2328. }
  2329. if ((flags & 1) == 1) {
  2330. if ((clip.x + clip.width) < paintMaxX) {
  2331. if ((clip.x + clip.width - paintX) % width == 0) {
  2332. paintMaxX = clip.x + clip.width;
  2333. }
  2334. else {
  2335. paintMaxX = ((clip.x + clip.width - paintX) /
  2336. width + 1) * width + paintX;
  2337. }
  2338. }
  2339. if (clip.x > paintX) {
  2340. paintX = (clip.x - paintX) / width * width + paintX;
  2341. }
  2342. }
  2343. if ((flags & 2) == 2) {
  2344. if ((clip.y + clip.height) < paintMaxY) {
  2345. if ((clip.y + clip.height - paintY) % height == 0) {
  2346. paintMaxY = clip.y + clip.height;
  2347. }
  2348. else {
  2349. paintMaxY = ((clip.y + clip.height - paintY) /
  2350. height + 1) * height + paintY;
  2351. }
  2352. }
  2353. if (clip.y > paintY) {
  2354. paintY = (clip.y - paintY) / height * height + paintY;
  2355. }
  2356. }
  2357. }
  2358. // Valid
  2359. return true;
  2360. }
  2361. }
  2362. /**
  2363. * A subclass of MuxingAttributeSet that translates between
  2364. * CSS and HTML and StyleConstants. The AttributeSets used are
  2365. * the CSS rules that match the Views Elements.
  2366. */
  2367. class ViewAttributeSet extends MuxingAttributeSet {
  2368. ViewAttributeSet(View v) {
  2369. host = v;
  2370. // PENDING(prinz) fix this up to be a more realistic
  2371. // implementation.
  2372. Document doc = v.getDocument();
  2373. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  2374. Vector muxList = sb.getVector();
  2375. try {
  2376. if (doc instanceof HTMLDocument) {
  2377. StyleSheet styles = StyleSheet.this;
  2378. Element elem = v.getElement();
  2379. AttributeSet a = elem.getAttributes();
  2380. AttributeSet htmlAttr = styles.translateHTMLToCSS(a);
  2381. if (htmlAttr.getAttributeCount() != 0) {
  2382. muxList.addElement(htmlAttr);
  2383. }
  2384. if (elem.isLeaf()) {
  2385. Enumeration keys = a.getAttributeNames();
  2386. while (keys.hasMoreElements()) {
  2387. Object key = keys.nextElement();
  2388. if (key instanceof HTML.Tag) {
  2389. if ((HTML.Tag)key == HTML.Tag.A) {
  2390. Object o = a.getAttribute((HTML.Tag)key);
  2391. /**
  2392. In the case of an A tag, the css rules
  2393. apply only for tags that have their
  2394. href attribute defined and not for
  2395. anchors that only have their name attributes
  2396. defined, i.e anchors that function as
  2397. destinations. Hence we do not add the
  2398. attributes for that latter kind of
  2399. anchors. When CSS2 support is added,
  2400. it will be possible to specificity this
  2401. kind of conditional behaviour in the
  2402. stylesheet.
  2403. **/
  2404. if (o != null && o instanceof AttributeSet) {
  2405. AttributeSet attr = (AttributeSet)o;
  2406. if (attr.getAttribute(HTML.Attribute.HREF) == null) {
  2407. continue;
  2408. }
  2409. }
  2410. }
  2411. AttributeSet cssRule = styles.getRule((HTML.Tag) key, elem);
  2412. if (cssRule != null) {
  2413. muxList.addElement(cssRule);
  2414. }
  2415. }
  2416. }
  2417. } else {
  2418. HTML.Tag t = (HTML.Tag) a.getAttribute
  2419. (StyleConstants.NameAttribute);
  2420. AttributeSet cssRule = styles.getRule(t, elem);
  2421. if (cssRule != null) {
  2422. muxList.addElement(cssRule);
  2423. }
  2424. }
  2425. }
  2426. AttributeSet[] attrs = new AttributeSet[muxList.size()];
  2427. muxList.copyInto(attrs);
  2428. setAttributes(attrs);
  2429. }
  2430. finally {
  2431. SearchBuffer.releaseSearchBuffer(sb);
  2432. }
  2433. }
  2434. // --- AttributeSet methods ----------------------------
  2435. /**
  2436. * Checks whether a given attribute is defined.
  2437. * This will convert the key over to CSS if the
  2438. * key is a StyleConstants key that has a CSS
  2439. * mapping.
  2440. *
  2441. * @param key the attribute key
  2442. * @return true if the attribute is defined
  2443. * @see AttributeSet#isDefined
  2444. */
  2445. public boolean isDefined(Object key) {
  2446. if (key instanceof StyleConstants) {
  2447. Object cssKey = css.styleConstantsKeyToCSSKey
  2448. ((StyleConstants)key);
  2449. if (cssKey != null) {
  2450. key = cssKey;
  2451. }
  2452. }
  2453. return super.isDefined(key);
  2454. }
  2455. /**
  2456. * Gets the value of an attribute. If the requested
  2457. * attribute is a StyleConstants attribute that has
  2458. * a CSS mapping, the request will be converted.
  2459. *
  2460. * @param key the attribute name
  2461. * @return the attribute value
  2462. * @see AttributeSet#getAttribute
  2463. */
  2464. public Object getAttribute(Object key) {
  2465. if (key instanceof StyleConstants) {
  2466. Object cssKey = css.styleConstantsKeyToCSSKey
  2467. ((StyleConstants)key);
  2468. if (cssKey != null) {
  2469. Object value = doGetAttribute(cssKey);
  2470. if (value instanceof CSS.CssValue) {
  2471. return ((CSS.CssValue)value).toStyleConstants
  2472. ((StyleConstants)key, host);
  2473. }
  2474. }
  2475. }
  2476. return doGetAttribute(key);
  2477. }
  2478. Object doGetAttribute(Object key) {
  2479. Object retValue = super.getAttribute(key);
  2480. if (retValue != null) {
  2481. return retValue;
  2482. }
  2483. // didn't find it... try parent if it's a css attribute
  2484. // that is inherited.
  2485. if (key instanceof CSS.Attribute) {
  2486. CSS.Attribute css = (CSS.Attribute) key;
  2487. if (css.isInherited()) {
  2488. AttributeSet parent = getResolveParent();
  2489. if (parent != null)
  2490. return parent.getAttribute(key);
  2491. }
  2492. }
  2493. return null;
  2494. }
  2495. /**
  2496. * If not overriden, the resolving parent defaults to
  2497. * the parent element.
  2498. *
  2499. * @return the attributes from the parent
  2500. * @see AttributeSet#getResolveParent
  2501. */
  2502. public AttributeSet getResolveParent() {
  2503. if (host == null) {
  2504. return null;
  2505. }
  2506. View parent = host.getParent();
  2507. return (parent != null) ? parent.getAttributes() : null;
  2508. }
  2509. /** View created for. */
  2510. View host;
  2511. }
  2512. /**
  2513. * A subclass of MuxingAttributeSet that implements Style. Currently
  2514. * the MutableAttributeSet methods are unimplemented, that is they
  2515. * do nothing.
  2516. */
  2517. // PENDING(sky): Decide what to do with this. Either make it
  2518. // contain a SimpleAttributeSet that modify methods are delegated to,
  2519. // or change getRule to return an AttributeSet and then don't make this
  2520. // implement Style.
  2521. static class ResolvedStyle extends MuxingAttributeSet implements
  2522. Serializable, Style {
  2523. ResolvedStyle(String name, AttributeSet[] attrs, int extendedIndex) {
  2524. super(attrs);
  2525. this.name = name;
  2526. this.extendedIndex = extendedIndex;
  2527. }
  2528. /**
  2529. * Inserts a Style into the receiver so that the styles the
  2530. * receiver represents are still ordered by specificity.
  2531. * <code>style</code> will be added before any extended styles, that
  2532. * is before extendedIndex.
  2533. */
  2534. synchronized void insertStyle(Style style, int specificity) {
  2535. AttributeSet[] attrs = getAttributes();
  2536. int maxCounter = attrs.length;
  2537. int counter = 0;
  2538. for (;counter < extendedIndex; counter++) {
  2539. if (specificity > getSpecificity(((Style)attrs[counter]).
  2540. getName())) {
  2541. break;
  2542. }
  2543. }
  2544. insertAttributeSetAt(style, counter);
  2545. extendedIndex++;
  2546. }
  2547. /**
  2548. * Removes a previously added style. This will do nothing if
  2549. * <code>style</code> is not referenced by the receiver.
  2550. */
  2551. synchronized void removeStyle(Style style) {
  2552. AttributeSet[] attrs = getAttributes();
  2553. for (int counter = attrs.length - 1; counter >= 0; counter--) {
  2554. if (attrs[counter] == style) {
  2555. removeAttributeSetAt(counter);
  2556. if (counter < extendedIndex) {
  2557. extendedIndex--;
  2558. }
  2559. break;
  2560. }
  2561. }
  2562. }
  2563. /**
  2564. * Adds <code>s</code> as one of the Attributesets to look up
  2565. * attributes in.
  2566. */
  2567. synchronized void insertExtendedStyleAt(Style attr, int index) {
  2568. insertAttributeSetAt(attr, extendedIndex + index);
  2569. }
  2570. /**
  2571. * Adds <code>s</code> as one of the AttributeSets to look up
  2572. * attributes in. It will be the AttributeSet last checked.
  2573. */
  2574. synchronized void addExtendedStyle(Style attr) {
  2575. insertAttributeSetAt(attr, getAttributes().length);
  2576. }
  2577. /**
  2578. * Removes the style at <code>index</code> +
  2579. * <code>extendedIndex</code>.
  2580. */
  2581. synchronized void removeExtendedStyleAt(int index) {
  2582. removeAttributeSetAt(extendedIndex + index);
  2583. }
  2584. /**
  2585. * Returns true if the receiver matches <code>selector</code>, where
  2586. * a match is defined by the CSS rule matching.
  2587. * Each simple selector must be separated by a single space.
  2588. */
  2589. protected boolean matches(String selector) {
  2590. int sLast = selector.length();
  2591. if (sLast == 0) {
  2592. return false;
  2593. }
  2594. int thisLast = name.length();
  2595. int sCurrent = selector.lastIndexOf(' ');
  2596. int thisCurrent = name.lastIndexOf(' ');
  2597. if (sCurrent >= 0) {
  2598. sCurrent++;
  2599. }
  2600. if (thisCurrent >= 0) {
  2601. thisCurrent++;
  2602. }
  2603. if (!matches(selector, sCurrent, sLast, thisCurrent, thisLast)) {
  2604. return false;
  2605. }
  2606. while (sCurrent != -1) {
  2607. sLast = sCurrent - 1;
  2608. sCurrent = selector.lastIndexOf(' ', sLast - 1);
  2609. if (sCurrent >= 0) {
  2610. sCurrent++;
  2611. }
  2612. boolean match = false;
  2613. while (!match && thisCurrent != -1) {
  2614. thisLast = thisCurrent - 1;
  2615. thisCurrent = name.lastIndexOf(' ', thisLast - 1);
  2616. if (thisCurrent >= 0) {
  2617. thisCurrent++;
  2618. }
  2619. match = matches(selector, sCurrent, sLast, thisCurrent,
  2620. thisLast);
  2621. }
  2622. if (!match) {
  2623. return false;
  2624. }
  2625. }
  2626. return true;
  2627. }
  2628. /**
  2629. * Returns true if the substring of the receiver, in the range
  2630. * thisCurrent, thisLast matches the substring of selector in
  2631. * the ranme sCurrent to sLast based on CSS selector matching.
  2632. */
  2633. boolean matches(String selector, int sCurrent, int sLast,
  2634. int thisCurrent, int thisLast) {
  2635. sCurrent = Math.max(sCurrent, 0);
  2636. thisCurrent = Math.max(thisCurrent, 0);
  2637. int thisDotIndex = boundedIndexOf(name, '.', thisCurrent,
  2638. thisLast);
  2639. int thisPoundIndex = boundedIndexOf(name, '#', thisCurrent,
  2640. thisLast);
  2641. int sDotIndex = boundedIndexOf(selector, '.', sCurrent, sLast);
  2642. int sPoundIndex = boundedIndexOf(selector, '#', sCurrent, sLast);
  2643. if (sDotIndex != -1) {
  2644. // Selector has a '.', which indicates name must match it,
  2645. // or if the '.' starts the selector than name must have
  2646. // the same class (doesn't matter what element name).
  2647. if (thisDotIndex == -1) {
  2648. return false;
  2649. }
  2650. if (sCurrent == sDotIndex) {
  2651. if ((thisLast - thisDotIndex) != (sLast - sDotIndex) ||
  2652. !selector.regionMatches(sCurrent, name, thisDotIndex,
  2653. (thisLast - thisDotIndex))) {
  2654. return false;
  2655. }
  2656. }
  2657. else {
  2658. // Has to fully match.
  2659. if ((sLast - sCurrent) != (thisLast - thisCurrent) ||
  2660. !selector.regionMatches(sCurrent, name, thisCurrent,
  2661. (thisLast - thisCurrent))) {
  2662. return false;
  2663. }
  2664. }
  2665. return true;
  2666. }
  2667. if (sPoundIndex != -1) {
  2668. // Selector has a '#', which indicates name must match it,
  2669. // or if the '#' starts the selector than name must have
  2670. // the same id (doesn't matter what element name).
  2671. if (thisPoundIndex == -1) {
  2672. return false;
  2673. }
  2674. if (sCurrent == sPoundIndex) {
  2675. if ((thisLast - thisPoundIndex) !=(sLast - sPoundIndex) ||
  2676. !selector.regionMatches(sCurrent, name, thisPoundIndex,
  2677. (thisLast - thisPoundIndex))) {
  2678. return false;
  2679. }
  2680. }
  2681. else {
  2682. // Has to fully match.
  2683. if ((sLast - sCurrent) != (thisLast - thisCurrent) ||
  2684. !selector.regionMatches(sCurrent, name, thisCurrent,
  2685. (thisLast - thisCurrent))) {
  2686. return false;
  2687. }
  2688. }
  2689. return true;
  2690. }
  2691. if (thisDotIndex != -1) {
  2692. // Reciever references a class, just check element name.
  2693. return (((thisDotIndex - thisCurrent) == (sLast - sCurrent)) &&
  2694. selector.regionMatches(sCurrent, name, thisCurrent,
  2695. thisDotIndex - thisCurrent));
  2696. }
  2697. if (thisPoundIndex != -1) {
  2698. // Reciever references an id, just check element name.
  2699. return (((thisPoundIndex - thisCurrent) ==(sLast - sCurrent))&&
  2700. selector.regionMatches(sCurrent, name, thisCurrent,
  2701. thisPoundIndex - thisCurrent));
  2702. }
  2703. // Fail through, no classes or ides, just check string.
  2704. return (((thisLast - thisCurrent) == (sLast - sCurrent)) &&
  2705. selector.regionMatches(sCurrent, name, thisCurrent,
  2706. thisLast - thisCurrent));
  2707. }
  2708. /**
  2709. * Similiar to String.indexOf, but allows an upper bound
  2710. * (this is slower in that it will still check string starting at
  2711. * start.
  2712. */
  2713. int boundedIndexOf(String string, char search, int start,
  2714. int end) {
  2715. int retValue = string.indexOf(search, start);
  2716. if (retValue >= end) {
  2717. return -1;
  2718. }
  2719. return retValue;
  2720. }
  2721. public void addAttribute(Object name, Object value) {}
  2722. public void addAttributes(AttributeSet attributes) {}
  2723. public void removeAttribute(Object name) {}
  2724. public void removeAttributes(Enumeration<?> names) {}
  2725. public void removeAttributes(AttributeSet attributes) {}
  2726. public void setResolveParent(AttributeSet parent) {}
  2727. public String getName() {return name;}
  2728. public void addChangeListener(ChangeListener l) {}
  2729. public void removeChangeListener(ChangeListener l) {}
  2730. public ChangeListener[] getChangeListeners() {
  2731. return new ChangeListener[0];
  2732. }
  2733. /** The name of the Style, which is the selector.
  2734. * This will NEVER change!
  2735. */
  2736. String name;
  2737. /** Start index of styles coming from other StyleSheets. */
  2738. private int extendedIndex;
  2739. }
  2740. /**
  2741. * SelectorMapping contains a specifitiy, as an integer, and an associated
  2742. * Style. It can also reference children <code>SelectorMapping</code>s,
  2743. * so that it behaves like a tree.
  2744. * <p>
  2745. * This is not thread safe, it is assumed the caller will take the
  2746. * necessary precations if this is to be used in a threaded environment.
  2747. */
  2748. static class SelectorMapping implements Serializable {
  2749. public SelectorMapping(int specificity) {
  2750. this.specificity = specificity;
  2751. }
  2752. /**
  2753. * Returns the specificity this mapping represents.
  2754. */
  2755. public int getSpecificity() {
  2756. return specificity;
  2757. }
  2758. /**
  2759. * Sets the Style associated with this mapping.
  2760. */
  2761. public void setStyle(Style style) {
  2762. this.style = style;
  2763. }
  2764. /**
  2765. * Returns the Style associated with this mapping.
  2766. */
  2767. public Style getStyle() {
  2768. return style;
  2769. }
  2770. /**
  2771. * Returns the child mapping identified by the simple selector
  2772. * <code>selector</code>. If a child mapping does not exist for
  2773. *<code>selector</code>, and <code>create</code> is true, a new
  2774. * one will be created.
  2775. */
  2776. public SelectorMapping getChildSelectorMapping(String selector,
  2777. boolean create) {
  2778. SelectorMapping retValue = null;
  2779. if (children != null) {
  2780. retValue = (SelectorMapping)children.get(selector);
  2781. }
  2782. else if (create) {
  2783. children = new HashMap(7);
  2784. }
  2785. if (retValue == null && create) {
  2786. int specificity = getChildSpecificity(selector);
  2787. retValue = createChildSelectorMapping(specificity);
  2788. children.put(selector, retValue);
  2789. }
  2790. return retValue;
  2791. }
  2792. /**
  2793. * Creates a child <code>SelectorMapping</code> with the specified
  2794. * <code>specificity</code>.
  2795. */
  2796. protected SelectorMapping createChildSelectorMapping(int specificity) {
  2797. return new SelectorMapping(specificity);
  2798. }
  2799. /**
  2800. * Returns the specificity for the child selector
  2801. * <code>selector</code>.
  2802. */
  2803. protected int getChildSpecificity(String selector) {
  2804. // class (.) 100
  2805. // id (#) 10000
  2806. char firstChar = selector.charAt(0);
  2807. int specificity = getSpecificity();
  2808. if (firstChar == '.') {
  2809. specificity += 100;
  2810. }
  2811. else if (firstChar == '#') {
  2812. specificity += 10000;
  2813. }
  2814. else {
  2815. specificity += 1;
  2816. if (selector.indexOf('.') != -1) {
  2817. specificity += 100;
  2818. }
  2819. if (selector.indexOf('#') != -1) {
  2820. specificity += 10000;
  2821. }
  2822. }
  2823. return specificity;
  2824. }
  2825. /**
  2826. * The specificity for this selector.
  2827. */
  2828. private int specificity;
  2829. /**
  2830. * Style for this selector.
  2831. */
  2832. private Style style;
  2833. /**
  2834. * Any sub selectors. Key will be String, and value will be
  2835. * another SelectorMapping.
  2836. */
  2837. private HashMap children;
  2838. }
  2839. // ---- Variables ---------------------------------------------
  2840. final static int DEFAULT_FONT_SIZE = 3;
  2841. private CSS css;
  2842. /**
  2843. * An inverted graph of the selectors.
  2844. */
  2845. private SelectorMapping selectorMapping;
  2846. /** Maps from selector (as a string) to Style that includes all
  2847. * relevant styles. */
  2848. private Hashtable resolvedStyles;
  2849. /** Vector of StyleSheets that the rules are to reference.
  2850. */
  2851. private Vector linkedStyleSheets;
  2852. /** Where the style sheet was found. Used for relative imports. */
  2853. private URL base;
  2854. /**
  2855. * Default parser for CSS specifications that get loaded into
  2856. * the StyleSheet.<p>
  2857. * This class is NOT thread safe, do not ask it to parse while it is
  2858. * in the middle of parsing.
  2859. */
  2860. class CssParser implements CSSParser.CSSParserCallback {
  2861. /**
  2862. * Parses the passed in CSS declaration into an AttributeSet.
  2863. */
  2864. public AttributeSet parseDeclaration(String string) {
  2865. try {
  2866. return parseDeclaration(new StringReader(string));
  2867. } catch (IOException ioe) {}
  2868. return null;
  2869. }
  2870. /**
  2871. * Parses the passed in CSS declaration into an AttributeSet.
  2872. */
  2873. public AttributeSet parseDeclaration(Reader r) throws IOException {
  2874. parse(base, r, true, false);
  2875. return declaration.copyAttributes();
  2876. }
  2877. /**
  2878. * Parse the given CSS stream
  2879. */
  2880. public void parse(URL base, Reader r, boolean parseDeclaration,
  2881. boolean isLink) throws IOException {
  2882. this.base = base;
  2883. this.isLink = isLink;
  2884. this.parsingDeclaration = parseDeclaration;
  2885. declaration.removeAttributes(declaration);
  2886. selectorTokens.removeAllElements();
  2887. selectors.removeAllElements();
  2888. propertyName = null;
  2889. parser.parse(r, this, parseDeclaration);
  2890. }
  2891. //
  2892. // CSSParserCallback methods, public to implement the interface.
  2893. //
  2894. /**
  2895. * Invoked when a valid @import is encountered, will call
  2896. * <code>importStyleSheet</code> if a
  2897. * <code>MalformedURLException</code> is not thrown in creating
  2898. * the URL.
  2899. */
  2900. public void handleImport(String importString) {
  2901. URL url = CSS.getURL(base, importString);
  2902. if (url != null) {
  2903. importStyleSheet(url);
  2904. }
  2905. }
  2906. /**
  2907. * A selector has been encountered.
  2908. */
  2909. public void handleSelector(String selector) {
  2910. selector = selector.toLowerCase();
  2911. int length = selector.length();
  2912. if (selector.endsWith(",")) {
  2913. if (length > 1) {
  2914. selector = selector.substring(0, length - 1);
  2915. selectorTokens.addElement(selector);
  2916. }
  2917. addSelector();
  2918. }
  2919. else if (length > 0) {
  2920. selectorTokens.addElement(selector);
  2921. }
  2922. }
  2923. /**
  2924. * Invoked when the start of a rule is encountered.
  2925. */
  2926. public void startRule() {
  2927. if (selectorTokens.size() > 0) {
  2928. addSelector();
  2929. }
  2930. propertyName = null;
  2931. }
  2932. /**
  2933. * Invoked when a property name is encountered.
  2934. */
  2935. public void handleProperty(String property) {
  2936. propertyName = property;
  2937. }
  2938. /**
  2939. * Invoked when a property value is encountered.
  2940. */
  2941. public void handleValue(String value) {
  2942. if (propertyName != null && value != null && value.length() > 0) {
  2943. CSS.Attribute cssKey = CSS.getAttribute(propertyName);
  2944. if (cssKey != null) {
  2945. // There is currently no mechanism to determine real
  2946. // base that style sheet was loaded from. For the time
  2947. // being, this maps for LIST_STYLE_IMAGE, which appear
  2948. // to be the only one that currently matters. A more
  2949. // general mechanism is definately needed.
  2950. if (cssKey == CSS.Attribute.LIST_STYLE_IMAGE) {
  2951. if (value != null && !value.equals("none")) {
  2952. URL url = CSS.getURL(base, value);
  2953. if (url != null) {
  2954. value = url.toString();
  2955. }
  2956. }
  2957. }
  2958. addCSSAttribute(declaration, cssKey, value);
  2959. }
  2960. propertyName = null;
  2961. }
  2962. }
  2963. /**
  2964. * Invoked when the end of a rule is encountered.
  2965. */
  2966. public void endRule() {
  2967. int n = selectors.size();
  2968. for (int i = 0; i < n; i++) {
  2969. String[] selector = (String[]) selectors.elementAt(i);
  2970. if (selector.length > 0) {
  2971. StyleSheet.this.addRule(selector, declaration, isLink);
  2972. }
  2973. }
  2974. declaration.removeAttributes(declaration);
  2975. selectors.removeAllElements();
  2976. }
  2977. private void addSelector() {
  2978. String[] selector = new String[selectorTokens.size()];
  2979. selectorTokens.copyInto(selector);
  2980. selectors.addElement(selector);
  2981. selectorTokens.removeAllElements();
  2982. }
  2983. Vector selectors = new Vector();
  2984. Vector selectorTokens = new Vector();
  2985. /** Name of the current property. */
  2986. String propertyName;
  2987. MutableAttributeSet declaration = new SimpleAttributeSet();
  2988. /** True if parsing a declaration, that is the Reader will not
  2989. * contain a selector. */
  2990. boolean parsingDeclaration;
  2991. /** True if the attributes are coming from a linked/imported style. */
  2992. boolean isLink;
  2993. /** Where the CSS stylesheet lives. */
  2994. URL base;
  2995. CSSParser parser = new CSSParser();
  2996. }
  2997. void rebaseSizeMap(int base) {
  2998. final int minimalFontSize = 4;
  2999. sizeMap = new int[sizeMapDefault.length];
  3000. for (int i = 0; i < sizeMapDefault.length; i++) {
  3001. sizeMap[i] = Math.max(base * sizeMapDefault[i] /
  3002. sizeMapDefault[CSS.baseFontSizeIndex],
  3003. minimalFontSize);
  3004. }
  3005. }
  3006. int[] getSizeMap() {
  3007. return sizeMap;
  3008. }
  3009. boolean isW3CLengthUnits() {
  3010. return w3cLengthUnits;
  3011. }
  3012. /**
  3013. * The HTML/CSS size model has seven slots
  3014. * that one can assign sizes to.
  3015. */
  3016. static int sizeMapDefault[] = { 8, 10, 12, 14, 18, 24, 36 };
  3017. private int sizeMap[] = sizeMapDefault;
  3018. private boolean w3cLengthUnits = false;
  3019. }