blob: 81e7718f686a829ee84aa531b2e23cc270859499 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2008, 2009 John Krasnay and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* John Krasnay - initial API and implementation
* Dave Holroyd - Implement font-weight:bolder
* Dave Holroyd - Implement text decoration
* John Austin - More complete CSS constants. Add the colour "orange".
* Travis Haagen - bug 260806 - enhanced support for 'content' CSS property
*******************************************************************************/
package org.eclipse.wst.xml.vex.core.internal.css;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.eclipse.wst.xml.vex.core.internal.core.FontSpec;
import org.eclipse.wst.xml.vex.core.internal.provisional.dom.I.VEXElement;
import org.w3c.css.sac.LexicalUnit;
/**
* Represents a CSS style sheet.
*/
public class StyleSheet implements Serializable {
private static final long serialVersionUID = -7309907170251446189L;
/**
* Standard CSS properties.
*/
private static final IProperty[] CSS_PROPERTIES = new IProperty[] {
new DisplayProperty(),
new LineHeightProperty(),
new ListStyleTypeProperty(),
new TextAlignProperty(),
new WhiteSpaceProperty(),
new FontFamilyProperty(),
new FontSizeProperty(),
new FontStyleProperty(),
new FontWeightProperty(),
new TextDecorationProperty(),
new ColorProperty(CSS.COLOR),
new ColorProperty(CSS.BACKGROUND_COLOR),
new LengthProperty(CSS.MARGIN_BOTTOM, IProperty.AXIS_VERTICAL),
new LengthProperty(CSS.MARGIN_LEFT, IProperty.AXIS_HORIZONTAL),
new LengthProperty(CSS.MARGIN_RIGHT, IProperty.AXIS_HORIZONTAL),
new LengthProperty(CSS.MARGIN_TOP, IProperty.AXIS_VERTICAL),
new LengthProperty(CSS.PADDING_BOTTOM, IProperty.AXIS_VERTICAL),
new LengthProperty(CSS.PADDING_LEFT, IProperty.AXIS_HORIZONTAL),
new LengthProperty(CSS.PADDING_RIGHT, IProperty.AXIS_HORIZONTAL),
new LengthProperty(CSS.PADDING_TOP, IProperty.AXIS_VERTICAL),
new ColorProperty(CSS.BORDER_BOTTOM_COLOR),
new ColorProperty(CSS.BORDER_LEFT_COLOR),
new ColorProperty(CSS.BORDER_RIGHT_COLOR),
new ColorProperty(CSS.BORDER_TOP_COLOR),
new BorderStyleProperty(CSS.BORDER_BOTTOM_STYLE),
new BorderStyleProperty(CSS.BORDER_LEFT_STYLE),
new BorderStyleProperty(CSS.BORDER_RIGHT_STYLE),
new BorderStyleProperty(CSS.BORDER_TOP_STYLE),
new BorderWidthProperty(CSS.BORDER_BOTTOM_WIDTH,
CSS.BORDER_BOTTOM_STYLE, IProperty.AXIS_VERTICAL),
new BorderWidthProperty(CSS.BORDER_LEFT_WIDTH,
CSS.BORDER_LEFT_STYLE, IProperty.AXIS_HORIZONTAL),
new BorderWidthProperty(CSS.BORDER_RIGHT_WIDTH,
CSS.BORDER_RIGHT_STYLE, IProperty.AXIS_HORIZONTAL),
new BorderWidthProperty(CSS.BORDER_TOP_WIDTH, CSS.BORDER_TOP_STYLE,
IProperty.AXIS_VERTICAL), new BorderSpacingProperty(), };
/**
* The properties to calculate. This can be changed by the app.
*/
private static IProperty[] properties = CSS_PROPERTIES;
/**
* Style sheet is the default for the renderer.
*/
public static final byte SOURCE_DEFAULT = 0;
/**
* Style sheet was provided by the document author.
*/
public static final byte SOURCE_AUTHOR = 1;
/**
* Style sheet was provided by the user.
*/
public static final byte SOURCE_USER = 2;
/**
* The rules that comprise the stylesheet.
*/
private Rule[] rules;
/**
* Computing styles can be expensive, e.g. we have to calculate the styles
* of all parents of an element. We therefore cache styles in a map of
* element => WeakReference(styles). A weak hash map is used to avoid
* leaking memory as elements are deleted. By using weak references to the
* values, we also ensure the cache is memory-sensitive.
*
* This must be transient to prevent it from being serialized, as
* WeakHashMaps are not serializable.
*/
private transient Map<VEXElement, WeakReference<Styles>> styleMap;
/**
* Class constructor.
*
* @param rules
* Rules that constitute the style sheet.
*/
public StyleSheet(Rule[] rules) {
this.rules = rules;
}
/**
* Flush any cached styles for the given element.
*
* @param element
* IVEXElement for which styles are to be flushed.
*/
public void flushStyles(VEXElement element) {
this.getStyleMap().remove(element);
}
/**
* Returns a pseudo-element representing content to be displayed after the
* given element, or null if there is no such content.
*
* @param element
* Parent element of the pseudo-element.
*/
public VEXElement getAfterElement(VEXElement element) {
PseudoElement pe = new PseudoElement(element, PseudoElement.AFTER);
Styles styles = this.getStyles(pe);
if (styles == null) {
return null;
} else {
return pe;
}
}
/**
* Returns a pseudo-element representing content to be displayed before the
* given element, or null if there is no such content.
*
* @param element
* Parent element of the pseudo-element.
*/
public VEXElement getBeforeElement(VEXElement element) {
PseudoElement pe = new PseudoElement(element, PseudoElement.BEFORE);
Styles styles = this.getStyles(pe);
if (styles == null) {
return null;
} else {
return pe;
}
}
/**
* Returns the array of standard CSS properties.
*/
public static IProperty[] getCssProperties() {
return CSS_PROPERTIES;
}
/**
* Returns the styles for the given element. The styles are cached to ensure
* reasonable performance.
*
* @param element
* IVEXElement for which to calculate the styles.
*/
public Styles getStyles(VEXElement element) {
Styles styles;
WeakReference<Styles> ref = getStyleMap().get(element);
if (ref != null) {
// can't combine these tests, since calling ref.get() twice
// (once to query for null, once to get the value) would
// cause a race condition should the GC happen btn the two.
styles = (Styles) ref.get();
if (styles != null) {
return styles;
}
} else if (getStyleMap().containsKey(element)) {
// this must be a pseudo-element with no content
return null;
}
styles = calculateStyles(element);
if (styles == null) {
// Yes, they can be null if element is a PseudoElement with no
// content property
getStyleMap().put(element, null);
} else {
getStyleMap().put(element, new WeakReference<Styles>(styles));
}
return styles;
}
private Styles calculateStyles(VEXElement element) {
Styles styles = new Styles();
Styles parentStyles = null;
if (element.getParent() != null) {
parentStyles = this.getStyles(element.getParent());
}
Map<String, LexicalUnit> decls = getApplicableDecls(element);
LexicalUnit lu;
// If we're finding a pseudo-element, look at the 'content' property
// first, since most of the time it'll be empty and we'll return null.
if (element instanceof PseudoElement) {
lu = (LexicalUnit) decls.get(CSS.CONTENT);
if (lu == null) {
return null;
}
List<String> content = new ArrayList<String>();
while (lu != null) {
switch (lu.getLexicalUnitType())
{
case LexicalUnit.SAC_STRING_VALUE :
// content: "A String"
content.add(lu.getStringValue());
break;
case LexicalUnit.SAC_ATTR :
// content: attr(attributeName)
String attributeValue =
element.getParent().getAttribute(lu.getStringValue());
if (attributeValue != null) {
content.add(attributeValue);
}
}
lu = lu.getNextLexicalUnit();
}
styles.setContent(content);
}
for (int i = 0; i < properties.length; i++) {
IProperty property = properties[i];
lu = (LexicalUnit) decls.get(property.getName());
Object value = property.calculate(lu, parentStyles, styles);
styles.put(property.getName(), value);
}
// Now, map font-family, font-style, font-weight, and font-size onto
// an AWT font.
int styleFlags = FontSpec.PLAIN;
String fontStyle = styles.getFontStyle();
if (fontStyle.equals(CSS.ITALIC) || fontStyle.equals(CSS.OBLIQUE)) {
styleFlags |= FontSpec.ITALIC;
}
if (styles.getFontWeight() > 550) {
// 550 is halfway btn normal (400) and bold (700)
styleFlags |= FontSpec.BOLD;
}
String textDecoration = styles.getTextDecoration();
if (textDecoration.equals(CSS.UNDERLINE)) {
styleFlags |= FontSpec.UNDERLINE;
} else if (textDecoration.equals(CSS.OVERLINE)) {
styleFlags |= FontSpec.OVERLINE;
} else if (textDecoration.equals(CSS.LINE_THROUGH)) {
styleFlags |= FontSpec.LINE_THROUGH;
}
styles.setFont(new FontSpec(styles.getFontFamilies(), styleFlags, Math
.round(styles.getFontSize())));
return styles;
}
/**
* Returns the list of properties to be parsed by StyleSheets in this app.
*/
public static IProperty[] getProperties() {
return StyleSheet.properties;
}
/**
* Returns the rules comprising this stylesheet.
*/
public Rule[] getRules() {
return this.rules;
}
/**
* Sets the list of properties to be used by StyleSheets in this
* application.
*
* @param properties
* New array of IProperty objects to be used.
*/
public static void setProperties(IProperty[] properties) {
StyleSheet.properties = properties;
}
// ========================================================= PRIVATE
/**
* Returns all the declarations that apply to the given element.
*/
private Map<String, LexicalUnit> getApplicableDecls(VEXElement element) {
// Find all the property declarations that apply to this element.
List<PropertyDecl> declList = new ArrayList<PropertyDecl>();
Rule[] rules = this.getRules();
for (int i = 0; i < rules.length; i++) {
Rule rule = rules[i];
if (rule.matches(element)) {
PropertyDecl[] ruleDecls = rule.getPropertyDecls();
for (int j = 0; j < ruleDecls.length; j++) {
declList.add(ruleDecls[j]);
}
}
}
// Sort in cascade order. We can then just stuff them into a
// map and get the right values since higher-priority values
// come later and overwrite lower-priority ones.
Collections.sort(declList);
Map<String, PropertyDecl> decls = new HashMap<String, PropertyDecl>();
Iterator<PropertyDecl> iter = declList.iterator();
while (iter.hasNext()) {
PropertyDecl decl = (PropertyDecl) iter.next();
PropertyDecl prevDecl = (PropertyDecl) decls
.get(decl.getProperty());
if (prevDecl == null || !prevDecl.isImportant()
|| decl.isImportant()) {
decls.put(decl.getProperty(), decl);
}
}
Map<String, LexicalUnit> values = new HashMap<String, LexicalUnit>();
for (Iterator<String> it = decls.keySet().iterator(); it.hasNext();) {
PropertyDecl decl = (PropertyDecl) decls.get(it.next());
values.put(decl.getProperty(), decl.getValue());
}
return values;
}
private Map<VEXElement, WeakReference<Styles>> getStyleMap() {
if (styleMap == null) {
styleMap = new WeakHashMap<VEXElement, WeakReference<Styles>>();
}
return styleMap;
}
}