package com.servoy.j2db.util; import java.awt.Color; import java.awt.Font; import java.awt.Insets; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.swing.border.Border; import javax.swing.text.AttributeSet; import javax.swing.text.html.CSS; import javax.swing.text.html.CSS.Attribute; import org.xhtmlrenderer.css.constants.CSSName; import org.xhtmlrenderer.css.newmatch.Matcher; import org.xhtmlrenderer.css.newmatch.Selector; import org.xhtmlrenderer.css.parser.CSSErrorHandler; import org.xhtmlrenderer.css.parser.CSSParser; import org.xhtmlrenderer.css.parser.PropertyValue; import org.xhtmlrenderer.css.sheet.PropertyDeclaration; import org.xhtmlrenderer.css.sheet.Ruleset; import org.xhtmlrenderer.css.sheet.Stylesheet; public class ServoyStyleSheet implements IStyleSheet { public static final String[] BORDER_CSS = new String[] { CSSName.BORDER_BOTTOM_COLOR.toString(), CSSName.BORDER_BOTTOM_SHORTHAND.toString(), CSSName.BORDER_BOTTOM_STYLE.toString(), CSSName.BORDER_BOTTOM_WIDTH.toString(), CSSName.BORDER_TOP_COLOR.toString(), CSSName.BORDER_TOP_SHORTHAND.toString(), CSSName.BORDER_TOP_STYLE.toString(), CSSName.BORDER_TOP_WIDTH.toString(), CSSName.BORDER_LEFT_COLOR.toString(), CSSName.BORDER_LEFT_SHORTHAND.toString(), CSSName.BORDER_LEFT_STYLE.toString(), CSSName.BORDER_LEFT_WIDTH.toString(), CSSName.BORDER_RIGHT_COLOR.toString(), CSSName.BORDER_RIGHT_SHORTHAND.toString(), CSSName.BORDER_RIGHT_STYLE.toString(), CSSName.BORDER_RIGHT_WIDTH.toString(), CSSName.BORDER_COLOR_SHORTHAND.toString(), CSSName.BORDER_SHORTHAND.toString(), CSSName.BORDER_TOP_SHORTHAND.toString(), CSSName.BORDER_LEFT_SHORTHAND.toString(), CSSName.BORDER_BOTTOM_SHORTHAND.toString(), CSSName.BORDER_RIGHT_SHORTHAND.toString() }; private final String[] MARGIN_CSS = new String[] { CSSName.MARGIN_BOTTOM.toString(), CSSName.MARGIN_TOP.toString(), CSSName.MARGIN_LEFT.toString(), CSSName.MARGIN_RIGHT.toString(), CSSName.MARGIN_SHORTHAND.toString() }; public final static String[] BACKGROUND_IMAGE_CSS = new String[] { CSSName.BACKGROUND_ATTACHMENT.toString(), CSSName.BACKGROUND_IMAGE.toString(), CSSName.BACKGROUND_POSITION.toString(), CSSName.BACKGROUND_REPEAT.toString(), CSSName.BACKGROUND_SIZE.toString(), CSSName.OPACITY.toString() }; public final static String[] ROUNDED_RADIUS_PREFIX = new String[] { "-webkit-", "-moz-" }; static Attribute[] marginAttributes = new Attribute[] { CSS.Attribute.MARGIN, CSS.Attribute.MARGIN_BOTTOM, CSS.Attribute.MARGIN_LEFT, CSS.Attribute.MARGIN_RIGHT, CSS.Attribute.MARGIN_TOP }; public static Attribute[] fontAttributes = new Attribute[] { CSS.Attribute.FONT, CSS.Attribute.FONT_FAMILY, CSS.Attribute.FONT_SIZE, CSS.Attribute.FONT_STYLE, CSS.Attribute.FONT_VARIANT, CSS.Attribute.FONT_WEIGHT }; public static String[] borderAttributesExtensions = new String[] { "border-left-style", "border-right-style", "border-top-style", "border-bottom-style", "border-left-color", "border-right-color", "border-top-color", "border-bottom-color", "border-radius", "border-top-left-radius", "border-top-right-radius", "border-bottom-right-radius", "border-bottom-left-radius" }; public static Attribute[] borderAttributes = new Attribute[] { CSS.Attribute.BORDER, CSS.Attribute.BORDER_BOTTOM, CSS.Attribute.BORDER_BOTTOM_WIDTH, CSS.Attribute.BORDER_COLOR, CSS.Attribute.BORDER_LEFT, CSS.Attribute.BORDER_LEFT_WIDTH, CSS.Attribute.BORDER_RIGHT, CSS.Attribute.BORDER_RIGHT_WIDTH, CSS.Attribute.BORDER_STYLE, CSS.Attribute.BORDER_TOP, CSS.Attribute.BORDER_TOP_WIDTH, CSS.Attribute.BORDER_WIDTH }; public static String[] LinearGradientsIdentifiers = new String[] { "-webkit-linear-gradient", "-moz-linear-gradient", "-ms-linear-gradient", "-o-linear-gradient" }; public static final String[] ROUNDED_RADIUS_ATTRIBUTES = new String[] { "border-radius", "border-top-left-radius", "border-top-right-radius", "border-bottom-right-radius", "border-bottom-left-radius" }; private final CSSErrorHandler errorHandler; private Stylesheet styleSheet; private final FixedStyleSheet ss; public ServoyStyleSheet(String cssContent) { this(cssContent, "<noname>"); } public ServoyStyleSheet(String cssContent, final String name) { this.errorHandler = new CSSErrorHandler() { public void error(String uri, String message) { Debug.error("Error at css parsing, " + name + ',' + uri + ':' + message); //$NON-NLS-1$ } }; CSSParser parser = new CSSParser(errorHandler); if (cssContent != null) { try { styleSheet = parser.parseStylesheet("servoy stylesheet" /* to fix NPE in parsing */, 0, new StringReader(cssContent)); } catch (IOException e) { Debug.error(e); } } ss = new FixedStyleSheet(); try { ss.addRule(cssContent); } catch (Exception e) { Debug.error(e); } } @Deprecated public AttributeSet getRule(String selector) { return ss.getRule(selector); } @Deprecated public Font getFont(AttributeSet a) { return ss.getFont(a); } @Deprecated public Insets getMargin(AttributeSet a) { return ss.getMargin(a); } @Deprecated public Border getBorder(AttributeSet a) { return ss.getBorder(a); } @Deprecated public int getHAlign(AttributeSet a) { return ss.getHAlign(a); } @Deprecated public int getVAlign(AttributeSet a) { return ss.getVAlign(a); } @Deprecated public Color getForeground(AttributeSet a) { return ss.getForeground(a); } @Deprecated public Color getBackground(AttributeSet a) { return ss.getBackground(a); } @Deprecated public boolean hasBorder(AttributeSet s) { return ss.hasBorder(s); } @Deprecated public boolean hasMargin(AttributeSet s) { return ss.hasMargin(s); } @Deprecated public boolean hasFont(AttributeSet s) { return ss.hasFont(s); } private final Map<String, IStyleRule> ruleCache = new ConcurrentHashMap<String, IStyleRule>(); public IStyleRule getCSSRule(String selector) { IStyleRule styleRule = ruleCache.get(selector); if (styleRule != null) return styleRule; if (styleSheet != null) { Matcher matcher = new Matcher(new ServoyTreeResolver(), new ServoyAttributeResolver(), new ServoyStylesheetFactor(errorHandler), Arrays.asList(styleSheet), null); styleRule = new ServoyStyleRule(matcher.getCascadedStyle(selector, true)); ruleCache.put(selector, styleRule); } return styleRule; } public Font getFont(IStyleRule a) { if (hasFont(a)) { String family = "SansSerif"; //$NON-NLS-1$ int size = 12; int style = Font.PLAIN; ServoyStyleRule rule = (ServoyStyleRule)a; if (a.hasAttribute(CSSName.FONT_FAMILY.toString())) { PropertyDeclaration declaration = rule.getPropertyDeclaration(CSSName.FONT_FAMILY.toString()); if (declaration != null) { family = ((PropertyValue)declaration.getValue()).getStringArrayValue()[0]; } } if (a.hasAttribute(CSSName.FONT_WEIGHT.toString())) { PropertyDeclaration declaration = rule.getPropertyDeclaration(CSSName.FONT_WEIGHT.toString()); if (declaration != null) { if (declaration.getValue().getCssText().contains("bold") || Utils.getAsFloat(declaration.getValue().getCssText()) > 400) //$NON-NLS-1$ { style |= Font.BOLD; } } } if (a.hasAttribute(CSSName.FONT_STYLE.toString())) { PropertyDeclaration declaration = rule.getPropertyDeclaration(CSSName.FONT_STYLE.toString()); if (declaration != null) { if (declaration.getValue().getCssText().contains("italic")) //$NON-NLS-1$ { style |= Font.ITALIC; } } } if (a.hasAttribute(CSSName.FONT_SIZE.toString())) { PropertyDeclaration declaration = rule.getPropertyDeclaration(CSSName.FONT_SIZE.toString()); if (declaration != null) { float fontSize = ss.getLength(declaration.getValue().getCssText()); if (fontSize > 0) { if (declaration.getValue().getCssText().endsWith("px")) //$NON-NLS-1$ { fontSize = 4 * fontSize / 3; } size = Math.round(fontSize); } } } return PersistHelper.createFont(family, style, size); } return null; } public Insets getMargin(IStyleRule a) { if (hasMargin(a)) { float top = ss.getLength(a.getValue(CSS.Attribute.MARGIN_TOP.toString())); float bottom = ss.getLength(a.getValue(CSS.Attribute.MARGIN_BOTTOM.toString())); float left = ss.getLength(a.getValue(CSS.Attribute.MARGIN_LEFT.toString())); float right = ss.getLength(a.getValue(CSS.Attribute.MARGIN_RIGHT.toString())); return new Insets(top < 0 ? 0 : (int)top, left < 0 ? 0 : (int)left, bottom < 0 ? 0 : (int)bottom, right < 0 ? 0 : (int)right); } return null; } public Border getBorder(final IStyleRule a) { return ss.getBorder(new AttributeSet() { public boolean isEqual(AttributeSet attr) { return false; } public boolean isDefined(Object attrName) { return a.hasAttribute(attrName.toString()); } public AttributeSet getResolveParent() { return null; } public Enumeration< ? > getAttributeNames() { final Iterator<String> it = a.getAttributeNames().iterator(); return new Enumeration() { public boolean hasMoreElements() { return it.hasNext(); } public Object nextElement() { return it.next(); } }; } public int getAttributeCount() { return a.getAttributeCount(); } public Object getAttribute(Object key) { if ("border-top-color".equals(key.toString()) || "border-right-color".equals(key.toString()) || "border-bottom-color".equals(key.toString()) || "border-left-color".equals(key.toString())) { String[] values = a.getValues(key.toString()); if (values != null && values.length > 0) { for (int i = values.length - 1; i >= 0; i--) { Color color = PersistHelper.createColor(values[i]); if (color != null) { return values[i]; } } } return null; } if (CSSName.BORDER_TOP_LEFT_RADIUS.toString().equals(key.toString()) || CSSName.BORDER_TOP_RIGHT_RADIUS.toString().equals(key.toString()) || CSSName.BORDER_BOTTOM_RIGHT_RADIUS.toString().equals(key.toString()) || CSSName.BORDER_BOTTOM_LEFT_RADIUS.toString().equals(key.toString())) { String[] values = a.getValues(key.toString()); if (values != null && values.length > 0) { for (int i = values.length - 1; i >= 0; i--) { if (values[i] != null && !values[i].contains("%")) { // fallback mechanism // % border radius length is not supported in SC return values[i]; } } } return null; } return a.getValue(key.toString()); } public AttributeSet copyAttributes() { return null; } public boolean containsAttributes(AttributeSet attributes) { return false; } public boolean containsAttribute(Object name, Object value) { return false; } }); } public int getHAlign(IStyleRule a) { return ss.getHAlign(a.getValue(CSSName.TEXT_ALIGN.toString())); } public int getVAlign(IStyleRule a) { return ss.getVAlign(a.getValue(CSSName.VERTICAL_ALIGN.toString())); } public Color getForeground(IStyleRule a) { return getLastValidColor(a, CSSName.COLOR.toString()); } public List<Color> getForegrounds(IStyleRule a) { return getValidColors(a, CSSName.COLOR.toString()); } public Color getBackground(IStyleRule a) { return getLastValidColor(a, CSSName.BACKGROUND_COLOR.toString()); } public List<Color> getBackgrounds(IStyleRule a) { return getValidColors(a, CSSName.BACKGROUND_COLOR.toString()); } private List<Color> getValidColors(IStyleRule a, String cssAttribute) { String[] cssDefinitions = a.getValues(cssAttribute); List<Color> cssValidColors = new ArrayList<Color>(); if (cssDefinitions != null && cssDefinitions.length > 0) { for (String cssDefinition : cssDefinitions) { Color color = PersistHelper.createColorWithTransparencySupport(cssDefinition); if (color != null) { cssValidColors.add(color); } } } return (cssValidColors.size() == 0) ? null : cssValidColors; } private Color getLastValidColor(IStyleRule a, String cssAttribute) { String[] cssDefinitions = a.getValues(cssAttribute); if (cssDefinitions != null && cssDefinitions.length > 0) { for (int i = cssDefinitions.length - 1; i >= 0; i--) { Color color = PersistHelper.createColor(cssDefinitions[i]); if (color != null) { return color; } } } return null; } private boolean hasProperty(IStyleRule s, String[] names) { for (String name : names) { if (s.hasAttribute(name)) return true; } return false; } public boolean hasBorder(IStyleRule s) { return hasProperty(s, BORDER_CSS); } public boolean hasMargin(IStyleRule s) { return hasProperty(s, MARGIN_CSS); } public boolean hasFont(IStyleRule s) { return s.hasAttribute(CSSName.FONT_FAMILY.toString()) || s.hasAttribute(CSSName.FONT_SHORTHAND.toString()) || s.hasAttribute(CSSName.FONT_SIZE.toString()) || s.hasAttribute(CSSName.FONT_STYLE.toString()) || s.hasAttribute(CSSName.FONT_VARIANT.toString()) || s.hasAttribute(CSSName.FONT_WEIGHT.toString()); } public List<String> getStyleNames() { List<String> list = new ArrayList<String>(); if (styleSheet != null) { for (Object ruleset : styleSheet.getContents()) { if (ruleset instanceof Ruleset) { for (Object selector : ((Ruleset)ruleset).getFSSelectors()) { if (selector instanceof Selector) { list.add(((Selector)selector).getSelectorText()); } } } } } return list; } }