/******************************************************************************* * Copyright (c) 2008, 2015 Innoopract Informationssysteme GmbH 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: * Innoopract Informationssysteme GmbH - initial API and implementation * EclipseSource - ongoing development ******************************************************************************/ package org.eclipse.rap.rwt.internal.theme.css; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TreeMap; import org.eclipse.rap.rwt.internal.theme.CssAnimation; import org.eclipse.rap.rwt.internal.theme.CssBorder; import org.eclipse.rap.rwt.internal.theme.CssBoxDimensions; import org.eclipse.rap.rwt.internal.theme.CssColor; import org.eclipse.rap.rwt.internal.theme.CssCursor; import org.eclipse.rap.rwt.internal.theme.CssDimension; import org.eclipse.rap.rwt.internal.theme.CssFloat; import org.eclipse.rap.rwt.internal.theme.CssFont; import org.eclipse.rap.rwt.internal.theme.CssIdentifier; import org.eclipse.rap.rwt.internal.theme.CssImage; import org.eclipse.rap.rwt.internal.theme.CssShadow; import org.eclipse.rap.rwt.service.ResourceLoader; import org.w3c.css.sac.LexicalUnit; public final class PropertyResolver { private static final String BOLD = "bold"; private static final String ITALIC = "italic"; private static final String NORMAL = "normal"; // No border; the computed border width is zero. private static final String NONE = "none"; // Same as 'none', except in terms of border conflict resolution for table // elements. private static final String HIDDEN = "hidden"; // The border is a series of dots. private static final String DOTTED = "dotted"; // The border is a series of short line segments. private static final String DASHED = "dashed"; // The border is a single line segment. private static final String SOLID = "solid"; // The border is two solid lines. The sum of the two lines and the space // between them equals the value of 'border-width'. private static final String DOUBLE = "double"; // The border looks as though it were carved into the canvas. private static final String GROOVE = "groove"; // The opposite of 'groove': the border looks as though it were coming out of // the canvas. private static final String RIDGE = "ridge"; // The border makes the box look as though it were embedded in the canvas. private static final String INSET = "inset"; // The opposite of 'inset': the border makes the box look as though it were // coming out of the canvas. private static final String OUTSET = "outset"; /** A thin border. */ private static final String THIN = "thin"; /** A medium border. */ private static final String MEDIUM = "medium"; /** A thick border. */ private static final String THICK = "thick"; private static final String TRANSPARENT = "transparent"; private static final Map<String,NamedColor> NAMED_COLORS = new HashMap<>(); private static final List<String> BORDER_STYLES = new ArrayList<>(); /** Width value for "thin" identifier. */ static final int THIN_VALUE = 1; /** Width value for "medium" identifier. */ static final int MEDIUM_VALUE = 3; /** Width value for "thick" identifier. */ static final int THICK_VALUE = 5; static { // register 16 standard HTML colors NAMED_COLORS.put( "black", new NamedColor( 0, 0, 0 ) ); NAMED_COLORS.put( "gray", new NamedColor( 128, 128, 128 ) ); NAMED_COLORS.put( "silver", new NamedColor( 192, 192, 192 ) ); NAMED_COLORS.put( "white", new NamedColor( 255, 255, 255 ) ); NAMED_COLORS.put( "maroon", new NamedColor( 128, 0, 0 ) ); NAMED_COLORS.put( "red", new NamedColor( 255, 0, 0 ) ); NAMED_COLORS.put( "purple", new NamedColor( 128, 0, 128 ) ); NAMED_COLORS.put( "fuchsia", new NamedColor( 255, 0, 255 ) ); NAMED_COLORS.put( "green", new NamedColor( 0, 128, 0 ) ); NAMED_COLORS.put( "lime", new NamedColor( 0, 255, 0 ) ); NAMED_COLORS.put( "navy", new NamedColor( 0, 0, 128 ) ); NAMED_COLORS.put( "blue", new NamedColor( 0, 0, 255 ) ); NAMED_COLORS.put( "olive", new NamedColor( 128, 128, 0 ) ); NAMED_COLORS.put( "yellow", new NamedColor( 255, 255, 0 ) ); NAMED_COLORS.put( "teal", new NamedColor( 0, 128, 128 ) ); NAMED_COLORS.put( "aqua", new NamedColor( 0, 255, 255 ) ); // register border styles BORDER_STYLES.add( NONE ); BORDER_STYLES.add( HIDDEN ); BORDER_STYLES.add( DOTTED ); BORDER_STYLES.add( DASHED ); BORDER_STYLES.add( SOLID ); BORDER_STYLES.add( DOUBLE ); BORDER_STYLES.add( GROOVE ); BORDER_STYLES.add( RIDGE ); BORDER_STYLES.add( INSET ); BORDER_STYLES.add( OUTSET ); } private final StylePropertyMap resolvedProperties; public PropertyResolver() { resolvedProperties = new StylePropertyMap(); } public void resolveProperty( String name, LexicalUnit unit, ResourceLoader loader ) { if( isBorderProperty( name ) ) { CssBorder border = readBorder( unit ); resolvedProperties.setProperty( "border-top", border ); resolvedProperties.setProperty( "border-right", border ); resolvedProperties.setProperty( "border-bottom", border ); resolvedProperties.setProperty( "border-left", border ); } else if( isBorderEdgeProperty( name ) ) { resolvedProperties.setProperty( name, readBorder( unit ) ); } else if( isBoxDimensionProperty( name ) ) { resolvedProperties.setProperty( name, readBoxDimensions( unit ) ); } else if( isColorProperty( name ) ) { resolvedProperties.setProperty( name, readColor( unit ) ); } else if( isDimensionProperty( name ) ) { resolvedProperties.setProperty( name, readDimension( unit ) ); } else if( isFontProperty( name ) ) { resolvedProperties.setProperty( name, readFont( unit ) ); } else if( isImageProperty( name ) ) { resolvedProperties.setProperty( name, readBackgroundImage( unit, loader ) ); } else if( isBackgroundRepeatProperty( name ) ) { resolvedProperties.setProperty( name, readBackgroundRepeat( unit ) ); } else if( isBackgroundPositionProperty( name ) ) { resolvedProperties.setProperty( name, readBackgroundPosition( unit ) ); } else if( isTextDecorationProperty( name ) ) { resolvedProperties.setProperty( name, readTextDecoration( unit ) ); } else if( isTextOverflowProperty( name ) ) { resolvedProperties.setProperty( name, readTextOverflow( unit ) ); } else if( isTextAlignProperty( name ) ) { resolvedProperties.setProperty( name, readTextAlign( unit ) ); } else if( isCursorProperty( name ) ) { resolvedProperties.setProperty( name, readCursor( unit, loader ) ); } else if( isFloatProperty( name ) ) { resolvedProperties.setProperty( name, readFloat( unit ) ); } else if( isAnimationProperty( name ) ) { resolvedProperties.setProperty( name, readAnimation( unit ) ); } else if( isShadowProperty( name ) ) { resolvedProperties.setProperty( name, readShadow( unit ) ); } else { throw new IllegalArgumentException( "Unknown property " + name ); } } public StylePropertyMap getResolvedProperties() { return resolvedProperties; } static boolean isColorProperty( String property ) { return "color".equals( property ) || property.endsWith( "-color" ); } static CssColor readColor( LexicalUnit unit ) { CssColor result = null; short type = unit.getLexicalUnitType(); if( type == LexicalUnit.SAC_RGBCOLOR ) { result = readColorRgb( unit ); } else if( type == LexicalUnit.SAC_FUNCTION && "rgba".equals( unit.getFunctionName() ) ) { result = readColorRgba( unit ); } else if( type == LexicalUnit.SAC_FUNCTION && "rgb".equals( unit.getFunctionName() ) ) { throw new IllegalArgumentException( "Failed to parse rgb() function" ); } else if( type == LexicalUnit.SAC_IDENT ) { result = readColorNamed( unit ); } else if( type == LexicalUnit.SAC_INHERIT ) { result = CssColor.TRANSPARENT; } if( result == null ) { throw new IllegalArgumentException( "Failed to parse color " + toString( unit ) ); } return result; } private static CssColor readColorNamed( LexicalUnit unit ) { CssColor result = null; String string = unit.getStringValue(); String lowerCaseString = string.toLowerCase( Locale.ENGLISH ); if( TRANSPARENT.equals( string ) ) { result = CssColor.TRANSPARENT; } else if( NAMED_COLORS.containsKey( lowerCaseString ) ) { NamedColor color = NAMED_COLORS.get( lowerCaseString ); result = CssColor.create( color.red, color.green, color.blue ); } return result; } private static CssColor readColorRgb( LexicalUnit unit ) { CssColor result = null; // The parser ensures that we have exactly three parameters for this type LexicalUnit redParam = unit.getParameters(); LexicalUnit greenParam = redParam.getNextLexicalUnit().getNextLexicalUnit(); LexicalUnit blueParam = greenParam.getNextLexicalUnit().getNextLexicalUnit(); short valueType = redParam.getLexicalUnitType(); if( greenParam.getLexicalUnitType() == valueType || blueParam.getLexicalUnitType() == valueType ) { if( valueType == LexicalUnit.SAC_INTEGER ) { int red = normalizeRGBValue( redParam.getIntegerValue() ); int green = normalizeRGBValue( greenParam.getIntegerValue() ); int blue = normalizeRGBValue( blueParam.getIntegerValue() ); result = CssColor.create( red, green, blue ); } else if( valueType == LexicalUnit.SAC_PERCENTAGE ) { float redPercent = normalizePercentValue( redParam.getFloatValue() ); float greenPercent = normalizePercentValue( greenParam.getFloatValue() ); float bluePercent = normalizePercentValue( blueParam.getFloatValue() ); int red = ( int )( 255 * redPercent / 100 ); int green = ( int )( 255 * greenPercent / 100 ); int blue = ( int )( 255 * bluePercent / 100 ); result = CssColor.create( red, green, blue ); } } return result; } static CssColor readColorRgba( LexicalUnit unit ) { CssColor result = null; int[] values = new int[ 3 ]; float alpha = 1f; short type = unit.getLexicalUnitType(); LexicalUnit nextUnit = unit.getParameters(); boolean ok = nextUnit != null; boolean mixedTypes = false; short previousType = -1; int pos = 0; while( ok ) { type = nextUnit.getLexicalUnitType(); if( pos == 0 || pos == 2 || pos == 4 ) { // color number if( type == LexicalUnit.SAC_INTEGER ) { values[ pos / 2 ] = normalizeRGBValue( nextUnit.getIntegerValue() ); } else if( type == LexicalUnit.SAC_PERCENTAGE ) { float percentValue = normalizePercentValue( nextUnit.getFloatValue() ); values[ pos / 2 ] = ( int )( 255 * percentValue / 100 ); } else { ok = false; } mixedTypes = previousType != -1 && previousType != type; previousType = type; } else if( pos == 1 || pos == 3 || pos == 5 ) { // comma if( type != LexicalUnit.SAC_OPERATOR_COMMA ) { ok = false; } } else if( pos == 6 ) { // alpha number if( type == LexicalUnit.SAC_REAL ) { alpha = normalizeAlphaValue( nextUnit.getFloatValue() ); } else { ok = false; } } pos++; nextUnit = nextUnit.getNextLexicalUnit(); ok &= nextUnit != null && pos < 7 && !mixedTypes; } if( pos == 7 ) { result = CssColor.create( values[ 0 ], values[ 1 ], values[ 2 ], alpha ); } if( result == null ) { throw new IllegalArgumentException( "Failed to parse rgba color" ); } return result; } static boolean isDimensionProperty( String property ) { return "spacing".equals( property ) || "width".equals( property ) || "height".equals( property ) || "min-height".equals( property ); } static CssDimension readDimension( LexicalUnit unit ) { CssDimension result = null; Integer length = readSingleLengthUnit( unit ); if( length != null ) { result = CssDimension.create( length.intValue() ); } if( result == null ) { throw new IllegalArgumentException( "Failed to parse dimension " + toString( unit ) ); } return result; } static boolean isBorderProperty( String property ) { return "border".equals( property ); } static boolean isBorderEdgeProperty( String property ) { return "border-top".equals( property ) || "border-right".equals( property ) || "border-bottom".equals( property ) || "border-left".equals( property ); } static CssBorder readBorder( LexicalUnit unit ) { CssBorder result = null; CssColor color = null; String style = null; int width = -1; LexicalUnit nextUnit = unit; boolean consumed = false; while( nextUnit != null ) { consumed = false; if( !consumed && width == -1 ) { width = readBorderWidth( nextUnit ); consumed |= width != -1; } if( !consumed && style == null ) { style = readBorderStyle( nextUnit ); consumed |= style != null; } if( !consumed && color == null ) { color = readColor( nextUnit ); consumed |= color != null; } nextUnit = consumed ? nextUnit.getNextLexicalUnit() : null; } if( consumed ) { result = CssBorder.create( width, style, color ); } if( result == null ) { throw new IllegalArgumentException( "Failed to parse border " + toString( unit ) ); } return result; } static String readBorderStyle( LexicalUnit unit ) { String result = null; short type = unit.getLexicalUnitType(); if( type == LexicalUnit.SAC_IDENT ) { String string = unit.getStringValue(); if( BORDER_STYLES.contains( string ) ) { result = string; } } return result; } static int readBorderWidth( LexicalUnit unit ) { int result = -1; short type = unit.getLexicalUnitType(); if( type == LexicalUnit.SAC_IDENT ) { String string = unit.getStringValue(); if( THIN.equals( string ) ) { result = THIN_VALUE; } else if( MEDIUM.equals( string ) ) { result = MEDIUM_VALUE; } else if( THICK.equals( string ) ) { result = THICK_VALUE; } } else if( type == LexicalUnit.SAC_INTEGER ) { int value = unit.getIntegerValue(); if( value == 0 ) { result = 0; } } else if( type == LexicalUnit.SAC_PIXEL ) { float value = unit.getFloatValue(); if( value >= 0f ) { result = Math.round( value ); } } return result; } static boolean isBoxDimensionProperty( String property ) { return "padding".equals( property ) || "margin".equals( property ) || "border-radius".equals( property ); } static CssBoxDimensions readBoxDimensions( LexicalUnit unit ) { CssBoxDimensions result = null; Integer value1 = readSingleLengthUnit( unit ); if( value1 != null ) { int top, right, left, bottom; top = right = bottom = left = value1.intValue(); LexicalUnit nextUnit = unit.getNextLexicalUnit(); boolean ok = true; int pos = 1; while( nextUnit != null && ok ) { pos++; Integer nextValue = readSingleLengthUnit( nextUnit ); ok &= nextValue != null && pos <= 4; if( ok ) { if( pos == 2 ) { right = left = nextValue.intValue(); } else if( pos == 3 ) { bottom = nextValue.intValue(); } else if( pos == 4 ) { left = nextValue.intValue(); } } nextUnit = nextUnit.getNextLexicalUnit(); } ok &= nextUnit == null; if( ok ) { result = CssBoxDimensions.create( top, right, bottom, left ); } } if( result == null ) { throw new IllegalArgumentException( "Failed to parse box dimensions " + toString( unit ) ); } return result; } static boolean isFontProperty( String property ) { return "font".equals( property ) || property.endsWith( "-fontlist" ); } // The format of a URI value is 'url(' followed by optional whitespace // followed by an optional single quote (') or double quote (") character // followed by the URI itself, followed by an optional single quote (') or // double quote (") character followed by optional whitespace followed by ')'. // The two quote characters must be the same. static CssFont readFont( LexicalUnit unit ) { CssFont result = null; String[] family = null; String style = null; String weight = null; int size = -1; boolean consumed = false; boolean consumedSize = false; boolean consumedFamily = false; LexicalUnit nextUnit = unit; while( nextUnit != null && !consumedFamily ) { consumed = false; if( !consumed && !consumedSize && style == null ) { style = readFontStyle( nextUnit ); consumed |= style != null; } if( !consumed && !consumedSize && weight == null ) { weight = readFontWeight( nextUnit ); consumed |= weight != null; } if( !consumed && !consumedFamily && size == -1 ) { size = readFontSize( nextUnit ); consumedSize = size != -1; consumed |= consumedSize; } if( !consumed && consumedSize && family == null ) { family = readFontFamily( nextUnit ); consumedFamily = family != null; consumed |= consumedFamily; } nextUnit = consumed ? nextUnit.getNextLexicalUnit() : null; } if( consumed && consumedSize && consumedFamily ) { boolean bold = BOLD.equals( weight ); boolean italic = ITALIC.equals( style ); result = CssFont.create( family, size, bold, italic ); } if( result == null ) { throw new IllegalArgumentException( "Failed to parse font " + toString( unit ) ); } return result; } static String readFontStyle( LexicalUnit unit ) { String result = null; short type = unit.getLexicalUnitType(); if( type == LexicalUnit.SAC_IDENT ) { String value = unit.getStringValue(); if( NORMAL.equals( value ) ) { result = value; } else if( ITALIC.equals( value ) ) { result = value; } } return result; } static String readFontWeight( LexicalUnit unit ) { String result = null; short type = unit.getLexicalUnitType(); if( type == LexicalUnit.SAC_IDENT ) { String value = unit.getStringValue(); if( NORMAL.equals( value ) ) { result = value; } else if( BOLD.equals( value ) ) { result = value; } } return result; } static int readFontSize( LexicalUnit unit ) { int result = -1; Integer length = readSingleLengthUnit( unit ); if( length != null ) { int value = length.intValue(); if( value >= 0 ) { result = value; } } return result; } static String[] readFontFamily( LexicalUnit unit ) { List<String> list = new ArrayList<>(); LexicalUnit nextUnit = unit; boolean ok = true; String buffer = ""; while( nextUnit != null && ok ) { short type = nextUnit.getLexicalUnitType(); if( type == LexicalUnit.SAC_STRING_VALUE || type == LexicalUnit.SAC_IDENT ) { if( buffer.length() > 0 ) { buffer += " "; } buffer += nextUnit.getStringValue(); } else if( type == LexicalUnit.SAC_OPERATOR_COMMA ) { if( buffer.length() > 0 ) { list.add( buffer ); } else { ok = false; } buffer = ""; } nextUnit = nextUnit.getNextLexicalUnit(); } String[] result = null; if( buffer.length() > 0 ) { list.add( buffer ); result = new String[ list.size() ]; list.toArray( result ); } return result; } static boolean isImageProperty( String property ) { return property.endsWith( "-image" ); } static CssImage readBackgroundImage( LexicalUnit unit, ResourceLoader loader ) { CssImage result = null; short type = unit.getLexicalUnitType(); if( type == LexicalUnit.SAC_URI ) { String value = unit.getStringValue(); result = CssImage.valueOf( value, loader ); } else if( type == LexicalUnit.SAC_IDENT ) { String value = unit.getStringValue(); if( NONE.equals( value ) ) { result = CssImage.NONE; } } else if( type == LexicalUnit.SAC_FUNCTION ) { String function = unit.getFunctionName(); if( "gradient".equals( function ) ) { result = readGradient( unit ); } } if( result == null ) { throw new IllegalArgumentException( "Failed to parse image " + toString( unit ) ); } return result; } static CssImage readGradient( LexicalUnit unit ) { CssImage result = null; boolean vertical; LexicalUnit nextUnit = unit.getParameters(); String gradientType = readGradientType( nextUnit ); if( !"linear".equals( gradientType ) ) { String msg = "Invalid value for background-image gradient type: " + gradientType; throw new IllegalArgumentException( msg ); } nextUnit = nextUnit.getNextLexicalUnit(); if( !checkComma( nextUnit) ) { throw new IllegalArgumentException( "Failed to parse background-image gradient." ); } nextUnit = nextUnit.getNextLexicalUnit(); String[] startPoint = readGradientPoint( nextUnit ); if( !( "left".equals( startPoint[ 0 ] ) && "top".equals( startPoint[ 1 ] ) ) ) { String msg = "Invalid value for background-image gradient start point: " + startPoint[ 0 ] + " " + startPoint[ 1 ]; throw new IllegalArgumentException( msg ); } nextUnit = nextUnit.getNextLexicalUnit(); nextUnit = nextUnit.getNextLexicalUnit(); if( !checkComma( nextUnit) ) { throw new IllegalArgumentException( "Failed to parse background-image gradient." ); } nextUnit = nextUnit.getNextLexicalUnit(); String[] endPoint = readGradientPoint( nextUnit ); if( "left".equals( endPoint[ 0 ] ) && "bottom".equals( endPoint[ 1 ] ) ) { vertical = true; } else if( "right".equals( endPoint[ 0 ] ) && "top".equals( endPoint[ 1 ] ) ) { vertical = false; } else { String msg = "Invalid value for background-image gradient end point: " + endPoint[ 0 ] + " " + endPoint[ 1 ]; throw new IllegalArgumentException( msg ); } nextUnit = nextUnit.getNextLexicalUnit(); nextUnit = nextUnit.getNextLexicalUnit(); TreeMap<Float, String> gradient = readGradientColorsPercents( nextUnit ); if( gradient.size() > 0 ) { gradient = normalizeGradientValue( gradient ); String[] gradientColors = getGradientColors( gradient ); float[] gradientPercents = getGradientPercents( gradient ); result = CssImage.createGradient( gradientColors, gradientPercents, vertical ); } return result; } static String readGradientType( LexicalUnit unit ) { String result = null; if( unit != null && unit.getLexicalUnitType() == LexicalUnit.SAC_IDENT ) { result = unit.getStringValue(); } return result; } static String[] readGradientPoint( LexicalUnit unit ) { String[] result = new String[ 2 ]; LexicalUnit x = unit; LexicalUnit y = null; if( unit != null ) { y = unit.getNextLexicalUnit(); } if( x != null && y != null ) { short xType = x.getLexicalUnitType(); short yType = y.getLexicalUnitType(); if( xType == LexicalUnit.SAC_IDENT && yType == LexicalUnit.SAC_IDENT ) { result[ 0 ] = x.getStringValue(); result[ 1 ] = y.getStringValue(); } else if( xType == LexicalUnit.SAC_INTEGER && yType == LexicalUnit.SAC_INTEGER ) { result[ 0 ] = Integer.toString( x.getIntegerValue() ); result[ 1 ] = Integer.toString( y.getIntegerValue() ); } } return result; } static TreeMap<Float, String> readGradientColorsPercents( LexicalUnit unit ) { TreeMap<Float, String> result = new TreeMap<>(); LexicalUnit nextUnit = unit; while( nextUnit != null ) { Float percent = null; String color = null; nextUnit = nextUnit.getNextLexicalUnit(); if( nextUnit != null && nextUnit.getLexicalUnitType() == LexicalUnit.SAC_FUNCTION ) { String function = nextUnit.getFunctionName(); if( "from".equals( function ) ) { percent = Float.valueOf( 0f ); LexicalUnit colorUnit = nextUnit.getParameters(); color = readGradientColor( colorUnit ); } else if( "to".equals( function ) ) { percent = Float.valueOf( 100f ); LexicalUnit colorUnit = nextUnit.getParameters(); color = readGradientColor( colorUnit ); } else if( "color-stop".equals( function ) ) { LexicalUnit percentUnit = nextUnit.getParameters(); percent = readGradientPercent( percentUnit ); LexicalUnit colorUnit = percentUnit.getNextLexicalUnit().getNextLexicalUnit(); color = readGradientColor( colorUnit ); } else { String msg = "Invalid value for background-image gradient: " + function; throw new IllegalArgumentException( msg ); } nextUnit = nextUnit.getNextLexicalUnit(); } if( percent != null && color != null ) { result.put( percent, color ); } } return result; } static String readGradientColor( LexicalUnit unit ) { CssColor result = readColor( unit ); return result != null ? result.toDefaultString() : null; } static Float readGradientPercent( LexicalUnit unit ) { Float result = null; short type = unit.getLexicalUnitType(); if( type == LexicalUnit.SAC_PERCENTAGE ) { result = Float.valueOf( normalizePercentValue( unit.getFloatValue() ) ); } else if( type == LexicalUnit.SAC_REAL ) { result = Float.valueOf( normalizePercentValue( unit.getFloatValue() * 100 ) ); } return result; } static boolean isBackgroundRepeatProperty( String property ) { return "background-repeat".equals( property ); } static CssIdentifier readBackgroundRepeat( LexicalUnit unit ) { CssIdentifier result = null; short type = unit.getLexicalUnitType(); if( type == LexicalUnit.SAC_IDENT ) { String value = unit.getStringValue(); if( "repeat".equals( value ) || "repeat-x".equals( value ) || "repeat-y".equals( value ) || "no-repeat".equals( value ) ) { result = new CssIdentifier( value ); } else { String msg = "Invalid value for background-repeat: " + value; throw new IllegalArgumentException( msg ); } } if( result == null ) { throw new IllegalArgumentException( "Failed to parse background-repeat " + toString( unit ) ); } return result; } static boolean isBackgroundPositionProperty( String property ) { return "background-position".equals( property ); } static CssIdentifier readBackgroundPosition( LexicalUnit unit ) { CssIdentifier result = null; short type = unit.getLexicalUnitType(); if( type == LexicalUnit.SAC_IDENT ) { StringBuilder buffer = new StringBuilder(); buffer.append( unit.getStringValue() ); LexicalUnit nextUnit = unit.getNextLexicalUnit(); if( nextUnit != null && nextUnit.getStringValue() != null ) { buffer.append( " " ); buffer.append( nextUnit.getStringValue() ); } else { buffer.append( " center" ); } String value = buffer.toString(); if( "left top".equals( value ) || "left center".equals( value ) || "left bottom".equals( value ) || "right top".equals( value ) || "right center".equals( value ) || "right bottom".equals( value ) || "center top".equals( value ) || "center center".equals( value ) || "center bottom".equals( value ) ) { result = new CssIdentifier( value ); } else { String msg = "Invalid value for background-position: " + value; throw new IllegalArgumentException( msg ); } } if( result == null ) { String msg = "Failed to parse background-position " + toString( unit ); throw new IllegalArgumentException( msg ); } return result; } static boolean isTextDecorationProperty( String property ) { return "text-decoration".equals( property ); } static CssIdentifier readTextDecoration( LexicalUnit unit ) { CssIdentifier result = null; short type = unit.getLexicalUnitType(); if( type == LexicalUnit.SAC_IDENT ) { String value = unit.getStringValue(); if( "underline".equals( value ) || "overline".equals( value ) || "line-through".equals( value ) || "none".equals( value ) ) { result = new CssIdentifier( value ); } else { throw new IllegalArgumentException( "Invalid value for text-decoration: " + value ); } } if( result == null ) { throw new IllegalArgumentException( "Failed to parse text-decoration " + toString( unit ) ); } return result; } static boolean isTextOverflowProperty( String property ) { return "text-overflow".equals( property ); } static CssIdentifier readTextOverflow( LexicalUnit unit ) { CssIdentifier result = null; short type = unit.getLexicalUnitType(); if( type == LexicalUnit.SAC_IDENT ) { String value = unit.getStringValue(); if( "clip".equals( value ) || "ellipsis".equals( value ) ) { result = new CssIdentifier( value ); } else { throw new IllegalArgumentException( "Invalid value for text-overflow: " + value ); } } if( result == null ) { throw new IllegalArgumentException( "Failed to parse text-overflow " + toString( unit ) ); } return result; } static boolean isTextAlignProperty( String property ) { return "text-align".equals( property ); } static CssIdentifier readTextAlign( LexicalUnit unit ) { CssIdentifier result = null; short type = unit.getLexicalUnitType(); if( type == LexicalUnit.SAC_IDENT ) { String value = unit.getStringValue(); if( "left".equals( value ) || "right".equals( value ) || "center".equals( value ) ) { result = new CssIdentifier( value ); } else { throw new IllegalArgumentException( "Invalid value for text-align: " + value ); } } if( result == null ) { throw new IllegalArgumentException( "Failed to parse text-align " + toString( unit ) ); } return result; } static boolean isCursorProperty( String property ) { return "cursor".equals( property ); } static CssCursor readCursor( LexicalUnit unit, ResourceLoader loader ) { CssCursor result = null; short type = unit.getLexicalUnitType(); if( type == LexicalUnit.SAC_URI ) { String value = unit.getStringValue(); result = CssCursor.valueOf( value, loader ); } else if( type == LexicalUnit.SAC_IDENT ) { String value = unit.getStringValue(); result = CssCursor.valueOf( value ); } if( result == null ) { throw new IllegalArgumentException( "Failed to parse cursor " + toString( unit ) ); } return result; } static boolean isFloatProperty( String property ) { return "opacity".equals( property ); } static CssFloat readFloat( LexicalUnit unit ) { CssFloat result; if( unit.getLexicalUnitType() == LexicalUnit.SAC_REAL || unit.getLexicalUnitType() == LexicalUnit.SAC_INTEGER ) { float value; if( unit.getLexicalUnitType() == LexicalUnit.SAC_INTEGER ) { value = unit.getIntegerValue(); } else { value = unit.getFloatValue(); } if( value >= 0 && value <= 1 ) { result = CssFloat.create( value ); } else { throw new IllegalArgumentException( "Float out of bounds: " + value ); } } else { String msg = "Failed to parse float " + toString( unit ); throw new IllegalArgumentException( msg ); } return result; } static boolean isAnimationProperty( String property ) { return "animation".equals( property ); } static CssAnimation readAnimation( LexicalUnit unit ) { CssAnimation result = new CssAnimation(); LexicalUnit nextUnit = unit; short type = nextUnit.getLexicalUnitType(); if( type == LexicalUnit.SAC_IDENT ) { String value = nextUnit.getStringValue(); if( "none".equals( value ) ) { nextUnit = null; } } while( nextUnit != null ) { String name; int duration; String timingFunction; type = nextUnit.getLexicalUnitType(); if( type == LexicalUnit.SAC_IDENT ) { name = nextUnit.getStringValue(); } else { String msg = "Invalid value for animation name: " + toString( nextUnit ); throw new IllegalArgumentException( msg ); } nextUnit = nextUnit.getNextLexicalUnit(); if( nextUnit == null ) { String msg = "Missing value for animation duration."; throw new IllegalArgumentException( msg ); } type = nextUnit.getLexicalUnitType(); if( type == LexicalUnit.SAC_SECOND ) { duration = Math.round( nextUnit.getFloatValue() * 1000 ); } else if( type == LexicalUnit.SAC_MILLISECOND ) { duration = Math.round( nextUnit.getFloatValue() ); } else { String msg = "Invalid value for animation duration: " + toString( nextUnit ); throw new IllegalArgumentException( msg ); } nextUnit = nextUnit.getNextLexicalUnit(); if( nextUnit == null ) { String msg = "Missing value for animation timing function."; throw new IllegalArgumentException( msg ); } type = nextUnit.getLexicalUnitType(); if( type == LexicalUnit.SAC_IDENT ) { timingFunction = nextUnit.getStringValue(); } else { String msg = "Invalid value for animation timing function: " + toString( nextUnit ); throw new IllegalArgumentException( msg ); } result.addAnimation( name, duration, timingFunction ); nextUnit = nextUnit.getNextLexicalUnit(); if( nextUnit != null ) { type = nextUnit.getLexicalUnitType(); if( type == LexicalUnit.SAC_OPERATOR_COMMA ) { nextUnit = nextUnit.getNextLexicalUnit(); } else { String msg = "Failed to parse animation " + toString( nextUnit ); throw new IllegalArgumentException( msg ); } } } return result; } static boolean isShadowProperty( String property ) { return "box-shadow".equals( property ) || "text-shadow".equals( property ); } static CssShadow readShadow( LexicalUnit unit ) { CssShadow result = null; boolean inset = false; Integer offsetX = null; Integer offsetY = null; int blur = 0; int spread = 0; CssColor color = CssColor.BLACK; LexicalUnit nextUnit = unit; short type = nextUnit.getLexicalUnitType(); if( type == LexicalUnit.SAC_IDENT ) { String value = nextUnit.getStringValue(); if( NONE.equals( value ) ) { result = CssShadow.NONE; } else if( INSET.equals( value ) ) { inset = true; } nextUnit = nextUnit.getNextLexicalUnit(); } if( result == null ) { boolean ok = true; int pos = 0; while( nextUnit != null && ok ) { pos++; Integer nextValue = readSingleLengthUnit( nextUnit ); ok &= nextValue != null && pos <= 4; if( ok ) { if( pos == 1 ) { offsetX = nextValue; } else if( pos == 2 ) { offsetY = nextValue; } else if( pos == 3 ) { blur = nextValue.intValue(); } else if( pos == 4 ) { spread = nextValue.intValue(); } nextUnit = nextUnit.getNextLexicalUnit(); } } if( nextUnit != null ) { type = nextUnit.getLexicalUnitType(); if( type == LexicalUnit.SAC_FUNCTION && "rgba".equals( nextUnit.getFunctionName() ) ) { color = readColorRgba( nextUnit ); } else { color = readColor( nextUnit ); } } } if( offsetX != null && offsetY != null ) { result = CssShadow.create( inset, offsetX.intValue(), offsetY.intValue(), blur, spread, color ); } if( result == null ) { throw new IllegalArgumentException( "Failed to parse shadow " + toString( unit ) ); } return result; } private static Integer readSingleLengthUnit( LexicalUnit unit ) { Integer result = null; short type = unit.getLexicalUnitType(); if( type == LexicalUnit.SAC_INTEGER ) { int value = unit.getIntegerValue(); if( value == 0 ) { result = Integer.valueOf( 0 ); } } else if( type == LexicalUnit.SAC_PIXEL ) { result = Integer.valueOf( ( int )unit.getFloatValue() ); } return result; } private static int normalizeRGBValue( int input ) { int result = input; if( input < 0 ) { result = 0; } else if( input > 255 ) { result = 255; } return result; } private static float normalizeAlphaValue( float input ) { float result = input; if( input < 0 ) { result = 0f; } else if( input > 1 ) { result = 1f; } return result; } private static float normalizePercentValue( float input ) { float result = input; if( input < 0f ) { result = 0f; } else if( input > 100f ) { result = 100f; } return result; } private static TreeMap<Float, String> normalizeGradientValue( TreeMap<Float, String> gradient ) { TreeMap<Float, String> result = gradient; if( gradient.size() > 0 ) { Float zero = Float.valueOf( 0f ); Float hundred = Float.valueOf( 100f ); if( gradient.get( zero ) == null ) { String firstColor = gradient.get( gradient.firstKey() ); result.put( zero, firstColor ); } if( gradient.get( hundred ) == null ) { String lastColor = gradient.get( gradient.lastKey() ); result.put( hundred, lastColor ); } } return result; } private static String[] getGradientColors( TreeMap<Float, String> gradient ) { Object[] values = gradient.values().toArray(); String[] result = new String[ values.length ]; for( int i = 0; i < values.length; i++ ) { result[ i ] = ( String )values[ i ]; } return result; } private static float[] getGradientPercents( TreeMap<Float, String> gradient ) { Object[] keys = gradient.keySet().toArray(); float[] result = new float[ keys.length ]; for( int i = 0; i < keys.length; i++ ) { result[ i ] = ( ( Float )keys[ i ] ).floatValue(); } return result; } static boolean checkComma( LexicalUnit unit ) { boolean result = false; if( unit != null && unit.getLexicalUnitType() == LexicalUnit.SAC_OPERATOR_COMMA ) { result = true; } return result; } static String toString( LexicalUnit value ) { StringBuilder buffer = new StringBuilder(); short type = value.getLexicalUnitType(); if( type == LexicalUnit.SAC_ATTR ) { buffer.append( "ATTR " + value.getStringValue() ); } else if( type == LexicalUnit.SAC_CENTIMETER || type == LexicalUnit.SAC_DEGREE || type == LexicalUnit.SAC_EM || type == LexicalUnit.SAC_EX || type == LexicalUnit.SAC_GRADIAN || type == LexicalUnit.SAC_HERTZ || type == LexicalUnit.SAC_INCH || type == LexicalUnit.SAC_KILOHERTZ || type == LexicalUnit.SAC_MILLIMETER || type == LexicalUnit.SAC_MILLISECOND || type == LexicalUnit.SAC_PERCENTAGE || type == LexicalUnit.SAC_PICA || type == LexicalUnit.SAC_POINT || type == LexicalUnit.SAC_PIXEL || type == LexicalUnit.SAC_RADIAN || type == LexicalUnit.SAC_SECOND || type == LexicalUnit.SAC_DIMENSION ) { buffer.append( "DIM " + value.getFloatValue() + value.getDimensionUnitText() ); } else if( type == LexicalUnit.SAC_RGBCOLOR ) { LexicalUnit parameters = value.getParameters(); buffer.append( "rgb(" + toString( parameters ) + ")" ); } else if( type == LexicalUnit.SAC_STRING_VALUE ) { buffer.append( "\"" + value.getStringValue() + "\"" ); } else if( type == LexicalUnit.SAC_IDENT ) { buffer.append( value.getStringValue() ); } else if( type == LexicalUnit.SAC_PIXEL ) { buffer.append( value.getFloatValue() + "px" ); } else if( type == LexicalUnit.SAC_INTEGER ) { buffer.append( value.getIntegerValue() ); } else if( type == LexicalUnit.SAC_OPERATOR_COMMA ) { buffer.append( "," ); } else if( type == LexicalUnit.SAC_ATTR ) { buffer.append( "ATTR " + value.getStringValue() ); } else if( type == LexicalUnit.SAC_FUNCTION ) { buffer.append( "UNKNOWN FUNCTION " + value.getFunctionName() ); } else if( type == LexicalUnit.SAC_DIMENSION ) { buffer.append( "UNKNOWN DIMENSION " + value ); } else { buffer.append( "unsupported unit " + value.getLexicalUnitType() ); } LexicalUnit next = value.getNextLexicalUnit(); if( next != null ) { buffer.append( " " ); buffer.append( toString( next ) ); } return buffer.toString(); } private static final class NamedColor { public NamedColor( int red, int green, int blue ) { this.red = red; this.green = green; this.blue = blue; } final int red; final int green; final int blue; } }