0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: /**
0018: * @author Alexey A. Ivanov
0019: * @version $Revision$
0020: */package javax.swing.text.html;
0021:
0022: import java.awt.BasicStroke;
0023: import java.awt.Color;
0024: import java.awt.Font;
0025: import java.awt.FontMetrics;
0026: import java.awt.Graphics;
0027: import java.awt.Graphics2D;
0028: import java.awt.Image;
0029: import java.awt.Rectangle;
0030: import java.awt.Stroke;
0031: import java.io.IOException;
0032: import java.io.InputStream;
0033: import java.io.Reader;
0034: import java.io.Serializable;
0035: import java.io.StringReader;
0036: import java.net.URL;
0037: import java.util.Enumeration;
0038: import java.util.HashMap;
0039: import java.util.Iterator;
0040: import java.util.LinkedList;
0041: import java.util.List;
0042: import java.util.Map;
0043: import java.util.regex.Pattern;
0044:
0045: import javax.swing.SwingUtilities;
0046: import javax.swing.text.AttributeSet;
0047: import javax.swing.text.BadLocationException;
0048: import javax.swing.text.BoxView;
0049: import javax.swing.text.Element;
0050: import javax.swing.text.JTextComponent;
0051: import javax.swing.text.MutableAttributeSet;
0052: import javax.swing.text.SimpleAttributeSet;
0053: import javax.swing.text.Style;
0054: import javax.swing.text.StyleConstants;
0055: import javax.swing.text.StyleContext;
0056: import javax.swing.text.View;
0057: import javax.swing.text.html.CSS.Attribute;
0058: import javax.swing.text.html.CSS.BorderColor;
0059: import javax.swing.text.html.CSS.BorderStyle;
0060: import javax.swing.text.html.CSS.ColorProperty;
0061: import javax.swing.text.html.CSS.PropertyValueConverter;
0062: import javax.swing.text.html.CSS.ShorthandPropertyExpander;
0063: import javax.swing.text.html.CSS.TextDecoration;
0064: import javax.swing.text.html.CSS.ViewUpdater;
0065:
0066: import org.apache.harmony.x.swing.Utilities;
0067: import org.apache.harmony.x.swing.text.html.cssparser.CSSParser;
0068: import org.apache.harmony.x.swing.text.html.cssparser.ParseException;
0069: import org.apache.harmony.x.swing.text.html.cssparser.metamodel.Property;
0070: import org.apache.harmony.x.swing.text.html.cssparser.metamodel.RuleSet;
0071: import org.apache.harmony.x.swing.text.html.cssparser.metamodel.Sheet;
0072:
0073: import org.apache.harmony.x.swing.internal.nls.Messages;
0074:
0075: /**
0076: * Implementation of this class is based on <a href="http://www.w3.org/TR/CSS1">CSS1</a>.
0077: */
0078: public class StyleSheet extends StyleContext {
0079:
0080: public static class BoxPainter implements Serializable {
0081: private static final CSS.Attribute[] MARGIN_KEYS = new CSS.Attribute[] {
0082: CSS.Attribute.MARGIN_TOP, CSS.Attribute.MARGIN_RIGHT,
0083: CSS.Attribute.MARGIN_BOTTOM, CSS.Attribute.MARGIN_LEFT };
0084: private static final CSS.Attribute[] PADDING_KEYS = new CSS.Attribute[] {
0085: CSS.Attribute.PADDING_TOP, CSS.Attribute.PADDING_RIGHT,
0086: CSS.Attribute.PADDING_BOTTOM,
0087: CSS.Attribute.PADDING_LEFT };
0088: private static final CSS.Attribute[] BORDER_WIDTH_KEYS = new CSS.Attribute[] {
0089: CSS.Attribute.BORDER_TOP_WIDTH,
0090: CSS.Attribute.BORDER_RIGHT_WIDTH,
0091: CSS.Attribute.BORDER_BOTTOM_WIDTH,
0092: CSS.Attribute.BORDER_LEFT_WIDTH };
0093:
0094: private View view;
0095: private final AttributeSet attr;
0096: private final StyleSheet styleSheet;
0097:
0098: private final float[] margin = new float[4];
0099: private final float[] borderWidth = new float[4];
0100:
0101: private CSS.BorderStyle borderStyle;
0102: private CSS.BorderColor borderColor;
0103: private CSS.ColorProperty backgroundColor;
0104: private CSS.ImageValue backgroundImage;
0105: private CSS.BackgroundRepeat backgroundRepeat;
0106: private Color foreground;
0107: private ViewUpdater updater;
0108: private boolean isTableBoxPainter;
0109:
0110: private BoxPainter(final AttributeSet attr,
0111: final StyleSheet styleSheet) {
0112: this .attr = attr;
0113: this .styleSheet = styleSheet;
0114: }
0115:
0116: public float getInset(final int side, final View v) {
0117: final int sideIndex = getSideIndex(side);
0118: CSS.Length length = (CSS.Length) v.getAttributes()
0119: .getAttribute(PADDING_KEYS[sideIndex]);
0120: return margin[sideIndex]
0121: + (length != null ? length.floatValue(v) : 0)
0122: + getBorderSideWidth(sideIndex, v);
0123: }
0124:
0125: public void paint(final Graphics g, final float x,
0126: final float y, final float w, final float h,
0127: final View v) {
0128: Color oldColor = g.getColor();
0129:
0130: updateProperties();
0131:
0132: if (!(g instanceof Graphics2D)) {
0133: System.err.println(Messages.getString("swing.err.09")); //$NON-NLS-1$
0134: }
0135: Graphics2D g2d = (Graphics2D) g;
0136:
0137: int xx = (int) x;
0138: int yy = (int) y;
0139: int ww = (int) w;
0140: int hh = (int) h;
0141: if (isTableBoxPainter) {
0142: int captionHeight = ((TableTagView) view)
0143: .getCaptionHeight();
0144: yy += captionHeight;
0145: hh -= captionHeight;
0146: }
0147: xx += margin[CSS.LEFT_SIDE];
0148: yy += margin[CSS.TOP_SIDE];
0149: ww -= (margin[CSS.LEFT_SIDE] + margin[CSS.RIGHT_SIDE]);
0150: hh -= (margin[CSS.TOP_SIDE] + margin[CSS.BOTTOM_SIDE]);
0151:
0152: if (backgroundImage != null
0153: && backgroundImage != CSS.ImageValue.NONE) {
0154:
0155: backgroundImage.loadImage(styleSheet.getBase());
0156: final Image image = backgroundImage.getImage();
0157: if (image != null) {
0158: paintBackgroundImage(image, g, xx, yy, ww, hh);
0159: } else {
0160: if (updater == null) {
0161: updater = new ViewUpdater() {
0162: public void updateView() {
0163: Rectangle rc;
0164: JTextComponent tc = (JTextComponent) view
0165: .getContainer();
0166: if (tc != null) {
0167: try {
0168: rc = tc.modelToView(view
0169: .getStartOffset());
0170: } catch (BadLocationException e) {
0171: return;
0172: }
0173: tc
0174: .repaint(
0175: rc.x,
0176: rc.y,
0177: ((BoxView) view)
0178: .getWidth(),
0179: ((BoxView) view)
0180: .getHeight());
0181: }
0182: }
0183: };
0184: }
0185: backgroundImage.addListener(updater);
0186: }
0187: } else if (backgroundColor != null
0188: && backgroundColor.getColor() != null) {
0189: g.setColor(backgroundColor.getColor());
0190: g.fillRect(xx, yy, ww, hh);
0191: }
0192:
0193: if (borderStyle != null) {
0194: Color bc;
0195: Stroke saveStroke = g2d.getStroke();
0196: Stroke bs;
0197:
0198: if (shouldDrawSide(CSS.BOTTOM_SIDE)) {
0199: bc = getSideColor(CSS.BOTTOM_SIDE);
0200: bs = getSideStroke(CSS.BOTTOM_SIDE);
0201: g2d.setColor(bc);
0202: g2d.setStroke(bs);
0203: g2d.drawLine(xx, yy + hh - 1, xx + ww - 1, yy + hh
0204: - 1);
0205: }
0206: if (shouldDrawSide(CSS.LEFT_SIDE)) {
0207: bc = getSideColor(CSS.LEFT_SIDE);
0208: bs = getSideStroke(CSS.LEFT_SIDE);
0209: g2d.setColor(bc);
0210: g2d.setStroke(bs);
0211: g2d.drawLine(xx, yy, xx, yy + hh - 1);
0212: }
0213: if (shouldDrawSide(CSS.TOP_SIDE)) {
0214: bc = getSideColor(CSS.TOP_SIDE);
0215: bs = getSideStroke(CSS.TOP_SIDE);
0216: g2d.setColor(bc);
0217: g2d.setStroke(bs);
0218: g2d.drawLine(xx, yy, xx + ww - 1, yy);
0219: }
0220: if (shouldDrawSide(CSS.RIGHT_SIDE)) {
0221: bc = getSideColor(CSS.RIGHT_SIDE);
0222: bs = getSideStroke(CSS.RIGHT_SIDE);
0223: g2d.setColor(bc);
0224: g2d.setStroke(bs);
0225: g2d.drawLine(xx + ww - 1, yy, xx + ww - 1, yy + hh
0226: - 1);
0227: }
0228:
0229: g2d.setStroke(saveStroke);
0230: }
0231:
0232: g.setColor(oldColor);
0233: foreground = null;
0234: }
0235:
0236: final void setView(final View view) {
0237: this .view = view;
0238: isTableBoxPainter = view instanceof TableTagView;
0239: updateMargin();
0240: }
0241:
0242: float getTopMargin() {
0243: return margin[CSS.TOP_SIDE];
0244: }
0245:
0246: float getRightMargin() {
0247: return margin[CSS.RIGHT_SIDE];
0248: }
0249:
0250: float getBottomMargin() {
0251: return margin[CSS.BOTTOM_SIDE];
0252: }
0253:
0254: float getLeftMargin() {
0255: return margin[CSS.LEFT_SIDE];
0256: }
0257:
0258: private static float getBorderSideWidth(final int side,
0259: final View view, final AttributeSet attrs) {
0260: CSS.Length length = (CSS.Length) attrs
0261: .getAttribute(BORDER_WIDTH_KEYS[side]);
0262: return length != null ? length.floatValue(view)
0263: : CSS.BorderWidthValue.factory.floatValue();
0264: }
0265:
0266: private float getBorderSideWidth(final int side, final View view) {
0267: final AttributeSet viewAttr = view.getAttributes();
0268: CSS.BorderStyle borderStyle = (BorderStyle) viewAttr
0269: .getAttribute(CSS.Attribute.BORDER_STYLE);
0270: if (borderStyle == null
0271: || borderStyle.getSideStyle(side).getIndex() == CSS.BorderStyleValue.NONE) {
0272:
0273: return 0;
0274: }
0275:
0276: return getBorderSideWidth(side, view, viewAttr);
0277: }
0278:
0279: private Color getSideColor(final int i) {
0280: Color result = null;
0281: if (borderColor != null) {
0282: result = (Color) borderColor.getSideColor(i).fromCSS();
0283: }
0284:
0285: if (result == null) {
0286: if (foreground == null) {
0287: ColorProperty cp = (ColorProperty) attr
0288: .getAttribute(CSS.Attribute.COLOR);
0289: foreground = cp != null ? cp.getColor()
0290: : Color.BLACK;
0291: }
0292: result = foreground;
0293: }
0294: return result;
0295: }
0296:
0297: private int getSideIndex(final int side) {
0298: switch (side) {
0299: case SwingUtilities.TOP:
0300: return CSS.TOP_SIDE;
0301:
0302: case SwingUtilities.RIGHT:
0303: return CSS.RIGHT_SIDE;
0304:
0305: case SwingUtilities.BOTTOM:
0306: return CSS.BOTTOM_SIDE;
0307:
0308: case SwingUtilities.LEFT:
0309: return CSS.LEFT_SIDE;
0310:
0311: default:
0312: throw new IllegalArgumentException(Messages.getString(
0313: "swing.A4", side)); //$NON-NLS-1$
0314: }
0315: }
0316:
0317: private Stroke getSideStroke(final int side) {
0318: return new BasicStroke(borderWidth[side]);
0319: }
0320:
0321: private boolean shouldDrawSide(final int side) {
0322: return borderStyle.getSideStyle(side).getIndex() != 0
0323: && borderWidth[side] > 0;
0324: }
0325:
0326: private void paintBackgroundImage(final Image image,
0327: final Graphics g, final int x, final int y,
0328: final int w, final int h) {
0329: if (image == null) {
0330: return;
0331: }
0332:
0333: final int imageWidth = backgroundImage.getWidth();
0334: final int imageHeight = backgroundImage.getHeight();
0335:
0336: final Image offscreen = view.getContainer()
0337: .createVolatileImage(w, h);
0338: final Graphics offG = offscreen.getGraphics();
0339:
0340: if (backgroundColor != null
0341: && backgroundColor.getColor() != null) {
0342:
0343: offG.setColor(backgroundColor.getColor());
0344: offG.fillRect(x, y, w, h);
0345: }
0346:
0347: final int repeat = backgroundRepeat != null ? backgroundRepeat
0348: .getIndex()
0349: : 0;
0350: switch (repeat) {
0351: default:
0352: case CSS.BackgroundRepeat.REPEAT:
0353: for (int ix = 0; ix < w; ix += imageWidth) {
0354: for (int iy = 0; iy < h; iy += imageHeight) {
0355: offG.drawImage(image, ix, iy, null);
0356: }
0357: }
0358: break;
0359:
0360: case CSS.BackgroundRepeat.REPEAT_X:
0361: for (int ix = 0; ix < w; ix += imageWidth) {
0362: offG.drawImage(image, ix, 0, null);
0363: }
0364: break;
0365:
0366: case CSS.BackgroundRepeat.REPEAT_Y:
0367: for (int iy = 0; iy < h; iy += imageHeight) {
0368: offG.drawImage(image, 0, iy, null);
0369: }
0370: break;
0371:
0372: case CSS.BackgroundRepeat.NO_REPEAT:
0373: offG.drawImage(image, 0, 0, null);
0374: break;
0375: }
0376:
0377: g.drawImage(offscreen, x, y, null);
0378:
0379: offG.dispose();
0380: offscreen.flush();
0381: }
0382:
0383: private void updateMargin() {
0384: for (int i = 0; i < margin.length; i++) {
0385: CSS.Length length = (CSS.Length) attr
0386: .getAttribute(MARGIN_KEYS[i]);
0387: margin[i] = length != null ? length.floatValue(view)
0388: : 0;
0389: }
0390: }
0391:
0392: private void updateBorderWidth() {
0393: for (int i = 0; i < borderWidth.length; i++) {
0394: borderWidth[i] = getBorderSideWidth(i, view, attr);
0395: }
0396: }
0397:
0398: private void updateProperties() {
0399: updateMargin();
0400:
0401: borderStyle = (BorderStyle) attr
0402: .getAttribute(CSS.Attribute.BORDER_STYLE);
0403: if (borderStyle != null) {
0404: borderColor = (BorderColor) attr
0405: .getAttribute(CSS.Attribute.BORDER_COLOR);
0406: updateBorderWidth();
0407: }
0408:
0409: backgroundImage = (CSS.ImageValue) attr
0410: .getAttribute(CSS.Attribute.BACKGROUND_IMAGE);
0411: backgroundRepeat = backgroundImage == null ? null
0412: : (CSS.BackgroundRepeat) attr
0413: .getAttribute(CSS.Attribute.BACKGROUND_REPEAT);
0414: backgroundColor = (CSS.ColorProperty) attr
0415: .getAttribute(CSS.Attribute.BACKGROUND_COLOR);
0416: }
0417: }
0418:
0419: public static class ListPainter implements Serializable {
0420: private static final float DECORATOR_MARGIN = 5;
0421: private static final String DISC = "\u25CF";
0422: private static final String CIRCLE = "\u25CB";
0423: private static final String SQUARE = "\u25A0";
0424:
0425: // private final AttributeSet attr;
0426: // private final CSS.ListStyleType listStyle;
0427: // private final CSS.ListStyleImage listImage;
0428:
0429: private ListPainter(final AttributeSet attr) {
0430: // this.attr = attr;
0431: // listStyle =
0432: // (CSS.ListStyleType)attr.getAttribute(Attribute.LIST_STYLE_TYPE);
0433: // TODO ListPainter: handle listImage
0434: // attr.getAttribute(Attribute.LIST_STYLE_IMAGE);
0435: }
0436:
0437: public void paint(final Graphics g, final float x,
0438: final float y, final float w, final float h,
0439: final View v, final int item) {
0440: final View child = v.getView(item);
0441: if (!(child instanceof BlockView)) {
0442: return;
0443: }
0444:
0445: final AttributeSet attr = child.getAttributes();
0446: final StyleSheet ss = ((BlockView) child).getStyleSheet();
0447: Font font = ss.getFont(attr);
0448: final Color color = ss.getForeground(attr);
0449: final CSS.ListStyleType listStyle = (CSS.ListStyleType) attr
0450: .getAttribute(Attribute.LIST_STYLE_TYPE);
0451:
0452: String decorator = null;
0453:
0454: int index;
0455: if (listStyle == null) {
0456: final String name = v.getElement().getName();
0457: if (HTML.Tag.OL.toString().equals(name)) {
0458: index = CSS.ListStyleType.LIST_STYLE_DECIMAL;
0459: } else if (HTML.Tag.UL.toString().equals(name)) {
0460: index = CSS.ListStyleType.LIST_STYLE_DISC;
0461: } else {
0462: index = CSS.ListStyleType.LIST_STYLE_NONE;
0463: }
0464: } else {
0465: index = listStyle.getIndex();
0466: }
0467:
0468: switch (index) {
0469: case CSS.ListStyleType.LIST_STYLE_DISC:
0470: decorator = DISC;
0471: font = font.deriveFont(font.getSize2D() * 0.7f);
0472: break;
0473:
0474: case CSS.ListStyleType.LIST_STYLE_CIRCLE:
0475: decorator = CIRCLE;
0476: font = font.deriveFont(font.getSize2D() * 0.7f);
0477: break;
0478:
0479: case CSS.ListStyleType.LIST_STYLE_SQUARE:
0480: decorator = SQUARE;
0481: font = font.deriveFont(font.getSize2D() * 0.7f);
0482: break;
0483:
0484: case CSS.ListStyleType.LIST_STYLE_DECIMAL:
0485: decorator = Integer.toString(item + 1) + ".";
0486: break;
0487:
0488: case CSS.ListStyleType.LIST_STYLE_LOWER_ROMAN:
0489: decorator = Integer.toString(item + 1) + "r.";
0490: break;
0491: case CSS.ListStyleType.LIST_STYLE_UPPER_ROMAN:
0492: decorator = Integer.toString(item + 1) + "R.";
0493: break;
0494:
0495: case CSS.ListStyleType.LIST_STYLE_LOWER_ALPHA:
0496: decorator = (char) ('a' + item) + ".";
0497: break;
0498: case CSS.ListStyleType.LIST_STYLE_UPPER_ALPHA:
0499: decorator = (char) ('A' + item) + ".";
0500: break;
0501:
0502: case CSS.ListStyleType.LIST_STYLE_NONE:
0503: default:
0504: }
0505:
0506: if (decorator == null) {
0507: return;
0508: }
0509:
0510: Color oldColor = g.getColor();
0511: Font oldFont = g.getFont();
0512:
0513: g.setColor(color);
0514: g.setFont(font);
0515: FontMetrics metrics = g.getFontMetrics(font);
0516: int width = metrics.stringWidth(decorator);
0517:
0518: Rectangle pAlloc = new Rectangle((int) x, (int) y, (int) w,
0519: (int) h);
0520: pAlloc = (Rectangle) child.getChildAllocation(0, pAlloc);
0521:
0522: g.drawString(decorator,
0523: (int) (x - width - DECORATOR_MARGIN),
0524: (int) (pAlloc.y
0525: + pAlloc.height
0526: * child.getView(0)
0527: .getAlignment(View.Y_AXIS)
0528: + metrics.getHeight() / 2f - metrics
0529: .getDescent()));
0530:
0531: g.setFont(oldFont);
0532: g.setColor(oldColor);
0533: }
0534: }
0535:
0536: final class SmallConverterSet extends SmallAttributeSet {
0537: public SmallConverterSet(final AttributeSet attrs) {
0538: super (attrs);
0539: }
0540:
0541: public SmallConverterSet(final Object[] attrs) {
0542: super (attrs);
0543: }
0544:
0545: public Object getAttribute(final Object key) {
0546: Object cssKey = CSS.mapToCSS(key);
0547: Object result = super .getAttribute(cssKey != null ? cssKey
0548: : key);
0549: if (!(cssKey instanceof Attribute)) {
0550: if (key == StyleConstants.Underline
0551: || key == StyleConstants.StrikeThrough) {
0552:
0553: return getTextDecoration(this , key);
0554: }
0555: return result;
0556: }
0557: if (result == null) {
0558: return super .getAttribute(key);
0559: }
0560:
0561: return ((PropertyValueConverter) result).fromCSS();
0562: }
0563:
0564: public boolean isDefined(final Object key) {
0565: if (key == StyleConstants.Underline
0566: || key == StyleConstants.StrikeThrough) {
0567:
0568: return super .isDefined(Attribute.TEXT_DECORATION);
0569: }
0570: Object cssKey = CSS.mapToCSS(key);
0571: return super .isDefined(cssKey != null ? cssKey : key);
0572: }
0573: }
0574:
0575: final class LargeConverterSet extends SimpleAttributeSet {
0576:
0577: LargeConverterSet(final AttributeSet source) {
0578: super (source);
0579: }
0580:
0581: public Object getAttribute(final Object key) {
0582: Object cssKey = CSS.mapToCSS(key);
0583: Object result = super .getAttribute(cssKey != null ? cssKey
0584: : key);
0585: if (!(cssKey instanceof Attribute)) {
0586: if (key == StyleConstants.Underline
0587: || key == StyleConstants.StrikeThrough) {
0588:
0589: return getTextDecoration(this , key);
0590: }
0591: return result;
0592: }
0593: if (result == null) {
0594: return super .getAttribute(key);
0595: }
0596:
0597: return ((PropertyValueConverter) result).fromCSS();
0598: }
0599:
0600: public boolean isDefined(final Object key) {
0601: if (key == StyleConstants.Underline
0602: || key == StyleConstants.StrikeThrough) {
0603:
0604: return super .isDefined(Attribute.TEXT_DECORATION);
0605: }
0606: Object cssKey = CSS.mapToCSS(key);
0607: return super .isDefined(cssKey != null ? cssKey : key);
0608: }
0609: }
0610:
0611: private final class ResultStyleHashMap extends HashMap {
0612: public CascadedStyle get(final String key) {
0613: return (CascadedStyle) super .get(key);
0614: }
0615:
0616: public void put(final Style resStyle) {
0617: super .put(resStyle.getName(), resStyle);
0618: }
0619: }
0620:
0621: private final class NameConverterEnumeration implements Enumeration {
0622: private final List attrKeys = new LinkedList();
0623: private final Iterator it;
0624:
0625: private final boolean underline;
0626: private final boolean lineThrough;
0627:
0628: NameConverterEnumeration(final AttributeSet toModify,
0629: final AttributeSet attrSet) {
0630: final Enumeration keys = attrSet.getAttributeNames();
0631: boolean ul = false;
0632: boolean lt = false;
0633: while (keys.hasMoreElements()) {
0634: Object key = keys.nextElement();
0635: Object value = attrSet.getAttribute(key);
0636: if (key == StyleConstants.Underline) {
0637: ul = ((Boolean) value).booleanValue();
0638: } else if (key == StyleConstants.StrikeThrough) {
0639: lt = ((Boolean) value).booleanValue();
0640: } else if (toModify.containsAttribute(key, value)) {
0641: attrKeys.add(key);
0642: }
0643: }
0644:
0645: it = attrKeys.iterator();
0646:
0647: underline = ul;
0648: lineThrough = lt;
0649: }
0650:
0651: NameConverterEnumeration(final Enumeration names) {
0652: boolean ul = false;
0653: boolean lt = false;
0654: while (names.hasMoreElements()) {
0655: Object key = names.nextElement();
0656: if (key == StyleConstants.Underline) {
0657: ul = true;
0658: } else if (key == StyleConstants.StrikeThrough) {
0659: lt = true;
0660: } else {
0661: attrKeys.add(key);
0662: }
0663: }
0664:
0665: it = attrKeys.iterator();
0666:
0667: underline = ul;
0668: lineThrough = lt;
0669: }
0670:
0671: public boolean hasMoreElements() {
0672: return it.hasNext();
0673: }
0674:
0675: public Object nextElement() {
0676: Object key = it.next();
0677: Object cssKey = CSS.mapToCSS(key);
0678: return cssKey != null ? cssKey : key;
0679: }
0680:
0681: boolean isUnderline() {
0682: return underline;
0683: }
0684:
0685: boolean isLineThrough() {
0686: return lineThrough;
0687: }
0688: }
0689:
0690: private static final Pattern relativePattern = Pattern
0691: .compile("(?:\\+|-)\\d+");
0692:
0693: private static final int DEFAULT_BASE_FONT_SIZE = 4;
0694:
0695: private static final Map HTML_ATTRIBUTE_TO_CSS = new HashMap();
0696:
0697: private URL base;
0698: private int baseFontSize = DEFAULT_BASE_FONT_SIZE;
0699:
0700: private CSSParser parser;
0701:
0702: private ResultStyleHashMap resultStyles = new ResultStyleHashMap();
0703: private List styleSheets = new LinkedList();
0704:
0705: static {
0706: HTML_ATTRIBUTE_TO_CSS.put(HTML.Attribute.ALIGN,
0707: CSS.Attribute.TEXT_ALIGN);
0708: HTML_ATTRIBUTE_TO_CSS.put(HTML.Attribute.BACKGROUND,
0709: CSS.Attribute.BACKGROUND_IMAGE);
0710: HTML_ATTRIBUTE_TO_CSS.put(HTML.Attribute.BGCOLOR,
0711: CSS.Attribute.BACKGROUND_COLOR);
0712: // HTML_ATTRIBUTE_TO_CSS.put(HTML.Attribute.BORDER,
0713: // CSS.Attribute.BORDER);
0714: HTML_ATTRIBUTE_TO_CSS.put(HTML.Attribute.TEXT,
0715: CSS.Attribute.COLOR);
0716:
0717: HTML_ATTRIBUTE_TO_CSS.put(HTML.Attribute.COLOR,
0718: CSS.Attribute.COLOR);
0719: HTML_ATTRIBUTE_TO_CSS.put(HTML.Attribute.FACE,
0720: CSS.Attribute.FONT_FAMILY);
0721:
0722: // HTML_ATTRIBUTE_TO_CSS.put(HTML.Attribute.HALIGN,
0723: // CSS.Attribute.TEXT_ALIGN);
0724: HTML_ATTRIBUTE_TO_CSS.put(HTML.Attribute.HEIGHT,
0725: CSS.Attribute.HEIGHT);
0726:
0727: // HTML_ATTRIBUTE_TO_CSS.put(HTML.Attribute.NOWRAP,
0728: // CSS.Attribute.WHITE_SPACE);
0729:
0730: HTML_ATTRIBUTE_TO_CSS.put(HTML.Attribute.VALIGN,
0731: CSS.Attribute.VERTICAL_ALIGN);
0732:
0733: HTML_ATTRIBUTE_TO_CSS.put(HTML.Attribute.WIDTH,
0734: CSS.Attribute.WIDTH);
0735:
0736: // HTML_ATTRIBUTE_TO_CSS.put(HTML.Attribute.LINK,
0737: // CSS.Attribute.COLOR);
0738: // HTML_ATTRIBUTE_TO_CSS.put(HTML.Attribute.ALINK,
0739: // CSS.Attribute.COLOR);
0740: // HTML_ATTRIBUTE_TO_CSS.put(HTML.Attribute.VLINK,
0741: // CSS.Attribute.COLOR);
0742: }
0743:
0744: public static int getIndexOfSize(final float size) {
0745: return CSS.FontSize.sizeValueIndex((int) size) + 1;
0746: }
0747:
0748: public AttributeSet addAttribute(final AttributeSet old,
0749: final Object key, final Object value) {
0750: Attribute cssKey = (Attribute) CSS.mapToCSSForced(key);
0751: if (cssKey == null || cssKey.getConverter() == null) {
0752: if (key == StyleConstants.Underline
0753: || key == StyleConstants.StrikeThrough) {
0754:
0755: return super .addAttribute(old,
0756: CSS.Attribute.TEXT_DECORATION,
0757: createTextDecoration(old, key, value));
0758: }
0759: return super .addAttribute(old, key, value);
0760: }
0761: return super .addAttribute(old, cssKey,
0762: value instanceof PropertyValueConverter ? value
0763: : cssKey.getConverter().toCSS(value));
0764: }
0765:
0766: public AttributeSet addAttributes(final AttributeSet old,
0767: final AttributeSet attr) {
0768: MutableAttributeSet converted = new SimpleAttributeSet(old);
0769: Enumeration attrKeys = attr.getAttributeNames();
0770: while (attrKeys.hasMoreElements()) {
0771: Object key = attrKeys.nextElement();
0772: Object value = attr.getAttribute(key);
0773: Attribute cssKey = (Attribute) CSS.mapToCSSForced(key);
0774: if (cssKey == null) {
0775: if (key == StyleConstants.Underline
0776: || key == StyleConstants.StrikeThrough) {
0777:
0778: value = createTextDecoration(converted, key, value);
0779: key = CSS.Attribute.TEXT_DECORATION;
0780: }
0781: converted.addAttribute(key, value);
0782: } else {
0783: if (!(value instanceof CSS.PropertyValueConverter)) {
0784: value = cssKey.getConverter().toCSS(value);
0785: }
0786: if (value != null) {
0787: converted.addAttribute(cssKey, value);
0788: }
0789: }
0790: }
0791: return super .addAttributes(getEmptySet(), converted);
0792: }
0793:
0794: public AttributeSet removeAttribute(final AttributeSet old,
0795: final Object key) {
0796: if (key == StyleConstants.Underline
0797: || key == StyleConstants.StrikeThrough) {
0798:
0799: TextDecoration td = (TextDecoration) old
0800: .getAttribute(Attribute.TEXT_DECORATION);
0801: td = (TextDecoration) td.clone();
0802: if (key == StyleConstants.Underline && td.isUnderline()) {
0803: td.setUnderline(false);
0804: }
0805: if (key == StyleConstants.StrikeThrough
0806: && td.isLineThrough()) {
0807: td.setLineThrough(false);
0808: }
0809: if (td.isNone()) {
0810: return super .removeAttribute(old,
0811: Attribute.TEXT_DECORATION);
0812: }
0813: return super .addAttribute(old, Attribute.TEXT_DECORATION,
0814: td);
0815: }
0816:
0817: Attribute cssKey = (Attribute) CSS.mapToCSSForced(key);
0818: if (cssKey == null) {
0819: return super .removeAttribute(old, key);
0820: }
0821: return super .removeAttribute(old, cssKey);
0822: }
0823:
0824: public AttributeSet removeAttributes(final AttributeSet old,
0825: final AttributeSet rem) {
0826: return localRemoveAttributes(old, new NameConverterEnumeration(
0827: old, rem));
0828: }
0829:
0830: public AttributeSet removeAttributes(final AttributeSet old,
0831: final Enumeration<?> names) {
0832: return localRemoveAttributes(old, new NameConverterEnumeration(
0833: names));
0834: }
0835:
0836: public void addCSSAttribute(final MutableAttributeSet attr,
0837: final CSS.Attribute key, final String value) {
0838: if (key == null || Utilities.isEmptyString(value)) {
0839: return;
0840: }
0841: ShorthandPropertyExpander spe = key.getExpander();
0842: if (spe != null) {
0843: spe.parseAndExpandProperty(attr, value);
0844: } else {
0845: Object cssValue = key.getConverter().toCSS(value);
0846: if (cssValue != null) {
0847: attr.addAttribute(key, cssValue);
0848: }
0849: }
0850: }
0851:
0852: public boolean addCSSAttributeFromHTML(
0853: final MutableAttributeSet attr, final CSS.Attribute key,
0854: final String value) {
0855:
0856: final int count = attr.getAttributeCount();
0857: final Object attrValue = attr.getAttribute(key);
0858: if (key == CSS.Attribute.BACKGROUND_IMAGE) {
0859: addCSSAttribute(attr, key, "url(" + value + ")");
0860: } else {
0861: addCSSAttribute(attr, key, value);
0862: }
0863: return count != attr.getAttributeCount()
0864: || attrValue != attr.getAttribute(key);
0865: }
0866:
0867: public void addRule(final String rule) {
0868: if (Utilities.isEmptyString(rule)) {
0869: return;
0870: }
0871: initCSSParser(new StringReader(rule));
0872: parseSheet(false);
0873: }
0874:
0875: public void addStyleSheet(final StyleSheet ss) {
0876: if (!styleSheets.contains(ss)) {
0877: styleSheets.add(0, ss);
0878: addStyleSheetToCascadedStyles(ss);
0879: }
0880: }
0881:
0882: protected MutableAttributeSet createLargeAttributeSet(
0883: final AttributeSet attr) {
0884:
0885: return new LargeConverterSet(attr);
0886: }
0887:
0888: protected SmallAttributeSet createSmallAttributeSet(
0889: final AttributeSet attr) {
0890:
0891: return new SmallConverterSet(attr);
0892: }
0893:
0894: public BoxPainter getBoxPainter(final AttributeSet attr) {
0895: return new BoxPainter(attr, this );
0896: }
0897:
0898: public AttributeSet getDeclaration(final String decl) {
0899: if (Utilities.isEmptyString(decl)) {
0900: return new SimpleAttributeSet();
0901: }
0902:
0903: initCSSParser(new StringReader("htmlTag {" + decl + "}"));
0904: RuleSet rs = null;
0905: try {
0906: rs = parser.parseRuleSet();
0907: } catch (ParseException e) {
0908: e.printStackTrace();
0909: }
0910: MutableAttributeSet attrs = new SimpleAttributeSet();
0911: Iterator pi = rs.getProperties();
0912: while (pi.hasNext()) {
0913: Property property = (Property) pi.next();
0914: addCSSAttribute(attrs,
0915: CSS.getAttribute(property.getName()), property
0916: .getValue());
0917: }
0918:
0919: return attrs;
0920: }
0921:
0922: public Font getFont(final AttributeSet attr) {
0923: Object value;
0924:
0925: value = getCSSProperty(attr, Attribute.FONT_FAMILY);
0926: final String family = value != null ? (String) value
0927: : CSS.FontFamily.DEFAULT;
0928:
0929: value = attr.getAttribute(Attribute.FONT_SIZE);
0930: final int size = value != null ? ((CSS.Length) value)
0931: .intValue(attr) : CSS.FontSize.getDefaultValue()
0932: .intValue();
0933:
0934: value = getCSSProperty(attr, Attribute.FONT_WEIGHT);
0935: final boolean bold = value != null ? ((Boolean) value)
0936: .booleanValue() : false;
0937:
0938: value = getCSSProperty(attr, Attribute.FONT_STYLE);
0939: boolean italic = value != null ? ((Boolean) value)
0940: .booleanValue() : false;
0941:
0942: int style = Font.PLAIN;
0943: if (bold) {
0944: style |= Font.BOLD;
0945: }
0946: if (italic) {
0947: style |= Font.ITALIC;
0948: }
0949: return getFont(family, style, size);
0950: }
0951:
0952: public Color getForeground(final AttributeSet attr) {
0953: Object fore = getCSSProperty(attr, Attribute.COLOR);
0954: return fore != null ? (Color) fore : super
0955: .getForeground(getEmptySet());
0956: }
0957:
0958: public Color getBackground(final AttributeSet attr) {
0959: Object back = getCSSProperty(attr, Attribute.BACKGROUND_COLOR);
0960: return back != null ? (Color) back : null;
0961: }
0962:
0963: public ListPainter getListPainter(final AttributeSet attr) {
0964: return new ListPainter(attr);
0965: }
0966:
0967: public Style getRule(final String selector) {
0968: return getCascadedStyle(new Selector(selector).toString());
0969: }
0970:
0971: public Style getRule(final HTML.Tag tag, final Element element) {
0972: return getCascadedStyle(CascadedStyle
0973: .getElementTreeSelector(element));
0974: }
0975:
0976: public StyleSheet[] getStyleSheets() {
0977: return styleSheets.size() > 0 ? (StyleSheet[]) styleSheets
0978: .toArray(new StyleSheet[styleSheets.size()]) : null;
0979: }
0980:
0981: public AttributeSet getViewAttributes(final View v) {
0982: return new ViewAttributeSet(this , v);
0983: }
0984:
0985: public void importStyleSheet(final URL url) {
0986: // TODO importStyleSheet: use the URL specified to resolve references
0987: if (url == null) {
0988: return;
0989: }
0990:
0991: try {
0992: initCSSParser(url.openStream());
0993: } catch (IOException e) {
0994: e.printStackTrace();
0995: return;
0996: }
0997: parseSheet(true);
0998: }
0999:
1000: public void loadRules(final Reader in, final URL ref)
1001: throws IOException {
1002: // TODO loadRules: use the URL specified to resolve references
1003: initCSSParser(in);
1004: parseSheet(false);
1005: }
1006:
1007: public void removeStyle(final String name) {
1008: super .removeStyle(name);
1009: if (name != null) {
1010: removeStyleFromCascadedStyles(name);
1011: }
1012: }
1013:
1014: public void removeStyleSheet(final StyleSheet ss) {
1015: if (styleSheets.remove(ss)) {
1016: removeStyleSheetFromCascadedStyles(ss);
1017: }
1018: }
1019:
1020: public void setBase(final URL base) {
1021: this .base = base;
1022: }
1023:
1024: public URL getBase() {
1025: return base;
1026: }
1027:
1028: public void setBaseFontSize(final int index) {
1029: baseFontSize = Utilities.range(index, 1, 7);
1030: }
1031:
1032: public void setBaseFontSize(final String size) {
1033: setBaseFontSize(getRelativeIndex(size));
1034: }
1035:
1036: public float getPointSize(final int index) {
1037: return CSS.FontSize.SIZE_TABLE[Utilities.range(index - 1, 0, 6)]
1038: .intValue();
1039: }
1040:
1041: public float getPointSize(final String size) {
1042: return getPointSize(getRelativeIndex(size));
1043: }
1044:
1045: public Color stringToColor(final String colorNameOrHex) {
1046: return CSS.ColorProperty.stringToColor(colorNameOrHex);
1047: }
1048:
1049: public AttributeSet translateHTMLToCSS(
1050: final AttributeSet htmlAttrSet) {
1051: final Style result = addStyle(null, null);
1052: // if (((Element)htmlAttrSet).isLeaf()) {
1053: // return result;
1054: // }
1055:
1056: final Enumeration keys = htmlAttrSet.getAttributeNames();
1057: while (keys.hasMoreElements()) {
1058: Object key = keys.nextElement();
1059: Object value = htmlAttrSet.getAttribute(key);
1060: if (key instanceof CSS.Attribute) {
1061: result.addAttribute(key, value);
1062: } else {
1063: Object cssKey = HTML_ATTRIBUTE_TO_CSS.get(key);
1064: if (cssKey != null
1065: && ((Attribute) cssKey).getConverter() != null) {
1066:
1067: if ((key == HTML.Attribute.HEIGHT || key == HTML.Attribute.WIDTH)
1068: && value instanceof String
1069: && !((String) value).endsWith("%")) {
1070:
1071: value = ((Attribute) cssKey).getConverter()
1072: .toCSS((String) value + /*"px"*/"pt");
1073: } else if (key == HTML.Attribute.BACKGROUND) {
1074: value = ((Attribute) cssKey).getConverter()
1075: .toCSS("url(" + value + ")");
1076: } else {
1077: value = ((Attribute) cssKey).getConverter()
1078: .toCSS(value);
1079: }
1080:
1081: if (value != null) {
1082: result.addAttribute(cssKey, value);
1083: }
1084: }
1085: }
1086: }
1087: return result;
1088: }
1089:
1090: final Boolean getTextDecoration(final AttributeSet attr,
1091: final Object key) {
1092: TextDecoration value = (TextDecoration) attr
1093: .getAttribute(Attribute.TEXT_DECORATION);
1094: if (value == null) {
1095: return null;
1096: }
1097: if (key == StyleConstants.Underline) {
1098: return Boolean.valueOf(value.isUnderline());
1099: }
1100: if (key == StyleConstants.StrikeThrough) {
1101: return Boolean.valueOf(value.isLineThrough());
1102: }
1103: return null;
1104: }
1105:
1106: private Object createTextDecoration(final AttributeSet old,
1107: final Object key, final Object value) {
1108: if (value instanceof String) {
1109: return Attribute.TEXT_DECORATION.getConverter()
1110: .toCSS(value);
1111: }
1112: TextDecoration oldValue = (TextDecoration) old
1113: .getAttribute(Attribute.TEXT_DECORATION);
1114: TextDecoration result = oldValue == null ? new TextDecoration()
1115: : (TextDecoration) oldValue.clone();
1116: if (key == StyleConstants.Underline) {
1117: result.setUnderline(((Boolean) value).booleanValue());
1118: }
1119: if (key == StyleConstants.StrikeThrough) {
1120: result.setLineThrough(((Boolean) value).booleanValue());
1121: }
1122: return result;
1123: }
1124:
1125: private Object getCSSProperty(final AttributeSet attr,
1126: final CSS.Attribute key) {
1127: Object value = attr.getAttribute(key);
1128: if (value != null) {
1129: return ((PropertyValueConverter) value).fromCSS();
1130: }
1131: return null;
1132: }
1133:
1134: private int getRelativeIndex(final String size) {
1135: if (!relativePattern.matcher(size).matches()) {
1136: return Integer.parseInt(size);
1137: }
1138:
1139: int index = Integer.parseInt(size.substring(1));
1140: if (size.charAt(0) == '-') {
1141: index = -index;
1142: }
1143: return baseFontSize + index;
1144: }
1145:
1146: private void initCSSParser(final Reader reader) {
1147: if (parser == null) {
1148: parser = new CSSParser(reader);
1149: } else {
1150: parser.ReInit(reader);
1151: }
1152: }
1153:
1154: private void initCSSParser(final InputStream stream) {
1155: if (parser == null) {
1156: parser = new CSSParser(stream);
1157: } else {
1158: parser.ReInit(stream);
1159: }
1160: }
1161:
1162: private void parseSheet(final boolean asResolver) {
1163: // throw new UnsupportedOperationException(Messages.getString("swing.A5")); //$NON-NLS-1$
1164: Sheet ss = null;
1165: try {
1166: ss = parser.parse();
1167: } catch (ParseException e) {
1168: e.printStackTrace();
1169: }
1170: addImportedStyleSheets(ss.getImportsIterator(), asResolver);
1171: addParsedSheet(ss.getRuleSetIterator(), asResolver);
1172: }
1173:
1174: private void addImportedStyleSheets(final Iterator it,
1175: final boolean asResolver) {
1176: while (it.hasNext()) {
1177: importStyleSheet(HTML.resolveURL(extractURL((String) it
1178: .next()), getBase()));
1179: }
1180: }
1181:
1182: private void addParsedSheet(final Iterator it,
1183: final boolean asResolver) {
1184: while (it.hasNext()) {
1185: RuleSet rs = (RuleSet) it.next();
1186: MutableAttributeSet attrs = new SimpleAttributeSet();
1187: Iterator pi = rs.getProperties();
1188: while (pi.hasNext()) {
1189: Property property = (Property) pi.next();
1190: addCSSAttribute(attrs, CSS.getAttribute(property
1191: .getName()), property.getValue());
1192: }
1193: if (attrs.getAttributeCount() == 0) {
1194: continue;
1195: }
1196:
1197: Iterator si = rs.getSelectors();
1198: while (si.hasNext()) {
1199: String selector = new Selector((String) si.next())
1200: .toString();
1201: Style rr = getStyle(selector);
1202: if (rr == null) {
1203: rr = addStyle(selector, null);
1204: addStyleToCascadedStyles(selector);
1205: }
1206: if (asResolver) {
1207: final AttributeSet resolver = rr.getResolveParent();
1208: final MutableAttributeSet importedAttrs = resolver instanceof MutableAttributeSet ? (MutableAttributeSet) resolver
1209: : addStyle(null, null);
1210: importedAttrs.addAttributes(attrs);
1211: rr.setResolveParent(importedAttrs);
1212: } else {
1213: rr.addAttributes(attrs);
1214: }
1215: }
1216: }
1217: }
1218:
1219: private void addStyleToCascadedStyles(final String styleName) {
1220: final Iterator it = resultStyles.values().iterator();
1221: while (it.hasNext()) {
1222: CascadedStyle rs = (CascadedStyle) it.next();
1223: rs.addStyle(styleName);
1224: }
1225: }
1226:
1227: private void removeStyleFromCascadedStyles(final String styleName) {
1228: final Iterator it = resultStyles.values().iterator();
1229: while (it.hasNext()) {
1230: CascadedStyle rs = (CascadedStyle) it.next();
1231: rs.removeStyle(styleName);
1232: }
1233: }
1234:
1235: private void addStyleSheetToCascadedStyles(
1236: final StyleSheet styleSheet) {
1237: final Iterator it = resultStyles.values().iterator();
1238: while (it.hasNext()) {
1239: CascadedStyle rs = (CascadedStyle) it.next();
1240: rs.addStyleSheet(styleSheet);
1241: }
1242: }
1243:
1244: private void removeStyleSheetFromCascadedStyles(
1245: final StyleSheet styleSheet) {
1246: final Iterator it = resultStyles.values().iterator();
1247: while (it.hasNext()) {
1248: CascadedStyle rs = (CascadedStyle) it.next();
1249: rs.removeStyleSheet(styleSheet);
1250: }
1251: }
1252:
1253: private Style getCascadedStyle(final String selector) {
1254: Style result = resultStyles.get(selector);
1255: if (result != null) {
1256: return result;
1257: }
1258: result = new CascadedStyle(this , selector, SelectorMatcher
1259: .findMatching(getStyleNames(), selector), styleSheets
1260: .iterator());
1261: resultStyles.put(result);
1262: return result;
1263: }
1264:
1265: private AttributeSet localRemoveAttributes(
1266: final AttributeSet toModify,
1267: final NameConverterEnumeration keys) {
1268: if (!keys.isUnderline() && !keys.isLineThrough()) {
1269: return super .removeAttributes(toModify, keys);
1270: }
1271:
1272: final MutableAttributeSet result = new SimpleAttributeSet(
1273: toModify);
1274: result.removeAttributes(keys);
1275: TextDecoration td = (TextDecoration) result
1276: .getAttribute(Attribute.TEXT_DECORATION);
1277: td = (TextDecoration) td.clone();
1278:
1279: if (keys.isUnderline() && td.isUnderline()) {
1280: td.setUnderline(false);
1281: }
1282: if (keys.isLineThrough() && td.isLineThrough()) {
1283: td.setLineThrough(false);
1284: }
1285: if (td.isNone()) {
1286: result.removeAttribute(Attribute.TEXT_DECORATION);
1287: } else {
1288: result.addAttribute(Attribute.TEXT_DECORATION, td);
1289: }
1290: return super .addAttributes(getEmptySet(), result);
1291: }
1292:
1293: private String extractURL(final String importPath) {
1294: String result = importPath;
1295: if (result.startsWith("url(") && result.endsWith(")")) {
1296: result = result.substring(4, result.length() - 1);
1297: }
1298: char c = result.charAt(0);
1299: if (c == '\'' || c == '"') {
1300: result = result.substring(1, result.length() - 1);
1301: }
1302: return result;
1303: }
1304: }
|