From 0d0f3b9af552baf0f03021f32242144a813af897 Mon Sep 17 00:00:00 2001 From: karlvr Date: Tue, 22 Jan 2013 19:08:08 +1300 Subject: [PATCH 01/29] Initial implementation of colour functions. Adds support for alpha into a subclass of ColorExpression, which then uses the rgba() format to output to the CSS. --- .../less4j/core/ast/ColorExpression.java | 51 ++- .../compiler/expressions/ColorFunctions.java | 217 +++++++++ .../expressions/ExpressionEvaluator.java | 1 + .../less4j/core/problems/ProblemsHandler.java | 9 + .../github/sommeri/less4j/utils/HSLColor.java | 417 ++++++++++++++++++ .../functions/functions-color.css | 43 ++ .../functions/functions-color.less | 49 ++ 7 files changed, 783 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java create mode 100644 src/main/java/com/github/sommeri/less4j/utils/HSLColor.java create mode 100644 src/test/resources/compile-basic-features/functions/functions-color.css create mode 100644 src/test/resources/compile-basic-features/functions/functions-color.less diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java b/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java index e6608797..12ee888a 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java @@ -1,5 +1,6 @@ package com.github.sommeri.less4j.core.ast; +import java.awt.Color; import java.util.Collections; import java.util.List; @@ -7,10 +8,10 @@ public class ColorExpression extends Expression { - private String value; - private int red; - private int green; - private int blue; + protected String value; + protected int red; + protected int green; + protected int blue; public ColorExpression(HiddenTokenAwareTree token, String value) { super(token); @@ -24,6 +25,10 @@ public ColorExpression(HiddenTokenAwareTree token, int red, int green, int blue) this.blue = blue; this.value = encode(red, green, blue); } + + public ColorExpression(HiddenTokenAwareTree token, Color color) { + this(token, color.getRed(), color.getGreen(), color.getBlue()); + } public String getValue() { return value; @@ -87,4 +92,42 @@ public String toString() { public ColorExpression clone() { return (ColorExpression) super.clone(); } + + public Color toColor() { + return new Color(this.red, this.green, this.blue); + } + + public static class ColorWithAlphaExpression extends ColorExpression { + + /** + * Alpha in the range 0-255. + */ + private int alpha; + + public ColorWithAlphaExpression(HiddenTokenAwareTree token, int red, int green, int blue, int alpha) { + super(token, red, green, blue); + this.alpha = alpha; + if (alpha != 255) { + this.value = encode(red, green, blue, alpha); + } + } + + public ColorWithAlphaExpression(HiddenTokenAwareTree token, Color color) { + this(token, color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); + } + + public int getAlpha() { + return alpha; + } + + protected String encode(int red, int green, int blue, int alpha) { + return "rgba(" + red + ", " + green + ", " + blue + ", " + (alpha/255.0) + ")"; + } + + @Override + public Color toColor() { + return new Color(this.red, this.green, this.blue, this.alpha); + } + + } } diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java new file mode 100644 index 00000000..f718cd66 --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -0,0 +1,217 @@ +package com.github.sommeri.less4j.core.compiler.expressions; + +import java.awt.Color; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.github.sommeri.less4j.core.ast.ASTCssNodeType; +import com.github.sommeri.less4j.core.ast.ColorExpression; +import com.github.sommeri.less4j.core.ast.ComposedExpression; +import com.github.sommeri.less4j.core.ast.Expression; +import com.github.sommeri.less4j.core.ast.FunctionExpression; +import com.github.sommeri.less4j.core.ast.NumberExpression; +import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; +import com.github.sommeri.less4j.core.problems.ProblemsHandler; +import com.github.sommeri.less4j.utils.HSLColor; + +public class ColorFunctions implements FunctionsPackage { + + protected static final String LIGHTEN = "lighten"; + protected static final String DARKEN = "darken"; + protected static final String HSLA = "hsla"; + + private static Map FUNCTIONS = new HashMap(); + static { + FUNCTIONS.put(LIGHTEN, new Lighten()); + FUNCTIONS.put(DARKEN, new Darken()); + FUNCTIONS.put(HSLA, new HSLA()); + } + + private final ProblemsHandler problemsHandler; + + public ColorFunctions(ProblemsHandler problemsHandler) { + this.problemsHandler = problemsHandler; + } + + /* (non-Javadoc) + * @see com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage#canEvaluate(com.github.sommeri.less4j.core.ast.FunctionExpression, com.github.sommeri.less4j.core.ast.Expression) + */ + @Override + public boolean canEvaluate(FunctionExpression input, Expression parameters) { + return FUNCTIONS.containsKey(input.getName()); + } + + /* (non-Javadoc) + * @see com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage#evaluate(com.github.sommeri.less4j.core.ast.FunctionExpression, com.github.sommeri.less4j.core.ast.Expression) + */ + @Override + public Expression evaluate(FunctionExpression input, Expression parameters) { + if (!canEvaluate(input, parameters)) + return input; + + Function function = FUNCTIONS.get(input.getName()); + return function.evaluate(parameters, problemsHandler); + } + +} + +abstract class ColorFunction implements Function { + + protected abstract Expression evaluate(ColorExpression color, NumberExpression amount); + + +} + +class Lighten extends AbstractColorAmountFunction { + + @Override + protected Expression evaluate(ColorExpression colorExpression, NumberExpression amount, HiddenTokenAwareTree token) { + Color color = colorExpression.toColor(); + float[] hsl = HSLColor.fromRGB(color); + hsl[2] += amount.getValueAsDouble(); + hsl[2] = clamp(hsl[2]); + return hsla(hsl, color.getAlpha() / 255.0f, token); + } + +} + +class Darken extends AbstractColorAmountFunction { + + protected Expression evaluate(ColorExpression colorExpression, NumberExpression amount, HiddenTokenAwareTree token) { + Color color = colorExpression.toColor(); + float[] hsl = HSLColor.fromRGB(color); + hsl[2] -= amount.getValueAsDouble(); + hsl[2] = clamp(hsl[2]); + return hsla(hsl, color.getAlpha() / 255.0f, token); + } + +} + +abstract class AbstractMultiParameterFunction implements Function { + + @Override + public Expression evaluate(Expression parameters, ProblemsHandler problemsHandler) { + if (parameters.getType() == ASTCssNodeType.COMPOSED_EXPRESSION) { + List splitParameters = ((ComposedExpression)parameters).splitByComma(); + if (splitParameters.size() >= getMinParameters() && splitParameters.size() <= getMaxParameters()) { + /* Validate */ + boolean valid = true; + for (int i = 0; i < splitParameters.size(); i++) { + if (!validateParameter(splitParameters.get(i), i, problemsHandler)) { + valid = false; + } + } + + if (valid) { + return evaluate(splitParameters, problemsHandler, parameters.getUnderlyingStructure()); + } else { + return parameters; + } + } else { + problemsHandler.wrongNumberOfArgumentsToFunction(parameters, 4); + return parameters; + } + } else { + problemsHandler.wrongNumberOfArgumentsToFunction(parameters, 4); + return parameters; + } + } + + protected abstract Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token); + + protected abstract int getMinParameters(); + + protected abstract int getMaxParameters(); + + protected abstract boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler); + + protected boolean validateParameter(Expression parameter, ASTCssNodeType expected, ProblemsHandler problemsHandler) { + if (parameter.getType() != expected) { + problemsHandler.wrongArgumentTypeToFunction(parameter, expected); + return false; + } else { + return true; + } + } + +} + +abstract class AbstractColorAmountFunction extends AbstractMultiParameterFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + ColorExpression color = (ColorExpression) splitParameters.get(0); + NumberExpression amount = (NumberExpression) splitParameters.get(1); + + return evaluate(color, amount, token); + } + + protected abstract Expression evaluate(ColorExpression color, NumberExpression amount, HiddenTokenAwareTree token); + + @Override + protected int getMinParameters() { + return 2; + } + + @Override + protected int getMaxParameters() { + return 2; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + switch (position) { + case 0: + return validateParameter(parameter, ASTCssNodeType.COLOR_EXPRESSION, problemsHandler); + case 1: + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + return false; + } + + static float clamp(float val) { + return Math.min(100, Math.max(0, val)); + } + + static ColorExpression hsl(float[] hsl, HiddenTokenAwareTree token) { + return hsla(hsl, 1.0f, token); + } + + static ColorExpression hsla(float[] hsl, float a, HiddenTokenAwareTree token) { + Color color = HSLColor.toRGB(hsl, a); + return new ColorExpression.ColorWithAlphaExpression(token, color); + } + +} + +class HSLA extends AbstractMultiParameterFunction { + + @Override + protected Expression evaluate(List parameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((NumberExpression)parameters.get(0), (NumberExpression)parameters.get(1), + (NumberExpression)parameters.get(2), (NumberExpression)parameters.get(3), token); + } + + private Expression evaluate(NumberExpression h, NumberExpression s, NumberExpression l, NumberExpression a, HiddenTokenAwareTree token) { + Color color = HSLColor.toRGB(h.getValueAsDouble().floatValue(), s.getValueAsDouble().floatValue(), + l.getValueAsDouble().floatValue(), a.getValueAsDouble().floatValue()); + return new ColorExpression.ColorWithAlphaExpression(token, color); + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + + @Override + protected int getMinParameters() { + return 4; + } + + @Override + protected int getMaxParameters() { + return 4; + } + +} diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java index 4db9fda8..c64319ae 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java @@ -60,6 +60,7 @@ public ExpressionEvaluator(Scope scope, ProblemsHandler problemsHandler) { colorsCalculator = new ColorsCalculator(problemsHandler); functions.add(new MathFunctions(problemsHandler)); functions.add(new StringFunctions(problemsHandler)); + functions.add(new ColorFunctions(problemsHandler)); functions.add(new UnknownFunctions(problemsHandler)); } diff --git a/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java b/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java index e2590740..ee99041e 100644 --- a/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java +++ b/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java @@ -4,6 +4,7 @@ import com.github.sommeri.less4j.LessCompiler.Problem; import com.github.sommeri.less4j.core.ast.ASTCssNode; +import com.github.sommeri.less4j.core.ast.ASTCssNodeType; import com.github.sommeri.less4j.core.ast.ArgumentDeclaration; import com.github.sommeri.less4j.core.ast.ComparisonExpressionOperator; import com.github.sommeri.less4j.core.ast.EscapedSelector; @@ -71,6 +72,14 @@ public void unsupportedKeyframesMember(ASTCssNode errorNode) { public void errFormatWrongFirstParameter(Expression param) { collector.addError(new CompilationError(param, "First argument of format function must be either string or escaped value.")); } + + public void wrongNumberOfArgumentsToFunction(Expression param, int expectedArguments) { + collector.addError(new CompilationError(param, "Wrong number of arguments to function, should be " + expectedArguments + ".")); + } + + public void wrongArgumentTypeToFunction(Expression param, ASTCssNodeType expected) { + collector.addError(new CompilationError(param, "Wrong argument type to function, should be " + expected + ".")); + } public void variablesCycle(List cycle) { collector.addError(new CompilationError(cycle.get(0), "Cyclic references among variables: " + printer.toVariablesString(cycle))); diff --git a/src/main/java/com/github/sommeri/less4j/utils/HSLColor.java b/src/main/java/com/github/sommeri/less4j/utils/HSLColor.java new file mode 100644 index 00000000..b77e65fb --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/utils/HSLColor.java @@ -0,0 +1,417 @@ +package com.github.sommeri.less4j.utils; + +import java.awt.Color; + +/** + * The HSLColor class provides methods to manipulate HSL (Hue, Saturation + * Luminance) values to create a corresponding Color object using the RGB + * ColorSpace. + * + * The HUE is the color, the Saturation is the purity of the color (with respect + * to grey) and Luminance is the brightness of the color (with respect to black + * and white) + * + * The Hue is specified as an angel between 0 - 360 degrees where red is 0, + * green is 120 and blue is 240. In between you have the colors of the rainbow. + * Saturation is specified as a percentage between 0 - 100 where 100 is fully + * saturated and 0 approaches gray. Luminance is specified as a percentage + * between 0 - 100 where 0 is black and 100 is white. + * + * In particular the HSL color space makes it easier change the Tone or Shade of + * a color by adjusting the luminance value. + * + * [Source http://tips4java.wordpress.com/2009/07/05/hsl-color/] + */ +public class HSLColor { + private Color rgb; + private float[] hsl; + private float alpha; + + /** + * Create a HSLColor object using an RGB Color object. + * + * @param rgb + * the RGB Color object + */ + public HSLColor(Color rgb) { + this.rgb = rgb; + hsl = fromRGB(rgb); + alpha = rgb.getAlpha() / 255.0f; + } + + /** + * Create a HSLColor object using individual HSL values and a default alpha + * value of 1.0. + * + * @param h + * is the Hue value in degrees between 0 - 360 + * @param s + * is the Saturation percentage between 0 - 100 + * @param l + * is the Lumanance percentage between 0 - 100 + */ + public HSLColor(float h, float s, float l) { + this(h, s, l, 1.0f); + } + + /** + * Create a HSLColor object using individual HSL values. + * + * @param h + * the Hue value in degrees between 0 - 360 + * @param s + * the Saturation percentage between 0 - 100 + * @param l + * the Lumanance percentage between 0 - 100 + * @param alpha + * the alpha value between 0 - 1 + */ + public HSLColor(float h, float s, float l, float alpha) { + hsl = new float[] { h, s, l }; + this.alpha = alpha; + rgb = toRGB(hsl, alpha); + } + + /** + * Create a HSLColor object using an an array containing the individual HSL + * values and with a default alpha value of 1. + * + * @param hsl + * array containing HSL values + */ + public HSLColor(float[] hsl) { + this(hsl, 1.0f); + } + + /** + * Create a HSLColor object using an an array containing the individual HSL + * values. + * + * @param hsl + * array containing HSL values + * @param alpha + * the alpha value between 0 - 1 + */ + public HSLColor(float[] hsl, float alpha) { + this.hsl = hsl; + this.alpha = alpha; + rgb = toRGB(hsl, alpha); + } + + /** + * Create a RGB Color object based on this HSLColor with a different Hue + * value. The degrees specified is an absolute value. + * + * @param degrees + * - the Hue value between 0 - 360 + * @return the RGB Color object + */ + public Color adjustHue(float degrees) { + return toRGB(degrees, hsl[1], hsl[2], alpha); + } + + /** + * Create a RGB Color object based on this HSLColor with a different Luminance + * value. The percent specified is an absolute value. + * + * @param percent + * - the Luminance value between 0 - 100 + * @return the RGB Color object + */ + public Color adjustLuminance(float percent) { + return toRGB(hsl[0], hsl[1], percent, alpha); + } + + /** + * Create a RGB Color object based on this HSLColor with a different + * Saturation value. The percent specified is an absolute value. + * + * @param percent + * - the Saturation value between 0 - 100 + * @return the RGB Color object + */ + public Color adjustSaturation(float percent) { + return toRGB(hsl[0], percent, hsl[2], alpha); + } + + /** + * Create a RGB Color object based on this HSLColor with a different Shade. + * Changing the shade will return a darker color. The percent specified is a + * relative value. + * + * @param percent + * - the value between 0 - 100 + * @return the RGB Color object + */ + public Color adjustShade(float percent) { + float multiplier = (100.0f - percent) / 100.0f; + float l = Math.max(0.0f, hsl[2] * multiplier); + + return toRGB(hsl[0], hsl[1], l, alpha); + } + + /** + * Create a RGB Color object based on this HSLColor with a different Tone. + * Changing the tone will return a lighter color. The percent specified is a + * relative value. + * + * @param percent + * - the value between 0 - 100 + * @return the RGB Color object + */ + public Color adjustTone(float percent) { + float multiplier = (100.0f + percent) / 100.0f; + float l = Math.min(100.0f, hsl[2] * multiplier); + + return toRGB(hsl[0], hsl[1], l, alpha); + } + + /** + * Get the Alpha value. + * + * @return the Alpha value. + */ + public float getAlpha() { + return alpha; + } + + /** + * Create a RGB Color object that is the complementary color of this HSLColor. + * This is a convenience method. The complementary color is determined by + * adding 180 degrees to the Hue value. + * + * @return the RGB Color object + */ + public Color getComplementary() { + float hue = (hsl[0] + 180.0f) % 360.0f; + return toRGB(hue, hsl[1], hsl[2]); + } + + /** + * Get the Hue value. + * + * @return the Hue value. + */ + public float getHue() { + return hsl[0]; + } + + /** + * Get the HSL values. + * + * @return the HSL values. + */ + public float[] getHSL() { + return hsl; + } + + /** + * Get the Luminance value. + * + * @return the Luminance value. + */ + public float getLuminance() { + return hsl[2]; + } + + /** + * Get the RGB Color object represented by this HDLColor. + * + * @return the RGB Color object. + */ + public Color getRGB() { + return rgb; + } + + /** + * Get the Saturation value. + * + * @return the Saturation value. + */ + public float getSaturation() { + return hsl[1]; + } + + public String toString() { + String toString = "HSLColor[h=" + hsl[0] + ",s=" + hsl[1] + ",l=" + hsl[2] + + ",alpha=" + alpha + "]"; + + return toString; + } + + /** + * Convert a RGB Color to it corresponding HSL values. + * + * @return an array containing the 3 HSL values. + */ + public static float[] fromRGB(Color color) { + // Get RGB values in the range 0 - 1 + + float[] rgb = color.getRGBColorComponents(null); + float r = rgb[0]; + float g = rgb[1]; + float b = rgb[2]; + + // Minimum and Maximum RGB values are used in the HSL calculations + + float min = Math.min(r, Math.min(g, b)); + float max = Math.max(r, Math.max(g, b)); + + // Calculate the Hue + + float h = 0; + + if (max == min) + h = 0; + else if (max == r) + h = ((60 * (g - b) / (max - min)) + 360) % 360; + else if (max == g) + h = (60 * (b - r) / (max - min)) + 120; + else if (max == b) + h = (60 * (r - g) / (max - min)) + 240; + + // Calculate the Luminance + + float l = (max + min) / 2; + System.out.println(max + " : " + min + " : " + l); + + // Calculate the Saturation + + float s = 0; + + if (max == min) + s = 0; + else if (l <= .5f) + s = (max - min) / (max + min); + else + s = (max - min) / (2 - max - min); + + return new float[] { h, s * 100, l * 100 }; + } + + /** + * Convert HSL values to a RGB Color with a default alpha value of 1. H (Hue) + * is specified as degrees in the range 0 - 360. S (Saturation) is specified + * as a percentage in the range 1 - 100. L (Lumanance) is specified as a + * percentage in the range 1 - 100. + * + * @param hsl + * an array containing the 3 HSL values + * + * @returns the RGB Color object + */ + public static Color toRGB(float[] hsl) { + return toRGB(hsl, 1.0f); + } + + /** + * Convert HSL values to a RGB Color. H (Hue) is specified as degrees in the + * range 0 - 360. S (Saturation) is specified as a percentage in the range 1 - + * 100. L (Lumanance) is specified as a percentage in the range 1 - 100. + * + * @param hsl + * an array containing the 3 HSL values + * @param alpha + * the alpha value between 0 - 1 + * + * @returns the RGB Color object + */ + public static Color toRGB(float[] hsl, float alpha) { + return toRGB(hsl[0], hsl[1], hsl[2], alpha); + } + + /** + * Convert HSL values to a RGB Color with a default alpha value of 1. + * + * @param h + * Hue is specified as degrees in the range 0 - 360. + * @param s + * Saturation is specified as a percentage in the range 1 - 100. + * @param l + * Lumanance is specified as a percentage in the range 1 - 100. + * + * @returns the RGB Color object + */ + public static Color toRGB(float h, float s, float l) { + return toRGB(h, s, l, 1.0f); + } + + /** + * Convert HSL values to a RGB Color. + * + * @param h + * Hue is specified as degrees in the range 0 - 360. + * @param s + * Saturation is specified as a percentage in the range 1 - 100. + * @param l + * Lumanance is specified as a percentage in the range 1 - 100. + * @param alpha + * the alpha value between 0 - 1 + * + * @returns the RGB Color object + */ + public static Color toRGB(float h, float s, float l, float alpha) { + if (s < 0.0f || s > 100.0f) { + String message = "Color parameter outside of expected range - Saturation"; + throw new IllegalArgumentException(message); + } + + if (l < 0.0f || l > 100.0f) { + String message = "Color parameter outside of expected range - Luminance"; + throw new IllegalArgumentException(message); + } + + if (alpha < 0.0f || alpha > 1.0f) { + String message = "Color parameter outside of expected range - Alpha"; + throw new IllegalArgumentException(message); + } + + // Formula needs all values between 0 - 1. + + h = h % 360.0f; + h /= 360f; + s /= 100f; + l /= 100f; + + float q = 0; + + if (l < 0.5) + q = l * (1 + s); + else + q = (l + s) - (s * l); + + float p = 2 * l - q; + + float r = Math.max(0, HueToRGB(p, q, h + (1.0f / 3.0f))); + float g = Math.max(0, HueToRGB(p, q, h)); + float b = Math.max(0, HueToRGB(p, q, h - (1.0f / 3.0f))); + + r = Math.min(r, 1.0f); + g = Math.min(g, 1.0f); + b = Math.min(b, 1.0f); + + return new Color(r, g, b, alpha); + } + + private static float HueToRGB(float p, float q, float h) { + if (h < 0) + h += 1; + + if (h > 1) + h -= 1; + + if (6 * h < 1) { + return p + ((q - p) * 6 * h); + } + + if (2 * h < 1) { + return q; + } + + if (3 * h < 2) { + return p + ((q - p) * 6 * ((2.0f / 3.0f) - h)); + } + + return p; + } +} diff --git a/src/test/resources/compile-basic-features/functions/functions-color.css b/src/test/resources/compile-basic-features/functions/functions-color.css new file mode 100644 index 00000000..a0f9bd03 --- /dev/null +++ b/src/test/resources/compile-basic-features/functions/functions-color.css @@ -0,0 +1,43 @@ +#functions { + color: #660000; + width: 16; + height: undefined("self"); + border-width: 5; + variable: 11; +} +#built-in { + escaped: -Some::weird(#thing, y); + lighten: #ffcccc; + darken: #330000; + saturate: #203c31; + desaturate: #29332f; + greyscale: #2e2e2e; + spin-p: #bf6a40; + spin-n: #bf4055; + format: "rgb(32, 128, 64)"; + format-string: "hello world"; + format-multiple: "hello earth 2"; + format-url-encode: "red is %23ff0000"; + eformat: rgb(32, 128, 64); + hue: 98; + saturation: 12%; + lightness: 95%; + rounded: 11; + roundedpx: 3px; + percentage: 20%; + color: #ff0011; +} +#built-in .is-a { + color: true; + color1: true; + color2: true; + keyword: true; + number: true; + string: true; + pixel: true; + percent: true; + em: true; +} +#alpha { + alpha: rgba(153, 94, 51, 0.6); +} diff --git a/src/test/resources/compile-basic-features/functions/functions-color.less b/src/test/resources/compile-basic-features/functions/functions-color.less new file mode 100644 index 00000000..c111f7e5 --- /dev/null +++ b/src/test/resources/compile-basic-features/functions/functions-color.less @@ -0,0 +1,49 @@ +#functions { + @var: 10; + color: _color("evil red"); // #660000 + width: increment(15); + height: undefined("self"); + border-width: add(2, 3); + variable: increment(@var); +} + +#built-in { + @r: 32; + escaped: e("-Some::weird(#thing, y)"); + lighten: lighten(#ff0000, 40%); + darken: darken(#ff0000, 40%); + saturate: saturate(#29332f, 20%); + desaturate: desaturate(#203c31, 20%); + greyscale: greyscale(#203c31); + spin-p: spin(hsl(340, 50%, 50%), 40); + spin-n: spin(hsl(30, 50%, 50%), -40); + format: %("rgb(%d, %d, %d)", @r, 128, 64); + format-string: %("hello %s", "world"); + format-multiple: %("hello %s %d", "earth", 2); + format-url-encode: %('red is %A', #ff0000); + eformat: e(%("rgb(%d, %d, %d)", @r, 128, 64)); + + hue: hue(hsl(98, 12%, 95%)); + saturation: saturation(hsl(98, 12%, 95%)); + lightness: lightness(hsl(98, 12%, 95%)); + rounded: round(@r/3); + roundedpx: round(10px / 3); + percentage: percentage(10px / 50); + color: color("#ff0011"); + + .is-a { + color: iscolor(#ddd); + color1: iscolor(red); + color2: iscolor(rgb(0, 0, 0)); + keyword: iskeyword(hello); + number: isnumber(32); + string: isstring("hello"); + pixel: ispixel(32px); + percent: ispercentage(32%); + em: isem(32em); + } +} + +#alpha { + alpha: darken(hsla(25, 50%, 50%, 0.6), 10%); +} From 083f3845b73ddc86e3ad9d5828045dc284e8ecf3 Mon Sep 17 00:00:00 2001 From: karlvr Date: Tue, 22 Jan 2013 17:23:02 +1300 Subject: [PATCH 02/29] Tests referenced incorrect resources (swapped) --- .../github/sommeri/less4j/compiler/CssOptimizationsTest.java | 2 +- .../java/com/github/sommeri/less4j/compiler/FunctionsTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/github/sommeri/less4j/compiler/CssOptimizationsTest.java b/src/test/java/com/github/sommeri/less4j/compiler/CssOptimizationsTest.java index c62bd387..01a5a047 100644 --- a/src/test/java/com/github/sommeri/less4j/compiler/CssOptimizationsTest.java +++ b/src/test/java/com/github/sommeri/less4j/compiler/CssOptimizationsTest.java @@ -9,7 +9,7 @@ public class CssOptimizationsTest extends BasicFeaturesTest { - private static final String standardCases = "src/test/resources/compile-basic-features/functions/"; + private static final String standardCases = "src/test/resources/compile-basic-features/css-optimizations/"; public CssOptimizationsTest(File inputFile, File outputFile, String testName) { super(inputFile, outputFile, testName); diff --git a/src/test/java/com/github/sommeri/less4j/compiler/FunctionsTest.java b/src/test/java/com/github/sommeri/less4j/compiler/FunctionsTest.java index 2ea83ac4..51f4b0ee 100644 --- a/src/test/java/com/github/sommeri/less4j/compiler/FunctionsTest.java +++ b/src/test/java/com/github/sommeri/less4j/compiler/FunctionsTest.java @@ -9,7 +9,7 @@ public class FunctionsTest extends BasicFeaturesTest { - private static final String standardCases = "src/test/resources/compile-basic-features/css-optimizations/"; + private static final String standardCases = "src/test/resources/compile-basic-features/functions/"; public FunctionsTest(File inputFile, File outputFile, String testName) { super(inputFile, outputFile, testName); From 37ee37077519cdf42f8eda4f83741c40797b7e7f Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Tue, 22 Jan 2013 20:13:40 +1300 Subject: [PATCH 03/29] ColorFunctions: create constants for all of the functions as a sign of optimism --- .../compiler/expressions/ColorFunctions.java | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java index f718cd66..76df07b2 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -16,10 +16,45 @@ import com.github.sommeri.less4j.utils.HSLColor; public class ColorFunctions implements FunctionsPackage { - + + protected static final String RGB = "rgb"; + protected static final String RGBA = "rgba"; + protected static final String ARGB = "argb"; + protected static final String HSL = "hsl"; + protected static final String HSLA = "hsla"; + protected static final String HSV = "hsv"; + protected static final String HSVA = "hsva"; + + protected static final String HUE = "hue"; + protected static final String SATURATION = "saturation"; + protected static final String LIGHTNESS = "lightness"; + protected static final String RED = "red"; + protected static final String GREEN = "green"; + protected static final String BLUE = "blue"; + protected static final String ALPHA = "alpha"; + protected static final String LUME = "luma"; + + protected static final String SATURATE = "saturate"; + protected static final String DESATURATE = "desaturate"; protected static final String LIGHTEN = "lighten"; protected static final String DARKEN = "darken"; - protected static final String HSLA = "hsla"; + protected static final String FADEIN = "fadein"; + protected static final String FADEOUT = "fadeout"; + protected static final String FADE = "fade"; + protected static final String SPIN = "spin"; + protected static final String MIX = "mix"; + protected static final String GREYSCALE = "greyscale"; + protected static final String CONTRAST = "contrast"; + + protected static final String MULTIPLY = "multiply"; + protected static final String SCREEN = "screen"; + protected static final String OVERLAY = "overlay"; + protected static final String SOFTLIGHT = "softlight"; + protected static final String HARDLIGHT = "hardlight"; + protected static final String DIFFERENCE = "difference"; + protected static final String EXCLUSION = "exclusion"; + protected static final String AVERAGE = "average"; + protected static final String NEGATION = "negation"; private static Map FUNCTIONS = new HashMap(); static { From b0b884f9980fcdf86f8ddac3a936ac0f358a4d5d Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Tue, 22 Jan 2013 21:06:19 +1300 Subject: [PATCH 04/29] Color functions: implement rob, rgba, hsl, hsla, argb. The implementation of ARGB uses a new node called Anonymous. I copied this approach from the less.js source. --- .../less4j/core/ast/ASTCssNodeType.java | 2 +- .../less4j/core/ast/AnonymousExpression.java | 37 +++ .../less4j/core/ast/ColorExpression.java | 29 +- .../compiler/expressions/ColorFunctions.java | 310 +++++++++++++----- .../sommeri/less4j/utils/CssPrinter.java | 9 + .../functions/colors.css | 58 ++++ .../functions/colors.less | 65 ++++ 7 files changed, 417 insertions(+), 93 deletions(-) create mode 100644 src/main/java/com/github/sommeri/less4j/core/ast/AnonymousExpression.java create mode 100644 src/test/resources/compile-basic-features/functions/colors.css create mode 100644 src/test/resources/compile-basic-features/functions/colors.less diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/ASTCssNodeType.java b/src/main/java/com/github/sommeri/less4j/core/ast/ASTCssNodeType.java index ba7a5c9b..bb0a14c4 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/ASTCssNodeType.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/ASTCssNodeType.java @@ -1,5 +1,5 @@ package com.github.sommeri.less4j.core.ast; public enum ASTCssNodeType { - UNKNOWN, CSS_CLASS, DECLARATION, STYLE_SHEET, RULE_SET, SELECTOR, SIMPLE_SELECTOR, PSEUDO_CLASS, PSEUDO_ELEMENT, SELECTOR_ATTRIBUTE, ID_SELECTOR, CHARSET_DECLARATION, FONT_FACE, IDENTIFIER_EXPRESSION, COMPOSED_EXPRESSION, STRING_EXPRESSION, NUMBER, COLOR_EXPRESSION, FUNCTION, MEDIA, COMMENT, DECLARATIONS_BODY, SELECTOR_OPERATOR, SELECTOR_COMBINATOR, EXPRESSION_OPERATOR, NTH, NAMED_EXPRESSION, MEDIA_QUERY, MEDIA_EXPRESSION, MEDIUM, MEDIUM_MODIFIER, MEDIUM_TYPE, MEDIUM_EX_FEATURE, VARIABLE_DECLARATION, VARIABLE, INDIRECT_VARIABLE, PARENTHESES_EXPRESSION, SIGNED_EXPRESSION, ARGUMENT_DECLARATION, MIXIN_REFERENCE, GUARD_CONDITION, COMPARISON_EXPRESSION, GUARD, NESTED_SELECTOR_APPENDER, REUSABLE_STRUCTURE, FAULTY_EXPRESSION, ESCAPED_SELECTOR, ESCAPED_VALUE, INTERPOLABLE_NAME, FIXED_NAME_PART, VARIABLE_NAME_PART, KEYFRAMES, KEYFRAMES_NAME, KEYFRAMES_BODY, REUSABLE_STRUCTURE_NAME, VIEWPORT, GENERAL_BODY, PAGE, NAME, PAGE_MA, PAGE_MARGIN_BOX, IMPORT, FAULTY_NODE + UNKNOWN, CSS_CLASS, DECLARATION, STYLE_SHEET, RULE_SET, SELECTOR, SIMPLE_SELECTOR, PSEUDO_CLASS, PSEUDO_ELEMENT, SELECTOR_ATTRIBUTE, ID_SELECTOR, CHARSET_DECLARATION, FONT_FACE, IDENTIFIER_EXPRESSION, COMPOSED_EXPRESSION, STRING_EXPRESSION, NUMBER, COLOR_EXPRESSION, FUNCTION, MEDIA, COMMENT, DECLARATIONS_BODY, SELECTOR_OPERATOR, SELECTOR_COMBINATOR, EXPRESSION_OPERATOR, NTH, NAMED_EXPRESSION, MEDIA_QUERY, MEDIA_EXPRESSION, MEDIUM, MEDIUM_MODIFIER, MEDIUM_TYPE, MEDIUM_EX_FEATURE, VARIABLE_DECLARATION, VARIABLE, INDIRECT_VARIABLE, PARENTHESES_EXPRESSION, SIGNED_EXPRESSION, ARGUMENT_DECLARATION, MIXIN_REFERENCE, GUARD_CONDITION, COMPARISON_EXPRESSION, GUARD, NESTED_SELECTOR_APPENDER, REUSABLE_STRUCTURE, FAULTY_EXPRESSION, ESCAPED_SELECTOR, ESCAPED_VALUE, INTERPOLABLE_NAME, FIXED_NAME_PART, VARIABLE_NAME_PART, KEYFRAMES, KEYFRAMES_NAME, KEYFRAMES_BODY, REUSABLE_STRUCTURE_NAME, VIEWPORT, GENERAL_BODY, PAGE, NAME, PAGE_MA, PAGE_MARGIN_BOX, IMPORT, FAULTY_NODE, ANONYMOUS } diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/AnonymousExpression.java b/src/main/java/com/github/sommeri/less4j/core/ast/AnonymousExpression.java new file mode 100644 index 00000000..b75be232 --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/ast/AnonymousExpression.java @@ -0,0 +1,37 @@ +package com.github.sommeri.less4j.core.ast; + +import java.util.Collections; +import java.util.List; + +import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; + +public class AnonymousExpression extends Expression { + + private String value; + + public AnonymousExpression(HiddenTokenAwareTree token, String value) { + super(token); + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public List getChilds() { + return Collections.emptyList(); + } + + @Override + public ASTCssNodeType getType() { + return ASTCssNodeType.ANONYMOUS; + } + + @Override + public AnonymousExpression clone() { + AnonymousExpression result = (AnonymousExpression) super.clone(); + return result; + } + +} diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java b/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java index 12ee888a..31fc26a8 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java @@ -66,7 +66,7 @@ private String encode(int red, int green, int blue) { return "#" + toHex(red) + toHex(green) + toHex(blue); } - private String toHex(int color) { + protected String toHex(int color) { String prefix = ""; if (color<16) prefix = "0"; @@ -93,6 +93,10 @@ public ColorExpression clone() { return (ColorExpression) super.clone(); } + public String toARGB() { + return "#FF" + toHex(red) + toHex(green) + toHex(blue); + } + public Color toColor() { return new Color(this.red, this.green, this.blue); } @@ -100,33 +104,38 @@ public Color toColor() { public static class ColorWithAlphaExpression extends ColorExpression { /** - * Alpha in the range 0-255. + * Alpha in the range 0-1. */ - private int alpha; + private float alpha; - public ColorWithAlphaExpression(HiddenTokenAwareTree token, int red, int green, int blue, int alpha) { + public ColorWithAlphaExpression(HiddenTokenAwareTree token, int red, int green, int blue, float alpha) { super(token, red, green, blue); this.alpha = alpha; - if (alpha != 255) { + if (alpha != 1.0) { this.value = encode(red, green, blue, alpha); } } public ColorWithAlphaExpression(HiddenTokenAwareTree token, Color color) { - this(token, color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); + this(token, color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha() / 255.0f); } - public int getAlpha() { + public float getAlpha() { return alpha; } - protected String encode(int red, int green, int blue, int alpha) { - return "rgba(" + red + ", " + green + ", " + blue + ", " + (alpha/255.0) + ")"; + protected String encode(int red, int green, int blue, float alpha) { + return "rgba(" + red + ", " + green + ", " + blue + ", " + alpha + ")"; + } + + @Override + public String toARGB() { + return "#" + toHex(Math.round(alpha * 255)) + toHex(red) + toHex(green) + toHex(blue); } @Override public Color toColor() { - return new Color(this.red, this.green, this.blue, this.alpha); + return new Color(this.red, this.green, this.blue, Math.round(this.alpha * 255)); } } diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java index 76df07b2..40033aed 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -1,16 +1,19 @@ package com.github.sommeri.less4j.core.compiler.expressions; import java.awt.Color; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import com.github.sommeri.less4j.core.ast.ASTCssNodeType; +import com.github.sommeri.less4j.core.ast.AnonymousExpression; import com.github.sommeri.less4j.core.ast.ColorExpression; import com.github.sommeri.less4j.core.ast.ComposedExpression; import com.github.sommeri.less4j.core.ast.Expression; import com.github.sommeri.less4j.core.ast.FunctionExpression; import com.github.sommeri.less4j.core.ast.NumberExpression; +import com.github.sommeri.less4j.core.ast.NumberExpression.Dimension; import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; import com.github.sommeri.less4j.core.problems.ProblemsHandler; import com.github.sommeri.less4j.utils.HSLColor; @@ -61,6 +64,10 @@ public class ColorFunctions implements FunctionsPackage { FUNCTIONS.put(LIGHTEN, new Lighten()); FUNCTIONS.put(DARKEN, new Darken()); FUNCTIONS.put(HSLA, new HSLA()); + FUNCTIONS.put(HSL, new HSL()); + FUNCTIONS.put(RGBA, new RGBA()); + FUNCTIONS.put(RGB, new RGB()); + FUNCTIONS.put(ARGB, new ARGB()); } private final ProblemsHandler problemsHandler; @@ -69,16 +76,25 @@ public ColorFunctions(ProblemsHandler problemsHandler) { this.problemsHandler = problemsHandler; } - /* (non-Javadoc) - * @see com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage#canEvaluate(com.github.sommeri.less4j.core.ast.FunctionExpression, com.github.sommeri.less4j.core.ast.Expression) + /* + * (non-Javadoc) + * + * @see com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage# + * canEvaluate(com.github.sommeri.less4j.core.ast.FunctionExpression, + * com.github.sommeri.less4j.core.ast.Expression) */ @Override public boolean canEvaluate(FunctionExpression input, Expression parameters) { return FUNCTIONS.containsKey(input.getName()); } - - /* (non-Javadoc) - * @see com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage#evaluate(com.github.sommeri.less4j.core.ast.FunctionExpression, com.github.sommeri.less4j.core.ast.Expression) + + /* + * (non-Javadoc) + * + * @see + * com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage#evaluate + * (com.github.sommeri.less4j.core.ast.FunctionExpression, + * com.github.sommeri.less4j.core.ast.Expression) */ @Override public Expression evaluate(FunctionExpression input, Expression parameters) { @@ -91,13 +107,6 @@ public Expression evaluate(FunctionExpression input, Expression parameters) { } -abstract class ColorFunction implements Function { - - protected abstract Expression evaluate(ColorExpression color, NumberExpression amount); - - -} - class Lighten extends AbstractColorAmountFunction { @Override @@ -123,44 +132,192 @@ protected Expression evaluate(ColorExpression colorExpression, NumberExpression } +class RGB extends AbstractColorFunction { + + @Override + protected Expression evaluate(List parameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((NumberExpression) parameters.get(0), (NumberExpression) parameters.get(1), (NumberExpression) parameters.get(2), token); + } + + private Expression evaluate(NumberExpression r, NumberExpression g, NumberExpression b, HiddenTokenAwareTree token) { + return new ColorExpression(token, (int) Math.round(scaled(r, 255)), (int) Math.round(scaled(g, 255)), (int) Math.round(scaled(b, 255))); + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + + @Override + protected int getMinParameters() { + return 3; + } + + @Override + protected int getMaxParameters() { + return 3; + } + +} + +class RGBA extends AbstractColorFunction { + + @Override + protected Expression evaluate(List parameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((NumberExpression) parameters.get(0), (NumberExpression) parameters.get(1), (NumberExpression) parameters.get(2), + (NumberExpression) parameters.get(3), token); + } + + private Expression evaluate(NumberExpression r, NumberExpression g, NumberExpression b, NumberExpression a, HiddenTokenAwareTree token) { + return new ColorExpression.ColorWithAlphaExpression(token, (int) Math.round(scaled(r, 255)), (int) Math.round(scaled(g, 255)), + (int) Math.round(scaled(b, 255)), (float) number(a)); + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + + @Override + protected int getMinParameters() { + return 4; + } + + @Override + protected int getMaxParameters() { + return 4; + } + +} + +class HSL extends AbstractColorFunction { + + @Override + protected Expression evaluate(List parameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((NumberExpression) parameters.get(0), (NumberExpression) parameters.get(1), (NumberExpression) parameters.get(2), token); + } + + private Expression evaluate(NumberExpression h, NumberExpression s, NumberExpression l, HiddenTokenAwareTree token) { + Color color = HSLColor.toRGB(h.getValueAsDouble().floatValue(), s.getValueAsDouble().floatValue(), l.getValueAsDouble().floatValue(), + 1.0f); + return new ColorExpression.ColorWithAlphaExpression(token, color); + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + + @Override + protected int getMinParameters() { + return 3; + } + + @Override + protected int getMaxParameters() { + return 3; + } + +} + +class HSLA extends AbstractColorFunction { + + @Override + protected Expression evaluate(List parameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((NumberExpression) parameters.get(0), (NumberExpression) parameters.get(1), (NumberExpression) parameters.get(2), + (NumberExpression) parameters.get(3), token); + } + + private Expression evaluate(NumberExpression h, NumberExpression s, NumberExpression l, NumberExpression a, HiddenTokenAwareTree token) { + Color color = HSLColor.toRGB(h.getValueAsDouble().floatValue(), s.getValueAsDouble().floatValue(), l.getValueAsDouble().floatValue(), a + .getValueAsDouble().floatValue()); + return new ColorExpression.ColorWithAlphaExpression(token, color); + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + + @Override + protected int getMinParameters() { + return 4; + } + + @Override + protected int getMaxParameters() { + return 4; + } + +} + +class ARGB extends AbstractColorFunction { + + @Override + protected Expression evaluate(List parameters, ProblemsHandler problemHandler, HiddenTokenAwareTree token) { + return new AnonymousExpression(token, ((ColorExpression)parameters.get(0)).toARGB()); + } + + @Override + protected int getMaxParameters() { + return 1; + } + + @Override + protected int getMinParameters() { + return 1; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.COLOR_EXPRESSION, problemsHandler); + } + +} + abstract class AbstractMultiParameterFunction implements Function { - + @Override public Expression evaluate(Expression parameters, ProblemsHandler problemsHandler) { - if (parameters.getType() == ASTCssNodeType.COMPOSED_EXPRESSION) { - List splitParameters = ((ComposedExpression)parameters).splitByComma(); - if (splitParameters.size() >= getMinParameters() && splitParameters.size() <= getMaxParameters()) { - /* Validate */ - boolean valid = true; - for (int i = 0; i < splitParameters.size(); i++) { - if (!validateParameter(splitParameters.get(i), i, problemsHandler)) { - valid = false; - } - } - - if (valid) { - return evaluate(splitParameters, problemsHandler, parameters.getUnderlyingStructure()); - } else { - return parameters; - } + List splitParameters; + + if (getMinParameters() == 1 && parameters.getType() != ASTCssNodeType.COMPOSED_EXPRESSION) { + splitParameters = Collections.singletonList(parameters); + } else if (parameters.getType() == ASTCssNodeType.COMPOSED_EXPRESSION) { + splitParameters = ((ComposedExpression) parameters).splitByComma(); + } else { + problemsHandler.wrongNumberOfArgumentsToFunction(parameters, getMinParameters()); + return parameters; + } + + if (splitParameters.size() >= getMinParameters() && splitParameters.size() <= getMaxParameters()) { + /* Validate */ + boolean valid = true; + for (int i = 0; i < splitParameters.size(); i++) { + if (!validateParameter(splitParameters.get(i), i, problemsHandler)) { + valid = false; + } + } + + if (valid) { + return evaluate(splitParameters, problemsHandler, parameters.getUnderlyingStructure()); } else { - problemsHandler.wrongNumberOfArgumentsToFunction(parameters, 4); return parameters; } } else { - problemsHandler.wrongNumberOfArgumentsToFunction(parameters, 4); + problemsHandler.wrongNumberOfArgumentsToFunction(parameters, getMinParameters()); return parameters; } } - + protected abstract Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token); protected abstract int getMinParameters(); - + protected abstract int getMaxParameters(); - + protected abstract boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler); - + protected boolean validateParameter(Expression parameter, ASTCssNodeType expected, ProblemsHandler problemsHandler) { if (parameter.getType() != expected) { problemsHandler.wrongArgumentTypeToFunction(parameter, expected); @@ -169,16 +326,49 @@ protected boolean validateParameter(Expression parameter, ASTCssNodeType expecte return true; } } - + +} + +abstract class AbstractColorFunction extends AbstractMultiParameterFunction { + + static float clamp(float val) { + return Math.min(100, Math.max(0, val)); + } + + static ColorExpression hsl(float[] hsl, HiddenTokenAwareTree token) { + return hsla(hsl, 1.0f, token); + } + + static ColorExpression hsla(float[] hsl, float a, HiddenTokenAwareTree token) { + Color color = HSLColor.toRGB(hsl, a); + return new ColorExpression.ColorWithAlphaExpression(token, color); + } + + static double scaled(NumberExpression n, int size) { + if (n.getDimension() == Dimension.PERCENTAGE) { + return n.getValueAsDouble() * size / 100; + } else { + return number(n); + } + } + + static double number(NumberExpression n) { + if (n.getDimension() == Dimension.PERCENTAGE) { + return n.getValueAsDouble() / 100; + } else { + return n.getValueAsDouble(); + } + } + } -abstract class AbstractColorAmountFunction extends AbstractMultiParameterFunction { +abstract class AbstractColorAmountFunction extends AbstractColorFunction { @Override protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { ColorExpression color = (ColorExpression) splitParameters.get(0); NumberExpression amount = (NumberExpression) splitParameters.get(1); - + return evaluate(color, amount, token); } @@ -204,49 +394,5 @@ protected boolean validateParameter(Expression parameter, int position, Problems } return false; } - - static float clamp(float val) { - return Math.min(100, Math.max(0, val)); - } - - static ColorExpression hsl(float[] hsl, HiddenTokenAwareTree token) { - return hsla(hsl, 1.0f, token); - } - - static ColorExpression hsla(float[] hsl, float a, HiddenTokenAwareTree token) { - Color color = HSLColor.toRGB(hsl, a); - return new ColorExpression.ColorWithAlphaExpression(token, color); - } - -} - -class HSLA extends AbstractMultiParameterFunction { - - @Override - protected Expression evaluate(List parameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { - return evaluate((NumberExpression)parameters.get(0), (NumberExpression)parameters.get(1), - (NumberExpression)parameters.get(2), (NumberExpression)parameters.get(3), token); - } - - private Expression evaluate(NumberExpression h, NumberExpression s, NumberExpression l, NumberExpression a, HiddenTokenAwareTree token) { - Color color = HSLColor.toRGB(h.getValueAsDouble().floatValue(), s.getValueAsDouble().floatValue(), - l.getValueAsDouble().floatValue(), a.getValueAsDouble().floatValue()); - return new ColorExpression.ColorWithAlphaExpression(token, color); - } - - @Override - protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { - return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); - } - - @Override - protected int getMinParameters() { - return 4; - } - - @Override - protected int getMaxParameters() { - return 4; - } } diff --git a/src/main/java/com/github/sommeri/less4j/utils/CssPrinter.java b/src/main/java/com/github/sommeri/less4j/utils/CssPrinter.java index 000976d2..b0487445 100644 --- a/src/main/java/com/github/sommeri/less4j/utils/CssPrinter.java +++ b/src/main/java/com/github/sommeri/less4j/utils/CssPrinter.java @@ -8,6 +8,7 @@ import com.github.sommeri.less4j.core.ExtendedStringBuilder; import com.github.sommeri.less4j.core.NotACssException; import com.github.sommeri.less4j.core.ast.ASTCssNode; +import com.github.sommeri.less4j.core.ast.AnonymousExpression; import com.github.sommeri.less4j.core.ast.Body; import com.github.sommeri.less4j.core.ast.CharsetDeclaration; import com.github.sommeri.less4j.core.ast.ColorExpression; @@ -214,6 +215,9 @@ public boolean switchOnType(ASTCssNode node) { case IMPORT: return appendImport((Import) node); + + case ANONYMOUS: + return appendAnonymous((AnonymousExpression) node); case ESCAPED_SELECTOR: case PARENTHESES_EXPRESSION: @@ -227,6 +231,11 @@ public boolean switchOnType(ASTCssNode node) { throw new IllegalStateException("Unknown: " + node.getType() + " " + node.getSourceLine() + ":" + node.getCharPositionInSourceLine()); } } + + private boolean appendAnonymous(AnonymousExpression node) { + builder.append(node.getValue()); + return true; + } private boolean appendImport(Import node) { builder.append("@import").ensureSeparator(); diff --git a/src/test/resources/compile-basic-features/functions/colors.css b/src/test/resources/compile-basic-features/functions/colors.css new file mode 100644 index 00000000..b4516425 --- /dev/null +++ b/src/test/resources/compile-basic-features/functions/colors.css @@ -0,0 +1,58 @@ +#yelow #short { + color: #fea; +} +#yelow #long { + color: #ffeeaa; +} +#yelow #rgba { + color: rgba(255, 238, 170, 0.1); +} +#yelow #argb { + color: #1affeeaa; +} +#blue #short { + color: #00f; +} +#blue #long { + color: #0000ff; +} +#blue #rgba { + color: rgba(0, 0, 255, 0.1); +} +#blue #argb { + color: #1a0000ff; +} +#alpha #hsla { + color: rgba(61, 45, 41, 0.6); +} +#overflow .a { + color: #000000; +} +#overflow .b { + color: #ffffff; +} +#overflow .c { + color: #ffffff; +} +#overflow .d { + color: #00ff00; +} +#grey { + color: #c8c8c8; +} +#808080 { + color: #808080; +} +#00ff00 { + color: #00ff00; +} +.lightenblue { + color: #3333ff; +} +.darkenblue { + color: #0000cc; +} +.unknowncolors { + color: blue2; + border: 2px solid superred; +} diff --git a/src/test/resources/compile-basic-features/functions/colors.less b/src/test/resources/compile-basic-features/functions/colors.less new file mode 100644 index 00000000..3f808625 --- /dev/null +++ b/src/test/resources/compile-basic-features/functions/colors.less @@ -0,0 +1,65 @@ +#yelow { + #short { + color: #fea; + } + #long { + color: #ffeeaa; + } + #rgba { + color: rgba(255, 238, 170, 0.1); + } + #argb { + color: argb(rgba(255, 238, 170, 0.1)); + } +} + +#blue { + #short { + color: #00f; + } + #long { + color: #0000ff; + } + #rgba { + color: rgba(0, 0, 255, 0.1); + } + #argb { + color: argb(rgba(0, 0, 255, 0.1)); + } +} + +#alpha #hsla { + color: hsla(11, 20%, 20%, 0.6); +} + +#overflow { + .a { color: #111111 - #444444; } // #000000 + .b { color: #eee + #fff; } // #ffffff + .c { color: #aaa * 3; } // #ffffff + .d { color: #00ee00 + #009900; } // #00ff00 +} + +#grey { + color: rgb(200, 200, 200); +} + +#808080 { + color: hsl(50, 0%, 50%); +} + +#00ff00 { + color: hsl(120, 100%, 50%); +} + +.lightenblue { + color: lighten(blue, 10%); +} + +.darkenblue { + color: darken(blue, 10%); +} + +.unknowncolors { + color: blue2; + border: 2px solid superred; +} From ed299d3898fac0f42370ed9530aa00c2f0b76f6c Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Tue, 22 Jan 2013 21:09:35 +1300 Subject: [PATCH 05/29] Mixin test-case output tweaked to match now implemented rgba function --- .../compile-basic-features/mixins/mixins-list-argument.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/compile-basic-features/mixins/mixins-list-argument.css b/src/test/resources/compile-basic-features/mixins/mixins-list-argument.css index 4585287d..c80e639b 100644 --- a/src/test/resources/compile-basic-features/mixins/mixins-list-argument.css +++ b/src/test/resources/compile-basic-features/mixins/mixins-list-argument.css @@ -4,5 +4,5 @@ content2: 4% identifier "hello"; } #gradientBarUsePlace { - text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); } From 8285d43d58533c51eddffefa65fd09937de7ad9b Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Tue, 22 Jan 2013 22:30:06 +1300 Subject: [PATCH 06/29] Color functions: more functions implemented Hue & spin don't quite match the sample yet. --- .../compiler/expressions/ColorFunctions.java | 202 +++++++++++++++--- 1 file changed, 170 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java index 40033aed..f0733e5d 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -35,7 +35,7 @@ public class ColorFunctions implements FunctionsPackage { protected static final String GREEN = "green"; protected static final String BLUE = "blue"; protected static final String ALPHA = "alpha"; - protected static final String LUME = "luma"; + protected static final String LUMA = "luma"; protected static final String SATURATE = "saturate"; protected static final String DESATURATE = "desaturate"; @@ -61,13 +61,29 @@ public class ColorFunctions implements FunctionsPackage { private static Map FUNCTIONS = new HashMap(); static { - FUNCTIONS.put(LIGHTEN, new Lighten()); - FUNCTIONS.put(DARKEN, new Darken()); - FUNCTIONS.put(HSLA, new HSLA()); - FUNCTIONS.put(HSL, new HSL()); - FUNCTIONS.put(RGBA, new RGBA()); FUNCTIONS.put(RGB, new RGB()); + FUNCTIONS.put(RGBA, new RGBA()); FUNCTIONS.put(ARGB, new ARGB()); + FUNCTIONS.put(HSL, new HSL()); + FUNCTIONS.put(HSLA, new HSLA()); + + FUNCTIONS.put(HUE, new Hue()); +// FUNCTIONS.put(SATURATION, new Saturation()); +// FUNCTIONS.put(LIGHTNESS, new Lightness()); +// FUNCTIONS.put(RED, new Red()); +// FUNCTIONS.put(GREEN, new Green()); +// FUNCTIONS.put(BLUE, new Blue()); +// FUNCTIONS.put(ALPHA, new Alpha()); +// FUNCTIONS.put(LUMA, new Luma()); + + FUNCTIONS.put(SATURATE, new Saturate()); + FUNCTIONS.put(DESATURATE, new Desaturate()); + FUNCTIONS.put(LIGHTEN, new Lighten()); + FUNCTIONS.put(DARKEN, new Darken()); + FUNCTIONS.put(FADEIN, new FadeIn()); + FUNCTIONS.put(FADEOUT, new FadeOut()); + FUNCTIONS.put(FADE, new Fade()); + FUNCTIONS.put(SPIN, new Spin()); } private final ProblemsHandler problemsHandler; @@ -107,31 +123,6 @@ public Expression evaluate(FunctionExpression input, Expression parameters) { } -class Lighten extends AbstractColorAmountFunction { - - @Override - protected Expression evaluate(ColorExpression colorExpression, NumberExpression amount, HiddenTokenAwareTree token) { - Color color = colorExpression.toColor(); - float[] hsl = HSLColor.fromRGB(color); - hsl[2] += amount.getValueAsDouble(); - hsl[2] = clamp(hsl[2]); - return hsla(hsl, color.getAlpha() / 255.0f, token); - } - -} - -class Darken extends AbstractColorAmountFunction { - - protected Expression evaluate(ColorExpression colorExpression, NumberExpression amount, HiddenTokenAwareTree token) { - Color color = colorExpression.toColor(); - float[] hsl = HSLColor.fromRGB(color); - hsl[2] -= amount.getValueAsDouble(); - hsl[2] = clamp(hsl[2]); - return hsla(hsl, color.getAlpha() / 255.0f, token); - } - -} - class RGB extends AbstractColorFunction { @Override @@ -275,6 +266,122 @@ protected boolean validateParameter(Expression parameter, int position, Problems } +class Hue extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + float[] hsl = HSLColor.fromRGB(color.toColor()); + return new NumberExpression(token, Double.valueOf(hsl[0]), "", "" + hsl[0], Dimension.NUMBER); + } + +} + +abstract class AbstractColorOperationFunction extends AbstractColorFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((ColorExpression)splitParameters.get(0), problemsHandler, token); + } + + protected abstract Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token); + + @Override + protected int getMinParameters() { + return 1; + } + + @Override + protected int getMaxParameters() { + return 1; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.COLOR_EXPRESSION, problemsHandler); + } + +} + +class Saturate extends AbstractColorHSLAmountFunction { + + @Override + protected void apply(NumberExpression amount, float[] hsla) { + hsla[1] += amount.getValueAsDouble() / 100.0f; + hsla[1] = clamp(hsla[1]); + } + +} + +class Desaturate extends AbstractColorHSLAmountFunction { + + @Override + protected void apply(NumberExpression amount, float[] hsla) { + hsla[1] -= amount.getValueAsDouble() / 100.0f; + hsla[1] = clamp(hsla[1]); + } + +} + +class Lighten extends AbstractColorHSLAmountFunction { + + @Override + protected void apply(NumberExpression amount, float[] hsla) { + hsla[2] += amount.getValueAsDouble() / 100.0f; + hsla[2] = clamp(hsla[2]); + } + +} + +class Darken extends AbstractColorHSLAmountFunction { + + @Override + protected void apply(NumberExpression amount, float[] hsla) { + hsla[2] -= amount.getValueAsDouble() / 100.0f; + hsla[2] = clamp(hsla[2]); + } + +} + +class FadeIn extends AbstractColorHSLAmountFunction { + + @Override + protected void apply(NumberExpression amount, float[] hsla) { + hsla[3] += amount.getValueAsDouble() / 100.0f; + hsla[3] = clamp(hsla[3]); + } + +} + +class FadeOut extends AbstractColorHSLAmountFunction { + + @Override + protected void apply(NumberExpression amount, float[] hsla) { + hsla[3] -= amount.getValueAsDouble() / 100.0f; + hsla[3] = clamp(hsla[3]); + } + +} + +class Fade extends AbstractColorHSLAmountFunction { + + @Override + protected void apply(NumberExpression amount, float[] hsla) { + hsla[3] = (float) (amount.getValueAsDouble() / 100.0f); + hsla[3] = clamp(hsla[3]); + } + +} + +class Spin extends AbstractColorHSLAmountFunction { + + @Override + protected void apply(NumberExpression amount, float[] hsla) { + float hue = (float) ((hsla[0] + amount.getValueAsDouble()) % 360); + hsla[0] = hue < 0 ? 360 + hue : hue; + } + +} + abstract class AbstractMultiParameterFunction implements Function { @Override @@ -332,7 +439,7 @@ protected boolean validateParameter(Expression parameter, ASTCssNodeType expecte abstract class AbstractColorFunction extends AbstractMultiParameterFunction { static float clamp(float val) { - return Math.min(100, Math.max(0, val)); + return Math.min(1, Math.max(0, val)); } static ColorExpression hsl(float[] hsl, HiddenTokenAwareTree token) { @@ -396,3 +503,34 @@ protected boolean validateParameter(Expression parameter, int position, Problems } } + +abstract class AbstractColorHSLAmountFunction extends AbstractColorAmountFunction { + + @Override + protected Expression evaluate(ColorExpression color, NumberExpression amount, HiddenTokenAwareTree token) { + Color c = color.toColor(); + float[] hsl = HSLColor.fromRGB(c); + float[] hsla = new float[4]; + + hsla[0] = hsl[0]; + hsla[1] = hsl[1] / 100.0f; + hsla[2] = hsl[2] / 100.0f; + hsla[3] = c.getAlpha() / 255.0f; + + apply(amount, hsla); + + hsl[0] = hsla[0]; + hsl[1] = hsla[1] * 100.0f; + hsl[2] = hsla[2] * 100.0f; + + return hsla(hsl, hsla[3], token); + } + + /** + * Apply the amount to the given hsla array. + * @param amount + * @param hsla + */ + protected abstract void apply(NumberExpression amount, float[] hsla); + +} From b4169f2deae50e23bb2ada966b274186e8ac21f1 Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Tue, 22 Jan 2013 23:00:21 +1300 Subject: [PATCH 07/29] Color functions: use HSL functions from less.js Still doesn't resolve hue not matching in functions-color.less -> .css. I have also tried another HSL->RGB->HSL converter and got the same result, so this example may not be correct. I will check to see how less.js handles it. --- .../less4j/core/ast/ColorExpression.java | 5 + .../compiler/expressions/ColorFunctions.java | 155 ++++--- .../github/sommeri/less4j/utils/HSLColor.java | 417 ------------------ 3 files changed, 106 insertions(+), 471 deletions(-) delete mode 100644 src/main/java/com/github/sommeri/less4j/utils/HSLColor.java diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java b/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java index 31fc26a8..6568054e 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java @@ -53,6 +53,10 @@ public int getBlue() { return blue; } + public float getAlpha() { + return 1.0f; + } + private int decode(String color, int i) { if (color.length()<7) { String substring = color.substring(i+1, i+2); @@ -120,6 +124,7 @@ public ColorWithAlphaExpression(HiddenTokenAwareTree token, Color color) { this(token, color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha() / 255.0f); } + @Override public float getAlpha() { return alpha; } diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java index f0733e5d..1ad4f892 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -1,6 +1,5 @@ package com.github.sommeri.less4j.core.compiler.expressions; -import java.awt.Color; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -16,7 +15,6 @@ import com.github.sommeri.less4j.core.ast.NumberExpression.Dimension; import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; import com.github.sommeri.less4j.core.problems.ProblemsHandler; -import com.github.sommeri.less4j.utils.HSLColor; public class ColorFunctions implements FunctionsPackage { @@ -189,9 +187,7 @@ protected Expression evaluate(List parameters, ProblemsHandler probl } private Expression evaluate(NumberExpression h, NumberExpression s, NumberExpression l, HiddenTokenAwareTree token) { - Color color = HSLColor.toRGB(h.getValueAsDouble().floatValue(), s.getValueAsDouble().floatValue(), l.getValueAsDouble().floatValue(), - 1.0f); - return new ColorExpression.ColorWithAlphaExpression(token, color); + return hsla(new HSLAValue((float)number(h), (float)number(s), (float)number(l)), token); } @Override @@ -220,9 +216,7 @@ protected Expression evaluate(List parameters, ProblemsHandler probl } private Expression evaluate(NumberExpression h, NumberExpression s, NumberExpression l, NumberExpression a, HiddenTokenAwareTree token) { - Color color = HSLColor.toRGB(h.getValueAsDouble().floatValue(), s.getValueAsDouble().floatValue(), l.getValueAsDouble().floatValue(), a - .getValueAsDouble().floatValue()); - return new ColorExpression.ColorWithAlphaExpression(token, color); + return hsla(new HSLAValue((float)number(h), (float)number(s), (float)number(l), (float)number(a)), token); } @Override @@ -270,8 +264,8 @@ class Hue extends AbstractColorOperationFunction { @Override protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { - float[] hsl = HSLColor.fromRGB(color.toColor()); - return new NumberExpression(token, Double.valueOf(hsl[0]), "", "" + hsl[0], Dimension.NUMBER); + HSLAValue hsla = toHSLA(color); + return new NumberExpression(token, Double.valueOf(hsla.h), "", "" + hsla.h, Dimension.NUMBER); } } @@ -305,9 +299,9 @@ protected boolean validateParameter(Expression parameter, int position, Problems class Saturate extends AbstractColorHSLAmountFunction { @Override - protected void apply(NumberExpression amount, float[] hsla) { - hsla[1] += amount.getValueAsDouble() / 100.0f; - hsla[1] = clamp(hsla[1]); + protected void apply(NumberExpression amount, HSLAValue hsla) { + hsla.s += amount.getValueAsDouble() / 100.0f; + hsla.s = clamp(hsla.s); } } @@ -315,9 +309,9 @@ protected void apply(NumberExpression amount, float[] hsla) { class Desaturate extends AbstractColorHSLAmountFunction { @Override - protected void apply(NumberExpression amount, float[] hsla) { - hsla[1] -= amount.getValueAsDouble() / 100.0f; - hsla[1] = clamp(hsla[1]); + protected void apply(NumberExpression amount, HSLAValue hsla) { + hsla.s -= amount.getValueAsDouble() / 100.0f; + hsla.s = clamp(hsla.s); } } @@ -325,9 +319,9 @@ protected void apply(NumberExpression amount, float[] hsla) { class Lighten extends AbstractColorHSLAmountFunction { @Override - protected void apply(NumberExpression amount, float[] hsla) { - hsla[2] += amount.getValueAsDouble() / 100.0f; - hsla[2] = clamp(hsla[2]); + protected void apply(NumberExpression amount, HSLAValue hsla) { + hsla.l += amount.getValueAsDouble() / 100.0f; + hsla.l = clamp(hsla.l); } } @@ -335,9 +329,9 @@ protected void apply(NumberExpression amount, float[] hsla) { class Darken extends AbstractColorHSLAmountFunction { @Override - protected void apply(NumberExpression amount, float[] hsla) { - hsla[2] -= amount.getValueAsDouble() / 100.0f; - hsla[2] = clamp(hsla[2]); + protected void apply(NumberExpression amount, HSLAValue hsla) { + hsla.l -= amount.getValueAsDouble() / 100.0f; + hsla.l = clamp(hsla.l); } } @@ -345,9 +339,9 @@ protected void apply(NumberExpression amount, float[] hsla) { class FadeIn extends AbstractColorHSLAmountFunction { @Override - protected void apply(NumberExpression amount, float[] hsla) { - hsla[3] += amount.getValueAsDouble() / 100.0f; - hsla[3] = clamp(hsla[3]); + protected void apply(NumberExpression amount, HSLAValue hsla) { + hsla.a += amount.getValueAsDouble() / 100.0f; + hsla.a = clamp(hsla.a); } } @@ -355,9 +349,9 @@ protected void apply(NumberExpression amount, float[] hsla) { class FadeOut extends AbstractColorHSLAmountFunction { @Override - protected void apply(NumberExpression amount, float[] hsla) { - hsla[3] -= amount.getValueAsDouble() / 100.0f; - hsla[3] = clamp(hsla[3]); + protected void apply(NumberExpression amount, HSLAValue hsla) { + hsla.a -= amount.getValueAsDouble() / 100.0f; + hsla.a = clamp(hsla.a); } } @@ -365,9 +359,9 @@ protected void apply(NumberExpression amount, float[] hsla) { class Fade extends AbstractColorHSLAmountFunction { @Override - protected void apply(NumberExpression amount, float[] hsla) { - hsla[3] = (float) (amount.getValueAsDouble() / 100.0f); - hsla[3] = clamp(hsla[3]); + protected void apply(NumberExpression amount, HSLAValue hsla) { + hsla.a = (float) (amount.getValueAsDouble() / 100.0f); + hsla.a = clamp(hsla.a); } } @@ -375,9 +369,9 @@ protected void apply(NumberExpression amount, float[] hsla) { class Spin extends AbstractColorHSLAmountFunction { @Override - protected void apply(NumberExpression amount, float[] hsla) { - float hue = (float) ((hsla[0] + amount.getValueAsDouble()) % 360); - hsla[0] = hue < 0 ? 360 + hue : hue; + protected void apply(NumberExpression amount, HSLAValue hsla) { + float hue = (float) ((hsla.h + amount.getValueAsDouble()) % 360); + hsla.h = hue < 0 ? 360 + hue : hue; } } @@ -442,13 +436,24 @@ static float clamp(float val) { return Math.min(1, Math.max(0, val)); } - static ColorExpression hsl(float[] hsl, HiddenTokenAwareTree token) { - return hsla(hsl, 1.0f, token); - } + static ColorExpression hsla(HSLAValue hsla, HiddenTokenAwareTree token) { + double h = (hsla.h % 360) / 360, s = hsla.s, l = hsla.l, a = hsla.a; + + double m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; + double m1 = l * 2 - m2; - static ColorExpression hsla(float[] hsl, float a, HiddenTokenAwareTree token) { - Color color = HSLColor.toRGB(hsl, a); - return new ColorExpression.ColorWithAlphaExpression(token, color); + return new ColorExpression.ColorWithAlphaExpression(token, (int)Math.round(hue(h + 1.0/3.0, m1, m2) * 255), + (int)Math.round(hue(h, m1, m2) * 255), + (int)Math.round(hue(h - 1.0/3.0, m1, m2) * 255), + (float) a); + } + + static double hue(double h, double m1, double m2) { + h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h); + if (h * 6 < 1) return m1 + (m2 - m1) * h * 6; + else if (h * 2 < 1) return m2; + else if (h * 3 < 2) return m1 + (m2 - m1) * (2.0/3.0 - h) * 6; + else return m1; } static double scaled(NumberExpression n, int size) { @@ -466,7 +471,60 @@ static double number(NumberExpression n) { return n.getValueAsDouble(); } } + + static HSLAValue toHSLA(ColorExpression color) { + double r = color.getRed() / 255.0f, + g = color.getGreen() / 255.0f, + b = color.getBlue() / 255.0f, + a = color.getAlpha(); + + double max = Math.max(r, Math.max(g, b)), + min = Math.min(r, Math.min(g, b)); + double h, s, l = (max + min) / 2, d = max - min; + + if (max == min) { + h = s = 0; + } else { + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + + if (max == r) { + h = (g - b) / d + (g < b ? 6 : 0); + } else if (max == g) { + h = (b - r) / d + 2; + } else { + h = (r - g) / d + 4; + } + + h /= 6; + } + + return new HSLAValue((float) (h * 360), (float)s, (float)l, (float)a); + } + +} + +class HSLAValue { + public float h, s, l, a; + + public HSLAValue() { + super(); + } + + public HSLAValue(float h, float s, float l, float a) { + super(); + this.h = h; + this.s = s; + this.l = l; + this.a = a; + } + public HSLAValue(float h, float s, float l) { + super(); + this.h = h; + this.s = s; + this.l = l; + this.a = 1.0f; + } } abstract class AbstractColorAmountFunction extends AbstractColorFunction { @@ -508,22 +566,11 @@ abstract class AbstractColorHSLAmountFunction extends AbstractColorAmountFunctio @Override protected Expression evaluate(ColorExpression color, NumberExpression amount, HiddenTokenAwareTree token) { - Color c = color.toColor(); - float[] hsl = HSLColor.fromRGB(c); - float[] hsla = new float[4]; - - hsla[0] = hsl[0]; - hsla[1] = hsl[1] / 100.0f; - hsla[2] = hsl[2] / 100.0f; - hsla[3] = c.getAlpha() / 255.0f; + HSLAValue hsla = toHSLA(color); apply(amount, hsla); - hsl[0] = hsla[0]; - hsl[1] = hsla[1] * 100.0f; - hsl[2] = hsla[2] * 100.0f; - - return hsla(hsl, hsla[3], token); + return hsla(hsla, token); } /** @@ -531,6 +578,6 @@ protected Expression evaluate(ColorExpression color, NumberExpression amount, Hi * @param amount * @param hsla */ - protected abstract void apply(NumberExpression amount, float[] hsla); + protected abstract void apply(NumberExpression amount, HSLAValue hsla); } diff --git a/src/main/java/com/github/sommeri/less4j/utils/HSLColor.java b/src/main/java/com/github/sommeri/less4j/utils/HSLColor.java deleted file mode 100644 index b77e65fb..00000000 --- a/src/main/java/com/github/sommeri/less4j/utils/HSLColor.java +++ /dev/null @@ -1,417 +0,0 @@ -package com.github.sommeri.less4j.utils; - -import java.awt.Color; - -/** - * The HSLColor class provides methods to manipulate HSL (Hue, Saturation - * Luminance) values to create a corresponding Color object using the RGB - * ColorSpace. - * - * The HUE is the color, the Saturation is the purity of the color (with respect - * to grey) and Luminance is the brightness of the color (with respect to black - * and white) - * - * The Hue is specified as an angel between 0 - 360 degrees where red is 0, - * green is 120 and blue is 240. In between you have the colors of the rainbow. - * Saturation is specified as a percentage between 0 - 100 where 100 is fully - * saturated and 0 approaches gray. Luminance is specified as a percentage - * between 0 - 100 where 0 is black and 100 is white. - * - * In particular the HSL color space makes it easier change the Tone or Shade of - * a color by adjusting the luminance value. - * - * [Source http://tips4java.wordpress.com/2009/07/05/hsl-color/] - */ -public class HSLColor { - private Color rgb; - private float[] hsl; - private float alpha; - - /** - * Create a HSLColor object using an RGB Color object. - * - * @param rgb - * the RGB Color object - */ - public HSLColor(Color rgb) { - this.rgb = rgb; - hsl = fromRGB(rgb); - alpha = rgb.getAlpha() / 255.0f; - } - - /** - * Create a HSLColor object using individual HSL values and a default alpha - * value of 1.0. - * - * @param h - * is the Hue value in degrees between 0 - 360 - * @param s - * is the Saturation percentage between 0 - 100 - * @param l - * is the Lumanance percentage between 0 - 100 - */ - public HSLColor(float h, float s, float l) { - this(h, s, l, 1.0f); - } - - /** - * Create a HSLColor object using individual HSL values. - * - * @param h - * the Hue value in degrees between 0 - 360 - * @param s - * the Saturation percentage between 0 - 100 - * @param l - * the Lumanance percentage between 0 - 100 - * @param alpha - * the alpha value between 0 - 1 - */ - public HSLColor(float h, float s, float l, float alpha) { - hsl = new float[] { h, s, l }; - this.alpha = alpha; - rgb = toRGB(hsl, alpha); - } - - /** - * Create a HSLColor object using an an array containing the individual HSL - * values and with a default alpha value of 1. - * - * @param hsl - * array containing HSL values - */ - public HSLColor(float[] hsl) { - this(hsl, 1.0f); - } - - /** - * Create a HSLColor object using an an array containing the individual HSL - * values. - * - * @param hsl - * array containing HSL values - * @param alpha - * the alpha value between 0 - 1 - */ - public HSLColor(float[] hsl, float alpha) { - this.hsl = hsl; - this.alpha = alpha; - rgb = toRGB(hsl, alpha); - } - - /** - * Create a RGB Color object based on this HSLColor with a different Hue - * value. The degrees specified is an absolute value. - * - * @param degrees - * - the Hue value between 0 - 360 - * @return the RGB Color object - */ - public Color adjustHue(float degrees) { - return toRGB(degrees, hsl[1], hsl[2], alpha); - } - - /** - * Create a RGB Color object based on this HSLColor with a different Luminance - * value. The percent specified is an absolute value. - * - * @param percent - * - the Luminance value between 0 - 100 - * @return the RGB Color object - */ - public Color adjustLuminance(float percent) { - return toRGB(hsl[0], hsl[1], percent, alpha); - } - - /** - * Create a RGB Color object based on this HSLColor with a different - * Saturation value. The percent specified is an absolute value. - * - * @param percent - * - the Saturation value between 0 - 100 - * @return the RGB Color object - */ - public Color adjustSaturation(float percent) { - return toRGB(hsl[0], percent, hsl[2], alpha); - } - - /** - * Create a RGB Color object based on this HSLColor with a different Shade. - * Changing the shade will return a darker color. The percent specified is a - * relative value. - * - * @param percent - * - the value between 0 - 100 - * @return the RGB Color object - */ - public Color adjustShade(float percent) { - float multiplier = (100.0f - percent) / 100.0f; - float l = Math.max(0.0f, hsl[2] * multiplier); - - return toRGB(hsl[0], hsl[1], l, alpha); - } - - /** - * Create a RGB Color object based on this HSLColor with a different Tone. - * Changing the tone will return a lighter color. The percent specified is a - * relative value. - * - * @param percent - * - the value between 0 - 100 - * @return the RGB Color object - */ - public Color adjustTone(float percent) { - float multiplier = (100.0f + percent) / 100.0f; - float l = Math.min(100.0f, hsl[2] * multiplier); - - return toRGB(hsl[0], hsl[1], l, alpha); - } - - /** - * Get the Alpha value. - * - * @return the Alpha value. - */ - public float getAlpha() { - return alpha; - } - - /** - * Create a RGB Color object that is the complementary color of this HSLColor. - * This is a convenience method. The complementary color is determined by - * adding 180 degrees to the Hue value. - * - * @return the RGB Color object - */ - public Color getComplementary() { - float hue = (hsl[0] + 180.0f) % 360.0f; - return toRGB(hue, hsl[1], hsl[2]); - } - - /** - * Get the Hue value. - * - * @return the Hue value. - */ - public float getHue() { - return hsl[0]; - } - - /** - * Get the HSL values. - * - * @return the HSL values. - */ - public float[] getHSL() { - return hsl; - } - - /** - * Get the Luminance value. - * - * @return the Luminance value. - */ - public float getLuminance() { - return hsl[2]; - } - - /** - * Get the RGB Color object represented by this HDLColor. - * - * @return the RGB Color object. - */ - public Color getRGB() { - return rgb; - } - - /** - * Get the Saturation value. - * - * @return the Saturation value. - */ - public float getSaturation() { - return hsl[1]; - } - - public String toString() { - String toString = "HSLColor[h=" + hsl[0] + ",s=" + hsl[1] + ",l=" + hsl[2] - + ",alpha=" + alpha + "]"; - - return toString; - } - - /** - * Convert a RGB Color to it corresponding HSL values. - * - * @return an array containing the 3 HSL values. - */ - public static float[] fromRGB(Color color) { - // Get RGB values in the range 0 - 1 - - float[] rgb = color.getRGBColorComponents(null); - float r = rgb[0]; - float g = rgb[1]; - float b = rgb[2]; - - // Minimum and Maximum RGB values are used in the HSL calculations - - float min = Math.min(r, Math.min(g, b)); - float max = Math.max(r, Math.max(g, b)); - - // Calculate the Hue - - float h = 0; - - if (max == min) - h = 0; - else if (max == r) - h = ((60 * (g - b) / (max - min)) + 360) % 360; - else if (max == g) - h = (60 * (b - r) / (max - min)) + 120; - else if (max == b) - h = (60 * (r - g) / (max - min)) + 240; - - // Calculate the Luminance - - float l = (max + min) / 2; - System.out.println(max + " : " + min + " : " + l); - - // Calculate the Saturation - - float s = 0; - - if (max == min) - s = 0; - else if (l <= .5f) - s = (max - min) / (max + min); - else - s = (max - min) / (2 - max - min); - - return new float[] { h, s * 100, l * 100 }; - } - - /** - * Convert HSL values to a RGB Color with a default alpha value of 1. H (Hue) - * is specified as degrees in the range 0 - 360. S (Saturation) is specified - * as a percentage in the range 1 - 100. L (Lumanance) is specified as a - * percentage in the range 1 - 100. - * - * @param hsl - * an array containing the 3 HSL values - * - * @returns the RGB Color object - */ - public static Color toRGB(float[] hsl) { - return toRGB(hsl, 1.0f); - } - - /** - * Convert HSL values to a RGB Color. H (Hue) is specified as degrees in the - * range 0 - 360. S (Saturation) is specified as a percentage in the range 1 - - * 100. L (Lumanance) is specified as a percentage in the range 1 - 100. - * - * @param hsl - * an array containing the 3 HSL values - * @param alpha - * the alpha value between 0 - 1 - * - * @returns the RGB Color object - */ - public static Color toRGB(float[] hsl, float alpha) { - return toRGB(hsl[0], hsl[1], hsl[2], alpha); - } - - /** - * Convert HSL values to a RGB Color with a default alpha value of 1. - * - * @param h - * Hue is specified as degrees in the range 0 - 360. - * @param s - * Saturation is specified as a percentage in the range 1 - 100. - * @param l - * Lumanance is specified as a percentage in the range 1 - 100. - * - * @returns the RGB Color object - */ - public static Color toRGB(float h, float s, float l) { - return toRGB(h, s, l, 1.0f); - } - - /** - * Convert HSL values to a RGB Color. - * - * @param h - * Hue is specified as degrees in the range 0 - 360. - * @param s - * Saturation is specified as a percentage in the range 1 - 100. - * @param l - * Lumanance is specified as a percentage in the range 1 - 100. - * @param alpha - * the alpha value between 0 - 1 - * - * @returns the RGB Color object - */ - public static Color toRGB(float h, float s, float l, float alpha) { - if (s < 0.0f || s > 100.0f) { - String message = "Color parameter outside of expected range - Saturation"; - throw new IllegalArgumentException(message); - } - - if (l < 0.0f || l > 100.0f) { - String message = "Color parameter outside of expected range - Luminance"; - throw new IllegalArgumentException(message); - } - - if (alpha < 0.0f || alpha > 1.0f) { - String message = "Color parameter outside of expected range - Alpha"; - throw new IllegalArgumentException(message); - } - - // Formula needs all values between 0 - 1. - - h = h % 360.0f; - h /= 360f; - s /= 100f; - l /= 100f; - - float q = 0; - - if (l < 0.5) - q = l * (1 + s); - else - q = (l + s) - (s * l); - - float p = 2 * l - q; - - float r = Math.max(0, HueToRGB(p, q, h + (1.0f / 3.0f))); - float g = Math.max(0, HueToRGB(p, q, h)); - float b = Math.max(0, HueToRGB(p, q, h - (1.0f / 3.0f))); - - r = Math.min(r, 1.0f); - g = Math.min(g, 1.0f); - b = Math.min(b, 1.0f); - - return new Color(r, g, b, alpha); - } - - private static float HueToRGB(float p, float q, float h) { - if (h < 0) - h += 1; - - if (h > 1) - h -= 1; - - if (6 * h < 1) { - return p + ((q - p) * 6 * h); - } - - if (2 * h < 1) { - return q; - } - - if (3 * h < 2) { - return p + ((q - p) * 6 * ((2.0f / 3.0f) - h)); - } - - return p; - } -} From 9ac66e96b210a0a4c344c7a83f1c94dc5aba1f7e Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Tue, 22 Jan 2013 23:32:21 +1300 Subject: [PATCH 08/29] Color functions: implement saturation, lightness, red, green, blue, alpha, luma, mix, greyscale --- .../compiler/expressions/ColorFunctions.java | 235 ++++++++++++++---- .../functions/functions-color.css | 12 + .../functions/functions-color.less | 12 + 3 files changed, 215 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java index 1ad4f892..b66f3fd5 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -64,15 +64,17 @@ public class ColorFunctions implements FunctionsPackage { FUNCTIONS.put(ARGB, new ARGB()); FUNCTIONS.put(HSL, new HSL()); FUNCTIONS.put(HSLA, new HSLA()); - + // FUNCTIONS.put(HSV, new HSV()); + // FUNCTIONS.put(HSVA, new HSVA()); + FUNCTIONS.put(HUE, new Hue()); -// FUNCTIONS.put(SATURATION, new Saturation()); -// FUNCTIONS.put(LIGHTNESS, new Lightness()); -// FUNCTIONS.put(RED, new Red()); -// FUNCTIONS.put(GREEN, new Green()); -// FUNCTIONS.put(BLUE, new Blue()); -// FUNCTIONS.put(ALPHA, new Alpha()); -// FUNCTIONS.put(LUMA, new Luma()); + FUNCTIONS.put(SATURATION, new Saturation()); + FUNCTIONS.put(LIGHTNESS, new Lightness()); + FUNCTIONS.put(RED, new Red()); + FUNCTIONS.put(GREEN, new Green()); + FUNCTIONS.put(BLUE, new Blue()); + FUNCTIONS.put(ALPHA, new Alpha()); + FUNCTIONS.put(LUMA, new Luma()); FUNCTIONS.put(SATURATE, new Saturate()); FUNCTIONS.put(DESATURATE, new Desaturate()); @@ -82,6 +84,19 @@ public class ColorFunctions implements FunctionsPackage { FUNCTIONS.put(FADEOUT, new FadeOut()); FUNCTIONS.put(FADE, new Fade()); FUNCTIONS.put(SPIN, new Spin()); + FUNCTIONS.put(MIX, new Mix()); + FUNCTIONS.put(GREYSCALE, new Greyscale()); +// FUNCTIONS.put(CONTRAST, new Contrast()); + +// FUNCTIONS.put(MULTIPLY, new Multiply()); +// FUNCTIONS.put(SCREEN, new Screen()); +// FUNCTIONS.put(OVERLAY, new Overlay()); +// FUNCTIONS.put(SOFTLIGHT, new Softlight()); +// FUNCTIONS.put(HARDLIGHT, new Hardlight()); +// FUNCTIONS.put(DIFFERENCE, new Difference()); +// FUNCTIONS.put(EXCLUSION, new Exclusion()); +// FUNCTIONS.put(AVERAGE, new Average()); +// FUNCTIONS.put(NEGATION, new Negation()); } private final ProblemsHandler problemsHandler; @@ -187,7 +202,7 @@ protected Expression evaluate(List parameters, ProblemsHandler probl } private Expression evaluate(NumberExpression h, NumberExpression s, NumberExpression l, HiddenTokenAwareTree token) { - return hsla(new HSLAValue((float)number(h), (float)number(s), (float)number(l)), token); + return hsla(new HSLAValue((float) number(h), (float) number(s), (float) number(l)), token); } @Override @@ -216,7 +231,7 @@ protected Expression evaluate(List parameters, ProblemsHandler probl } private Expression evaluate(NumberExpression h, NumberExpression s, NumberExpression l, NumberExpression a, HiddenTokenAwareTree token) { - return hsla(new HSLAValue((float)number(h), (float)number(s), (float)number(l), (float)number(a)), token); + return hsla(new HSLAValue((float) number(h), (float) number(s), (float) number(l), (float) number(a)), token); } @Override @@ -240,7 +255,7 @@ class ARGB extends AbstractColorFunction { @Override protected Expression evaluate(List parameters, ProblemsHandler problemHandler, HiddenTokenAwareTree token) { - return new AnonymousExpression(token, ((ColorExpression)parameters.get(0)).toARGB()); + return new AnonymousExpression(token, ((ColorExpression) parameters.get(0)).toARGB()); } @Override @@ -265,7 +280,76 @@ class Hue extends AbstractColorOperationFunction { @Override protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { HSLAValue hsla = toHSLA(color); - return new NumberExpression(token, Double.valueOf(hsla.h), "", "" + hsla.h, Dimension.NUMBER); + return new NumberExpression(token, Double.valueOf(hsla.h), "", "" + (float) hsla.h, Dimension.NUMBER); + } + +} + +class Saturation extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + HSLAValue hsla = toHSLA(color); + return new NumberExpression(token, Double.valueOf(hsla.s * 100), "%", "" + ((float) hsla.s * 100) + "%", Dimension.PERCENTAGE); + } + +} + +class Lightness extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + HSLAValue hsla = toHSLA(color); + return new NumberExpression(token, Double.valueOf(hsla.l * 100), "%", "" + ((float) hsla.l * 100) + "%", Dimension.PERCENTAGE); + } + +} + +class Red extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return new NumberExpression(token, Double.valueOf(color.getRed()), "", "" + color.getRed(), Dimension.NUMBER); + } + +} + +class Green extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return new NumberExpression(token, Double.valueOf(color.getGreen()), "", "" + color.getGreen(), Dimension.NUMBER); + } + +} + +class Blue extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return new NumberExpression(token, Double.valueOf(color.getBlue()), "", "" + color.getBlue(), Dimension.NUMBER); + } + +} + +class Alpha extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return new NumberExpression(token, Double.valueOf(color.getAlpha()), "", "" + color.getAlpha(), Dimension.NUMBER); + } + +} + +class Luma extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + double luma = (Math + .round((0.2126 * (color.getRed() / 255.0) + 0.7152 * (color.getGreen() / 255.0) + 0.0722 * (color.getBlue() / 255.0)) + * color.getAlpha() * 100)); + + return new NumberExpression(token, Double.valueOf(luma), "%", "" + (float) luma + "%", Dimension.PERCENTAGE); } } @@ -274,7 +358,7 @@ abstract class AbstractColorOperationFunction extends AbstractColorFunction { @Override protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { - return evaluate((ColorExpression)splitParameters.get(0), problemsHandler, token); + return evaluate((ColorExpression) splitParameters.get(0), problemsHandler, token); } protected abstract Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token); @@ -293,7 +377,7 @@ protected int getMaxParameters() { protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { return validateParameter(parameter, ASTCssNodeType.COLOR_EXPRESSION, problemsHandler); } - + } class Saturate extends AbstractColorHSLAmountFunction { @@ -303,7 +387,7 @@ protected void apply(NumberExpression amount, HSLAValue hsla) { hsla.s += amount.getValueAsDouble() / 100.0f; hsla.s = clamp(hsla.s); } - + } class Desaturate extends AbstractColorHSLAmountFunction { @@ -313,7 +397,7 @@ protected void apply(NumberExpression amount, HSLAValue hsla) { hsla.s -= amount.getValueAsDouble() / 100.0f; hsla.s = clamp(hsla.s); } - + } class Lighten extends AbstractColorHSLAmountFunction { @@ -376,6 +460,70 @@ protected void apply(NumberExpression amount, HSLAValue hsla) { } +// +// Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein +// http://sass-lang.com +// +class Mix extends AbstractColorFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + ColorExpression color1 = (ColorExpression) splitParameters.get(0); + ColorExpression color2 = (ColorExpression) splitParameters.get(1); + NumberExpression weight = splitParameters.size() > 2 ? (NumberExpression) splitParameters.get(2) : null; + + if (weight == null) { + weight = new NumberExpression(token, Double.valueOf(50), "%", "50%", Dimension.PERCENTAGE); + } + + double p = weight.getValueAsDouble() / 100.0; + double w = p * 2 - 1; + double a = color1.getAlpha() - color2.getAlpha(); + + double w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + double w2 = 1 - w1; + + return new ColorExpression.ColorWithAlphaExpression(token, (int)Math.round(color1.getRed() * w1 + color2.getRed() * w2), + (int)Math.round(color1.getGreen() * w1 + color2.getGreen() * w2), + (int)Math.round(color1.getBlue() * w1 + color2.getBlue() * w2), + (float) (color1.getAlpha() * p + color2.getAlpha() * (1 - p))); + } + + @Override + protected int getMinParameters() { + return 2; + } + + @Override + protected int getMaxParameters() { + return 3; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + switch (position) { + case 0: + case 1: + return validateParameter(parameter, ASTCssNodeType.COLOR_EXPRESSION, problemsHandler); + case 2: + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + return false; + } + +} + +class Greyscale extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + HSLAValue hsla = toHSLA(color); + hsla.s = 0; + return hsla(hsla, token); + } + +} + abstract class AbstractMultiParameterFunction implements Function { @Override @@ -432,7 +580,7 @@ protected boolean validateParameter(Expression parameter, ASTCssNodeType expecte abstract class AbstractColorFunction extends AbstractMultiParameterFunction { - static float clamp(float val) { + static double clamp(double val) { return Math.min(1, Math.max(0, val)); } @@ -442,18 +590,20 @@ static ColorExpression hsla(HSLAValue hsla, HiddenTokenAwareTree token) { double m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; double m1 = l * 2 - m2; - return new ColorExpression.ColorWithAlphaExpression(token, (int)Math.round(hue(h + 1.0/3.0, m1, m2) * 255), - (int)Math.round(hue(h, m1, m2) * 255), - (int)Math.round(hue(h - 1.0/3.0, m1, m2) * 255), - (float) a); + return new ColorExpression.ColorWithAlphaExpression(token, (int) Math.round(hue(h + 1.0 / 3.0, m1, m2) * 255), (int) Math.round(hue(h, + m1, m2) * 255), (int) Math.round(hue(h - 1.0 / 3.0, m1, m2) * 255), (float) a); } - + static double hue(double h, double m1, double m2) { h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h); - if (h * 6 < 1) return m1 + (m2 - m1) * h * 6; - else if (h * 2 < 1) return m2; - else if (h * 3 < 2) return m1 + (m2 - m1) * (2.0/3.0 - h) * 6; - else return m1; + if (h * 6 < 1) + return m1 + (m2 - m1) * h * 6; + else if (h * 2 < 1) + return m2; + else if (h * 3 < 2) + return m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6; + else + return m1; } static double scaled(NumberExpression n, int size) { @@ -471,17 +621,13 @@ static double number(NumberExpression n) { return n.getValueAsDouble(); } } - + static HSLAValue toHSLA(ColorExpression color) { - double r = color.getRed() / 255.0f, - g = color.getGreen() / 255.0f, - b = color.getBlue() / 255.0f, - a = color.getAlpha(); - - double max = Math.max(r, Math.max(g, b)), - min = Math.min(r, Math.min(g, b)); + double r = color.getRed() / 255.0, g = color.getGreen() / 255.0, b = color.getBlue() / 255.0, a = color.getAlpha(); + + double max = Math.max(r, Math.max(g, b)), min = Math.min(r, Math.min(g, b)); double h, s, l = (max + min) / 2, d = max - min; - + if (max == min) { h = s = 0; } else { @@ -494,23 +640,23 @@ static HSLAValue toHSLA(ColorExpression color) { } else { h = (r - g) / d + 4; } - + h /= 6; } - - return new HSLAValue((float) (h * 360), (float)s, (float)l, (float)a); + + return new HSLAValue((h * 360), s, l, a); } } class HSLAValue { - public float h, s, l, a; + public double h, s, l, a; public HSLAValue() { super(); } - public HSLAValue(float h, float s, float l, float a) { + public HSLAValue(double h, double s, double l, double a) { super(); this.h = h; this.s = s; @@ -518,7 +664,7 @@ public HSLAValue(float h, float s, float l, float a) { this.a = a; } - public HSLAValue(float h, float s, float l) { + public HSLAValue(double h, double s, double l) { super(); this.h = h; this.s = s; @@ -567,17 +713,18 @@ abstract class AbstractColorHSLAmountFunction extends AbstractColorAmountFunctio @Override protected Expression evaluate(ColorExpression color, NumberExpression amount, HiddenTokenAwareTree token) { HSLAValue hsla = toHSLA(color); - + apply(amount, hsla); - + return hsla(hsla, token); } /** * Apply the amount to the given hsla array. + * * @param amount * @param hsla */ protected abstract void apply(NumberExpression amount, HSLAValue hsla); - + } diff --git a/src/test/resources/compile-basic-features/functions/functions-color.css b/src/test/resources/compile-basic-features/functions/functions-color.css index a0f9bd03..80712df9 100644 --- a/src/test/resources/compile-basic-features/functions/functions-color.css +++ b/src/test/resources/compile-basic-features/functions/functions-color.css @@ -22,6 +22,18 @@ hue: 98; saturation: 12%; lightness: 95%; + red: 17; + green: 34; + blue: 51; + alpha: 0.5; + luma: 65%; + mix: #800080; + mix2: rgba(75, 25, 0, 0.75); + contrast: #000000; + contrast2: #ffffff; + contrast3: #dddddd; + contrast4: #000000; + contrast5: #ffffff; rounded: 11; roundedpx: 3px; percentage: 20%; diff --git a/src/test/resources/compile-basic-features/functions/functions-color.less b/src/test/resources/compile-basic-features/functions/functions-color.less index c111f7e5..54b8259f 100644 --- a/src/test/resources/compile-basic-features/functions/functions-color.less +++ b/src/test/resources/compile-basic-features/functions/functions-color.less @@ -26,6 +26,18 @@ hue: hue(hsl(98, 12%, 95%)); saturation: saturation(hsl(98, 12%, 95%)); lightness: lightness(hsl(98, 12%, 95%)); + red: red(#112233); + green: green(#112233); + blue: blue(#112233); + alpha: alpha(rgba(10, 20, 30, 0.5)); + luma: luma(rgb(100, 200, 30)); + mix: mix(#ff0000, #0000ff, 50%); + mix2: mix(rgba(100,0,0,1.0), rgba(0,100,0,0.5), 50%); + contrast: contrast(#aaaaaa); + contrast2: contrast(#222222, #101010); + contrast3: contrast(#222222, #101010, #dddddd); + contrast4: contrast(hsl(90, 100%, 50%),#000000,#ffffff,40%); + contrast5: contrast(hsl(90, 100%, 50%),#000000,#ffffff,60%); rounded: round(@r/3); roundedpx: round(10px / 3); percentage: percentage(10px / 50); From 274c6502cd0a8bc4937af1dc8efa3c5d221466db Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 10:26:22 +1300 Subject: [PATCH 09/29] Misc functions: added color and unit functions --- .../less4j/core/ast/NumberExpression.java | 21 +++ .../AbstractMultiParameterFunction.java | 64 +++++++++ .../compiler/expressions/ColorFunctions.java | 56 -------- .../expressions/ExpressionEvaluator.java | 1 + .../compiler/expressions/MiscFunctions.java | 123 ++++++++++++++++++ .../less4j/core/problems/ProblemsHandler.java | 4 +- .../functions/functions-color.css | 3 + .../functions/functions-color.less | 3 + 8 files changed, 217 insertions(+), 58 deletions(-) create mode 100644 src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java create mode 100644 src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/NumberExpression.java b/src/main/java/com/github/sommeri/less4j/core/ast/NumberExpression.java index 08b5d13f..50fb388c 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/NumberExpression.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/NumberExpression.java @@ -97,6 +97,27 @@ public ASTCssNodeType getType() { public enum Dimension { NUMBER, PERCENTAGE, LENGTH, EMS, EXS, ANGLE, TIME, FREQ, REPEATER, UNKNOWN; + + public static Dimension forSuffix(String suffix) { + if (suffix.equals("%")) { + return PERCENTAGE; + } else if (suffix.equals("px") || suffix.equals("cm") || suffix.equals("mm") || suffix.equals("in") || + suffix.equals("pt") || suffix.equals("pc")) { + return LENGTH; + } else if (suffix.equals("em")) { + return EMS; + } else if (suffix.equals("ex")) { + return EXS; + } else if (suffix.equals("deg") || suffix.equals("rad") || suffix.equals("grad")) { + return ANGLE; + } else if (suffix.equals("ms") || suffix.equals("s")) { + return TIME; + } else if (suffix.equals("khz") || suffix.equals("hz")) { + return FREQ; + } else { + return UNKNOWN; + } + } } @Override diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java new file mode 100644 index 00000000..794bc0ee --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java @@ -0,0 +1,64 @@ +package com.github.sommeri.less4j.core.compiler.expressions; + +import java.util.Collections; +import java.util.List; + +import com.github.sommeri.less4j.core.ast.ASTCssNodeType; +import com.github.sommeri.less4j.core.ast.ComposedExpression; +import com.github.sommeri.less4j.core.ast.Expression; +import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; +import com.github.sommeri.less4j.core.problems.ProblemsHandler; + +abstract class AbstractMultiParameterFunction implements Function { + + @Override + public Expression evaluate(Expression parameters, ProblemsHandler problemsHandler) { + List splitParameters; + + if (getMinParameters() == 1 && parameters.getType() != ASTCssNodeType.COMPOSED_EXPRESSION) { + splitParameters = Collections.singletonList(parameters); + } else if (parameters.getType() == ASTCssNodeType.COMPOSED_EXPRESSION) { + splitParameters = ((ComposedExpression) parameters).splitByComma(); + } else { + problemsHandler.wrongNumberOfArgumentsToFunction(parameters, getMinParameters()); + return parameters; + } + + if (splitParameters.size() >= getMinParameters() && splitParameters.size() <= getMaxParameters()) { + /* Validate */ + boolean valid = true; + for (int i = 0; i < splitParameters.size(); i++) { + if (!validateParameter(splitParameters.get(i), i, problemsHandler)) { + valid = false; + } + } + + if (valid) { + return evaluate(splitParameters, problemsHandler, parameters.getUnderlyingStructure()); + } else { + return parameters; + } + } else { + problemsHandler.wrongNumberOfArgumentsToFunction(parameters, getMinParameters()); + return parameters; + } + } + + protected abstract Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token); + + protected abstract int getMinParameters(); + + protected abstract int getMaxParameters(); + + protected abstract boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler); + + protected boolean validateParameter(Expression parameter, ASTCssNodeType expected, ProblemsHandler problemsHandler) { + if (parameter.getType() != expected) { + problemsHandler.wrongArgumentTypeToFunction(parameter, expected, parameter.getType()); + return false; + } else { + return true; + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java index b66f3fd5..c8bd2169 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -1,6 +1,5 @@ package com.github.sommeri.less4j.core.compiler.expressions; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -8,7 +7,6 @@ import com.github.sommeri.less4j.core.ast.ASTCssNodeType; import com.github.sommeri.less4j.core.ast.AnonymousExpression; import com.github.sommeri.less4j.core.ast.ColorExpression; -import com.github.sommeri.less4j.core.ast.ComposedExpression; import com.github.sommeri.less4j.core.ast.Expression; import com.github.sommeri.less4j.core.ast.FunctionExpression; import com.github.sommeri.less4j.core.ast.NumberExpression; @@ -524,60 +522,6 @@ protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHan } -abstract class AbstractMultiParameterFunction implements Function { - - @Override - public Expression evaluate(Expression parameters, ProblemsHandler problemsHandler) { - List splitParameters; - - if (getMinParameters() == 1 && parameters.getType() != ASTCssNodeType.COMPOSED_EXPRESSION) { - splitParameters = Collections.singletonList(parameters); - } else if (parameters.getType() == ASTCssNodeType.COMPOSED_EXPRESSION) { - splitParameters = ((ComposedExpression) parameters).splitByComma(); - } else { - problemsHandler.wrongNumberOfArgumentsToFunction(parameters, getMinParameters()); - return parameters; - } - - if (splitParameters.size() >= getMinParameters() && splitParameters.size() <= getMaxParameters()) { - /* Validate */ - boolean valid = true; - for (int i = 0; i < splitParameters.size(); i++) { - if (!validateParameter(splitParameters.get(i), i, problemsHandler)) { - valid = false; - } - } - - if (valid) { - return evaluate(splitParameters, problemsHandler, parameters.getUnderlyingStructure()); - } else { - return parameters; - } - } else { - problemsHandler.wrongNumberOfArgumentsToFunction(parameters, getMinParameters()); - return parameters; - } - } - - protected abstract Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token); - - protected abstract int getMinParameters(); - - protected abstract int getMaxParameters(); - - protected abstract boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler); - - protected boolean validateParameter(Expression parameter, ASTCssNodeType expected, ProblemsHandler problemsHandler) { - if (parameter.getType() != expected) { - problemsHandler.wrongArgumentTypeToFunction(parameter, expected); - return false; - } else { - return true; - } - } - -} - abstract class AbstractColorFunction extends AbstractMultiParameterFunction { static double clamp(double val) { diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java index c64319ae..ae90ff01 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java @@ -61,6 +61,7 @@ public ExpressionEvaluator(Scope scope, ProblemsHandler problemsHandler) { functions.add(new MathFunctions(problemsHandler)); functions.add(new StringFunctions(problemsHandler)); functions.add(new ColorFunctions(problemsHandler)); + functions.add(new MiscFunctions(problemsHandler)); functions.add(new UnknownFunctions(problemsHandler)); } diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java new file mode 100644 index 00000000..e9f3a6a0 --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java @@ -0,0 +1,123 @@ +package com.github.sommeri.less4j.core.compiler.expressions; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.github.sommeri.less4j.core.ast.ASTCssNodeType; +import com.github.sommeri.less4j.core.ast.ColorExpression; +import com.github.sommeri.less4j.core.ast.CssString; +import com.github.sommeri.less4j.core.ast.Expression; +import com.github.sommeri.less4j.core.ast.FunctionExpression; +import com.github.sommeri.less4j.core.ast.IdentifierExpression; +import com.github.sommeri.less4j.core.ast.NumberExpression; +import com.github.sommeri.less4j.core.ast.NumberExpression.Dimension; +import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; +import com.github.sommeri.less4j.core.problems.ProblemsHandler; + +public class MiscFunctions implements FunctionsPackage { + + protected static final String COLOR = "color"; + protected static final String UNIT = "unit"; + + private static Map FUNCTIONS = new HashMap(); + static { + FUNCTIONS.put(COLOR, new Color()); + FUNCTIONS.put(UNIT, new Unit()); + } + + private final ProblemsHandler problemsHandler; + + public MiscFunctions(ProblemsHandler problemsHandler) { + this.problemsHandler = problemsHandler; + } + + /* (non-Javadoc) + * @see com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage#canEvaluate(com.github.sommeri.less4j.core.ast.FunctionExpression, com.github.sommeri.less4j.core.ast.Expression) + */ + @Override + public boolean canEvaluate(FunctionExpression input, Expression parameters) { + return FUNCTIONS.containsKey(input.getName()); + } + + /* (non-Javadoc) + * @see com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage#evaluate(com.github.sommeri.less4j.core.ast.FunctionExpression, com.github.sommeri.less4j.core.ast.Expression) + */ + @Override + public Expression evaluate(FunctionExpression input, Expression parameters) { + if (!canEvaluate(input, parameters)) + return input; + + Function function = FUNCTIONS.get(input.getName()); + return function.evaluate(parameters, problemsHandler); + } + +} + +class Color extends AbstractMultiParameterFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + CssString string = (CssString) splitParameters.get(0); + return new ColorExpression(token, string.getValue()); + } + + @Override + protected int getMinParameters() { + return 1; + } + + @Override + protected int getMaxParameters() { + return 1; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.STRING_EXPRESSION, problemsHandler); + } + +} + +class Unit extends AbstractMultiParameterFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + NumberExpression dimension = (NumberExpression) splitParameters.get(0); + IdentifierExpression unit = splitParameters.size() > 1 ? (IdentifierExpression) splitParameters.get(1) : null; + + String newSuffix; + Dimension newDimension; + if (unit != null) { + newSuffix = unit.getValue(); + newDimension = Dimension.forSuffix(newSuffix); + } else { + newSuffix = ""; + newDimension = Dimension.NUMBER; + } + + return new NumberExpression(token, dimension.getValueAsDouble(), "", "" + dimension.getValueAsDouble() + newSuffix, newDimension); + } + + @Override + protected int getMinParameters() { + return 1; + } + + @Override + protected int getMaxParameters() { + return 2; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + switch (position) { + case 0: + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + case 1: + return validateParameter(parameter, ASTCssNodeType.IDENTIFIER_EXPRESSION, problemsHandler); + } + return false; + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java b/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java index ee99041e..c87386af 100644 --- a/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java +++ b/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java @@ -77,8 +77,8 @@ public void wrongNumberOfArgumentsToFunction(Expression param, int expectedArgum collector.addError(new CompilationError(param, "Wrong number of arguments to function, should be " + expectedArguments + ".")); } - public void wrongArgumentTypeToFunction(Expression param, ASTCssNodeType expected) { - collector.addError(new CompilationError(param, "Wrong argument type to function, should be " + expected + ".")); + public void wrongArgumentTypeToFunction(Expression param, ASTCssNodeType expected, ASTCssNodeType received) { + collector.addError(new CompilationError(param, "Wrong argument type to function, expected " + expected + " saw " + received + ".")); } public void variablesCycle(List cycle) { diff --git a/src/test/resources/compile-basic-features/functions/functions-color.css b/src/test/resources/compile-basic-features/functions/functions-color.css index 80712df9..340a827c 100644 --- a/src/test/resources/compile-basic-features/functions/functions-color.css +++ b/src/test/resources/compile-basic-features/functions/functions-color.css @@ -38,6 +38,9 @@ roundedpx: 3px; percentage: 20%; color: #ff0011; + color2: #aaa; + unit: 5px; + unit2: 15; } #built-in .is-a { color: true; diff --git a/src/test/resources/compile-basic-features/functions/functions-color.less b/src/test/resources/compile-basic-features/functions/functions-color.less index 54b8259f..9d6d450e 100644 --- a/src/test/resources/compile-basic-features/functions/functions-color.less +++ b/src/test/resources/compile-basic-features/functions/functions-color.less @@ -42,6 +42,9 @@ roundedpx: round(10px / 3); percentage: percentage(10px / 50); color: color("#ff0011"); + color2: color("#aaa"); + unit: unit(5, px); + unit2: unit(15em); .is-a { color: iscolor(#ddd); From 789d0e501145feb36755ed44cf8071bf704ebbf9 Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 10:42:44 +1300 Subject: [PATCH 10/29] Don't process filter declarations. The almost-css21.less has an example of a filter declaration that invokes an alpha function. Now that we've implemented the alpha function this test fails as it's not supposed to be a function. Best to just not process filter declarations I think. References issue [#90] --- .../java/com/github/sommeri/less4j/core/ast/Declaration.java | 4 ++++ .../sommeri/less4j/core/compiler/LessToCssCompiler.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/Declaration.java b/src/main/java/com/github/sommeri/less4j/core/ast/Declaration.java index a4130d99..4540fa2a 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/Declaration.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/Declaration.java @@ -63,6 +63,10 @@ public List getChilds() { public boolean isFontDeclaration() { return getName()!=null? getName().toLowerCase().equals("font") : false; } + + public boolean isFilterDeclaration() { + return getName()!=null? getName().toLowerCase().equals("filter") : false; + } @Override public String toString() { diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/LessToCssCompiler.java b/src/main/java/com/github/sommeri/less4j/core/compiler/LessToCssCompiler.java index f7886785..d17ddd3c 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/LessToCssCompiler.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/LessToCssCompiler.java @@ -142,7 +142,7 @@ private void evaluateExpressions(ASTCssNode node) { } private void evaluateInDeclaration(Declaration node) { - if (!node.isFontDeclaration()) { + if (!node.isFontDeclaration() && !node.isFilterDeclaration()) { evaluateExpressions(node); return; } From c779b63a9231e8244b64a3d96a10a15475747ade Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 10:56:00 +1300 Subject: [PATCH 11/29] Color functions: change colour to store its RGB values as floating point so we mimic the accuracy of less.js under computation such as to and from HSL. Also change to use the existing formatting for numbers (didn't realise I could pass null for originalString), which tidies up the output by removing extra .0s on the ends of numbers. These accuracy changes mean much more of the test case functions-color.less is now correct. There is one case of too many decimal places of accuracy that I still need to address. Also I have removed the first set of functions from the test case as they don't appear to be in less.js either? --- .../less4j/core/ast/ColorExpression.java | 36 ++++++++-------- .../compiler/expressions/ColorFunctions.java | 42 +++++++++---------- .../compiler/expressions/MiscFunctions.java | 2 +- .../functions/functions-color.css | 7 ---- .../functions/functions-color.less | 9 ---- 5 files changed, 40 insertions(+), 56 deletions(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java b/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java index 6568054e..43bbfd71 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java @@ -9,16 +9,16 @@ public class ColorExpression extends Expression { protected String value; - protected int red; - protected int green; - protected int blue; + protected double red; + protected double green; + protected double blue; public ColorExpression(HiddenTokenAwareTree token, String value) { super(token); setValue(value); } - public ColorExpression(HiddenTokenAwareTree token, int red, int green, int blue) { + public ColorExpression(HiddenTokenAwareTree token, double red, double green, double blue) { super(token); this.red = red; this.green = green; @@ -41,19 +41,19 @@ public void setValue(String value) { blue=decode(value, 2); } - public int getRed() { + public double getRed() { return red; } - public int getGreen() { + public double getGreen() { return green; } - public int getBlue() { + public double getBlue() { return blue; } - public float getAlpha() { + public double getAlpha() { return 1.0f; } @@ -66,15 +66,15 @@ private int decode(String color, int i) { return Integer.parseInt(color.substring(i*2+1, i*2+3), 16); } - private String encode(int red, int green, int blue) { + private String encode(double red, double green, double blue) { return "#" + toHex(red) + toHex(green) + toHex(blue); } - protected String toHex(int color) { + protected String toHex(double color) { String prefix = ""; if (color<16) prefix = "0"; - return prefix + Integer.toHexString(color); + return prefix + Integer.toHexString((int) Math.round(color)); } @Override @@ -102,7 +102,7 @@ public String toARGB() { } public Color toColor() { - return new Color(this.red, this.green, this.blue); + return new Color((int)Math.round(this.red), (int)Math.round(this.green), (int)Math.round(this.blue)); } public static class ColorWithAlphaExpression extends ColorExpression { @@ -110,9 +110,9 @@ public static class ColorWithAlphaExpression extends ColorExpression { /** * Alpha in the range 0-1. */ - private float alpha; + private double alpha; - public ColorWithAlphaExpression(HiddenTokenAwareTree token, int red, int green, int blue, float alpha) { + public ColorWithAlphaExpression(HiddenTokenAwareTree token, double red, double green, double blue, double alpha) { super(token, red, green, blue); this.alpha = alpha; if (alpha != 1.0) { @@ -125,12 +125,12 @@ public ColorWithAlphaExpression(HiddenTokenAwareTree token, Color color) { } @Override - public float getAlpha() { + public double getAlpha() { return alpha; } - protected String encode(int red, int green, int blue, float alpha) { - return "rgba(" + red + ", " + green + ", " + blue + ", " + alpha + ")"; + protected String encode(double red, double green, double blue, double alpha) { + return "rgba(" + Math.round(red) + ", " + Math.round(green) + ", " + Math.round(blue) + ", " + alpha + ")"; } @Override @@ -140,7 +140,7 @@ public String toARGB() { @Override public Color toColor() { - return new Color(this.red, this.green, this.blue, Math.round(this.alpha * 255)); + return new Color((int)Math.round(this.red), (int)Math.round(this.green), (int)Math.round(this.blue), Math.round(this.alpha * 255)); } } diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java index c8bd2169..ed4e6185 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -171,8 +171,8 @@ protected Expression evaluate(List parameters, ProblemsHandler probl } private Expression evaluate(NumberExpression r, NumberExpression g, NumberExpression b, NumberExpression a, HiddenTokenAwareTree token) { - return new ColorExpression.ColorWithAlphaExpression(token, (int) Math.round(scaled(r, 255)), (int) Math.round(scaled(g, 255)), - (int) Math.round(scaled(b, 255)), (float) number(a)); + return new ColorExpression.ColorWithAlphaExpression(token, scaled(r, 255), scaled(g, 255), + scaled(b, 255), number(a)); } @Override @@ -200,7 +200,7 @@ protected Expression evaluate(List parameters, ProblemsHandler probl } private Expression evaluate(NumberExpression h, NumberExpression s, NumberExpression l, HiddenTokenAwareTree token) { - return hsla(new HSLAValue((float) number(h), (float) number(s), (float) number(l)), token); + return hsla(new HSLAValue(number(h), number(s), number(l)), token); } @Override @@ -229,7 +229,7 @@ protected Expression evaluate(List parameters, ProblemsHandler probl } private Expression evaluate(NumberExpression h, NumberExpression s, NumberExpression l, NumberExpression a, HiddenTokenAwareTree token) { - return hsla(new HSLAValue((float) number(h), (float) number(s), (float) number(l), (float) number(a)), token); + return hsla(new HSLAValue(number(h), number(s), number(l), number(a)), token); } @Override @@ -278,7 +278,7 @@ class Hue extends AbstractColorOperationFunction { @Override protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { HSLAValue hsla = toHSLA(color); - return new NumberExpression(token, Double.valueOf(hsla.h), "", "" + (float) hsla.h, Dimension.NUMBER); + return new NumberExpression(token, Double.valueOf(hsla.h), "", null, Dimension.NUMBER); } } @@ -288,7 +288,7 @@ class Saturation extends AbstractColorOperationFunction { @Override protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { HSLAValue hsla = toHSLA(color); - return new NumberExpression(token, Double.valueOf(hsla.s * 100), "%", "" + ((float) hsla.s * 100) + "%", Dimension.PERCENTAGE); + return new NumberExpression(token, Double.valueOf(hsla.s * 100), "%", null, Dimension.PERCENTAGE); } } @@ -298,7 +298,7 @@ class Lightness extends AbstractColorOperationFunction { @Override protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { HSLAValue hsla = toHSLA(color); - return new NumberExpression(token, Double.valueOf(hsla.l * 100), "%", "" + ((float) hsla.l * 100) + "%", Dimension.PERCENTAGE); + return new NumberExpression(token, Double.valueOf(hsla.l * 100), "%", null, Dimension.PERCENTAGE); } } @@ -307,7 +307,7 @@ class Red extends AbstractColorOperationFunction { @Override protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { - return new NumberExpression(token, Double.valueOf(color.getRed()), "", "" + color.getRed(), Dimension.NUMBER); + return new NumberExpression(token, Double.valueOf(color.getRed()), "", null, Dimension.NUMBER); } } @@ -316,7 +316,7 @@ class Green extends AbstractColorOperationFunction { @Override protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { - return new NumberExpression(token, Double.valueOf(color.getGreen()), "", "" + color.getGreen(), Dimension.NUMBER); + return new NumberExpression(token, Double.valueOf(color.getGreen()), "", null, Dimension.NUMBER); } } @@ -325,7 +325,7 @@ class Blue extends AbstractColorOperationFunction { @Override protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { - return new NumberExpression(token, Double.valueOf(color.getBlue()), "", "" + color.getBlue(), Dimension.NUMBER); + return new NumberExpression(token, Double.valueOf(color.getBlue()), "", null, Dimension.NUMBER); } } @@ -334,7 +334,7 @@ class Alpha extends AbstractColorOperationFunction { @Override protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { - return new NumberExpression(token, Double.valueOf(color.getAlpha()), "", "" + color.getAlpha(), Dimension.NUMBER); + return new NumberExpression(token, Double.valueOf(color.getAlpha()), "", null, Dimension.NUMBER); } } @@ -347,7 +347,7 @@ protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHan .round((0.2126 * (color.getRed() / 255.0) + 0.7152 * (color.getGreen() / 255.0) + 0.0722 * (color.getBlue() / 255.0)) * color.getAlpha() * 100)); - return new NumberExpression(token, Double.valueOf(luma), "%", "" + (float) luma + "%", Dimension.PERCENTAGE); + return new NumberExpression(token, Double.valueOf(luma), "%", null, Dimension.PERCENTAGE); } } @@ -442,7 +442,7 @@ class Fade extends AbstractColorHSLAmountFunction { @Override protected void apply(NumberExpression amount, HSLAValue hsla) { - hsla.a = (float) (amount.getValueAsDouble() / 100.0f); + hsla.a = (amount.getValueAsDouble() / 100.0f); hsla.a = clamp(hsla.a); } @@ -452,7 +452,7 @@ class Spin extends AbstractColorHSLAmountFunction { @Override protected void apply(NumberExpression amount, HSLAValue hsla) { - float hue = (float) ((hsla.h + amount.getValueAsDouble()) % 360); + double hue = ((hsla.h + amount.getValueAsDouble()) % 360); hsla.h = hue < 0 ? 360 + hue : hue; } @@ -471,7 +471,7 @@ protected Expression evaluate(List splitParameters, ProblemsHandler NumberExpression weight = splitParameters.size() > 2 ? (NumberExpression) splitParameters.get(2) : null; if (weight == null) { - weight = new NumberExpression(token, Double.valueOf(50), "%", "50%", Dimension.PERCENTAGE); + weight = new NumberExpression(token, Double.valueOf(50), "%", null, Dimension.PERCENTAGE); } double p = weight.getValueAsDouble() / 100.0; @@ -481,10 +481,10 @@ protected Expression evaluate(List splitParameters, ProblemsHandler double w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; double w2 = 1 - w1; - return new ColorExpression.ColorWithAlphaExpression(token, (int)Math.round(color1.getRed() * w1 + color2.getRed() * w2), - (int)Math.round(color1.getGreen() * w1 + color2.getGreen() * w2), - (int)Math.round(color1.getBlue() * w1 + color2.getBlue() * w2), - (float) (color1.getAlpha() * p + color2.getAlpha() * (1 - p))); + return new ColorExpression.ColorWithAlphaExpression(token, color1.getRed() * w1 + color2.getRed() * w2, + color1.getGreen() * w1 + color2.getGreen() * w2, + color1.getBlue() * w1 + color2.getBlue() * w2, + color1.getAlpha() * p + color2.getAlpha() * (1 - p)); } @Override @@ -534,8 +534,8 @@ static ColorExpression hsla(HSLAValue hsla, HiddenTokenAwareTree token) { double m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; double m1 = l * 2 - m2; - return new ColorExpression.ColorWithAlphaExpression(token, (int) Math.round(hue(h + 1.0 / 3.0, m1, m2) * 255), (int) Math.round(hue(h, - m1, m2) * 255), (int) Math.round(hue(h - 1.0 / 3.0, m1, m2) * 255), (float) a); + return new ColorExpression.ColorWithAlphaExpression(token, hue(h + 1.0 / 3.0, m1, m2) * 255, hue(h, + m1, m2) * 255, hue(h - 1.0 / 3.0, m1, m2) * 255, a); } static double hue(double h, double m1, double m2) { diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java index e9f3a6a0..dff623cd 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java @@ -96,7 +96,7 @@ protected Expression evaluate(List splitParameters, ProblemsHandler newDimension = Dimension.NUMBER; } - return new NumberExpression(token, dimension.getValueAsDouble(), "", "" + dimension.getValueAsDouble() + newSuffix, newDimension); + return new NumberExpression(token, dimension.getValueAsDouble(), newSuffix, null, newDimension); } @Override diff --git a/src/test/resources/compile-basic-features/functions/functions-color.css b/src/test/resources/compile-basic-features/functions/functions-color.css index 340a827c..b390b9cf 100644 --- a/src/test/resources/compile-basic-features/functions/functions-color.css +++ b/src/test/resources/compile-basic-features/functions/functions-color.css @@ -1,10 +1,3 @@ -#functions { - color: #660000; - width: 16; - height: undefined("self"); - border-width: 5; - variable: 11; -} #built-in { escaped: -Some::weird(#thing, y); lighten: #ffcccc; diff --git a/src/test/resources/compile-basic-features/functions/functions-color.less b/src/test/resources/compile-basic-features/functions/functions-color.less index 9d6d450e..f61b2b71 100644 --- a/src/test/resources/compile-basic-features/functions/functions-color.less +++ b/src/test/resources/compile-basic-features/functions/functions-color.less @@ -1,12 +1,3 @@ -#functions { - @var: 10; - color: _color("evil red"); // #660000 - width: increment(15); - height: undefined("self"); - border-width: add(2, 3); - variable: increment(@var); -} - #built-in { @r: 32; escaped: e("-Some::weird(#thing, y)"); From 72f0c7e17ceac450e956150bb47cb006b3034061 Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 11:14:57 +1300 Subject: [PATCH 12/29] Color functions: implement Contrast. Note that there is a bug in less.js (I am submitting a bug report) in the contrast function when a threshold is given, so I have changed the expected result and added more test-cases. --- .../compiler/expressions/ColorFunctions.java | 45 ++++++++++++++++++- .../functions/functions-color.css | 5 ++- .../functions/functions-color.less | 3 ++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java index ed4e6185..3d45c942 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -84,7 +84,7 @@ public class ColorFunctions implements FunctionsPackage { FUNCTIONS.put(SPIN, new Spin()); FUNCTIONS.put(MIX, new Mix()); FUNCTIONS.put(GREYSCALE, new Greyscale()); -// FUNCTIONS.put(CONTRAST, new Contrast()); + FUNCTIONS.put(CONTRAST, new Contrast()); // FUNCTIONS.put(MULTIPLY, new Multiply()); // FUNCTIONS.put(SCREEN, new Screen()); @@ -522,6 +522,49 @@ protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHan } +class Contrast extends AbstractMultiParameterFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + ColorExpression color = (ColorExpression) splitParameters.get(0); + ColorExpression dark = (ColorExpression) (splitParameters.size() > 1 ? splitParameters.get(1) : new ColorExpression(token, 0, 0, 0)); + ColorExpression light = (ColorExpression) (splitParameters.size() > 2 ? splitParameters.get(2) : new ColorExpression(token, 255, 255, 255)); + NumberExpression threshold = (NumberExpression) (splitParameters.size() > 3 ? splitParameters.get(3) : new NumberExpression(token, 43.0, "%", null, Dimension.PERCENTAGE)); + double thresholdValue = AbstractColorFunction.number(threshold); + + if (((0.2126 * (color.getRed()/255) + 0.7152 * (color.getGreen()/255) + 0.0722 * (color.getBlue()/255)) * color.getAlpha()) < thresholdValue) { + return light; + } else { + return dark; + } + } + + @Override + protected int getMinParameters() { + return 1; + } + + @Override + protected int getMaxParameters() { + return 4; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + switch (position) { + case 0: + case 1: + case 2: + return validateParameter(parameter, ASTCssNodeType.COLOR_EXPRESSION, problemsHandler); + case 3: + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + return false; + } + + +} + abstract class AbstractColorFunction extends AbstractMultiParameterFunction { static double clamp(double val) { diff --git a/src/test/resources/compile-basic-features/functions/functions-color.css b/src/test/resources/compile-basic-features/functions/functions-color.css index b390b9cf..07f773f8 100644 --- a/src/test/resources/compile-basic-features/functions/functions-color.css +++ b/src/test/resources/compile-basic-features/functions/functions-color.css @@ -26,7 +26,10 @@ contrast2: #ffffff; contrast3: #dddddd; contrast4: #000000; - contrast5: #ffffff; + contrast5: #000000; + contrast6: #000000; + contrast7: #ffffff; + contrast8: #ffffff; rounded: 11; roundedpx: 3px; percentage: 20%; diff --git a/src/test/resources/compile-basic-features/functions/functions-color.less b/src/test/resources/compile-basic-features/functions/functions-color.less index f61b2b71..08f7fc4d 100644 --- a/src/test/resources/compile-basic-features/functions/functions-color.less +++ b/src/test/resources/compile-basic-features/functions/functions-color.less @@ -29,6 +29,9 @@ contrast3: contrast(#222222, #101010, #dddddd); contrast4: contrast(hsl(90, 100%, 50%),#000000,#ffffff,40%); contrast5: contrast(hsl(90, 100%, 50%),#000000,#ffffff,60%); + contrast6: contrast(hsl(90, 100%, 50%),#000000,#ffffff,80%); + contrast7: contrast(hsl(90, 100%, 50%),#000000,#ffffff,90%); + contrast8: contrast(hsl(90, 100%, 50%),#000000,#ffffff,100%); rounded: round(@r/3); roundedpx: round(10px / 3); percentage: percentage(10px / 50); From b427975154f1d15ba8afe3bc18f3940720f99cff Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 11:35:24 +1300 Subject: [PATCH 13/29] If validation fails for the function return null, so we don't alter the output. A test-case to support this is coming, it was in less.js test-cases. --- .../expressions/AbstractMultiParameterFunction.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java index 794bc0ee..730e7240 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java @@ -21,7 +21,7 @@ public Expression evaluate(Expression parameters, ProblemsHandler problemsHandle splitParameters = ((ComposedExpression) parameters).splitByComma(); } else { problemsHandler.wrongNumberOfArgumentsToFunction(parameters, getMinParameters()); - return parameters; + return null; } if (splitParameters.size() >= getMinParameters() && splitParameters.size() <= getMaxParameters()) { @@ -36,11 +36,11 @@ public Expression evaluate(Expression parameters, ProblemsHandler problemsHandle if (valid) { return evaluate(splitParameters, problemsHandler, parameters.getUnderlyingStructure()); } else { - return parameters; + return null; } } else { problemsHandler.wrongNumberOfArgumentsToFunction(parameters, getMinParameters()); - return parameters; + return null; } } From 7855a14dd013950748d52d48adfaf502a1cbd5d1 Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 11:41:56 +1300 Subject: [PATCH 14/29] Color functions: Contrast now supports failing silently if the first parameter isn't a color, to support filters I guess. Test-cases updated with additional color testing cases from less.js. This matches the test-case from less.js, included in functions-color.less now. The bug in less.js is unclear actually, the documentation suggests that the threshold parameter to contrast should be a percentage but it doesn't work if it is. Less.js has updated their test-cases to use decimal numbers rather than percentages. I have included those cases, but left in the percentage cases as we handle those "correctly", but don't match less.js. --- .../compiler/expressions/ColorFunctions.java | 1 + .../functions/functions-color.css | 84 ++++++++++++------ .../functions/functions-color.less | 86 +++++++++++++------ 3 files changed, 117 insertions(+), 54 deletions(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java index 3d45c942..7919691c 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -553,6 +553,7 @@ protected int getMaxParameters() { protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { switch (position) { case 0: + return (parameter.getType() == ASTCssNodeType.COLOR_EXPRESSION); case 1: case 2: return validateParameter(parameter, ASTCssNodeType.COLOR_EXPRESSION, problemsHandler); diff --git a/src/test/resources/compile-basic-features/functions/functions-color.css b/src/test/resources/compile-basic-features/functions/functions-color.css index 07f773f8..a1237c75 100644 --- a/src/test/resources/compile-basic-features/functions/functions-color.css +++ b/src/test/resources/compile-basic-features/functions/functions-color.css @@ -7,6 +7,37 @@ greyscale: #2e2e2e; spin-p: #bf6a40; spin-n: #bf4055; + luma-white: 100%; + luma-black: 0%; + luma-black-alpha: 0%; + luma-red: 21%; + luma-green: 72%; + luma-blue: 7%; + luma-yellow: 93%; + luma-cyan: 79%; + luma-white-alpha: 50%; + contrast-filter: contrast(30%); + contrast-white: #000000; + contrast-black: #ffffff; + contrast-red: #ffffff; + contrast-green: #000000; + contrast-blue: #ffffff; + contrast-yellow: #000000; + contrast-cyan: #000000; + contrast-light: #111111; + contrast-dark: #eeeeee; + contrast-light-thresh: #111111; + contrast-dark-thresh: #eeeeee; + contrast-high-thresh: #eeeeee; + contrast-low-thresh: #111111; + contrast: #000000; + contrast2: #ffffff; + contrast3: #dddddd; + contrast4: #000000; + contrast5: #000000; + contrast6: #000000; + contrast7: #ffffff; + contrast8: #ffffff; format: "rgb(32, 128, 64)"; format-string: "hello world"; format-multiple: "hello earth 2"; @@ -15,40 +46,39 @@ hue: 98; saturation: 12%; lightness: 95%; - red: 17; - green: 34; - blue: 51; + red: 255; + green: 255; + blue: 255; alpha: 0.5; luma: 65%; mix: #800080; mix2: rgba(75, 25, 0, 0.75); - contrast: #000000; - contrast2: #ffffff; - contrast3: #dddddd; - contrast4: #000000; - contrast5: #000000; - contrast6: #000000; - contrast7: #ffffff; - contrast8: #ffffff; - rounded: 11; - roundedpx: 3px; - percentage: 20%; color: #ff0011; color2: #aaa; - unit: 5px; - unit2: 15; -} -#built-in .is-a { - color: true; - color1: true; - color2: true; - keyword: true; - number: true; - string: true; - pixel: true; - percent: true; - em: true; + tint: #898989; + tint-full: #ffffff; + tint-percent: #898989; + shade: #686868; + shade-full: #000000; + shade-percent: #686868; + hsv: #4d2926; + hsva: rgba(77, 40, 38, 0.2); + mix: #ff3300; + mix-0: #ffff00; + mix-100: #ff0000; + mix-weightless: #ff8000; } #alpha { alpha: rgba(153, 94, 51, 0.6); } +#blendmodes { + multiply: #ed0000; + screen: #f600f6; + overlay: #ed0000; + softlight: #ff0000; + hardlight: #0000ed; + difference: #f600f6; + exclusion: #f600f6; + average: #7b007b; + negation: #d73131; +} diff --git a/src/test/resources/compile-basic-features/functions/functions-color.less b/src/test/resources/compile-basic-features/functions/functions-color.less index 08f7fc4d..87052485 100644 --- a/src/test/resources/compile-basic-features/functions/functions-color.less +++ b/src/test/resources/compile-basic-features/functions/functions-color.less @@ -8,6 +8,37 @@ greyscale: greyscale(#203c31); spin-p: spin(hsl(340, 50%, 50%), 40); spin-n: spin(hsl(30, 50%, 50%), -40); + luma-white: luma(#fff); + luma-black: luma(#000); + luma-black-alpha: luma(rgba(0,0,0,0.5)); + luma-red: luma(#ff0000); + luma-green: luma(#00ff00); + luma-blue: luma(#0000ff); + luma-yellow: luma(#ffff00); + luma-cyan: luma(#00ffff); + luma-white-alpha: luma(rgba(255,255,255,0.5)); + contrast-filter: contrast(30%); + contrast-white: contrast(#fff); + contrast-black: contrast(#000); + contrast-red: contrast(#ff0000); + contrast-green: contrast(#00ff00); + contrast-blue: contrast(#0000ff); + contrast-yellow: contrast(#ffff00); + contrast-cyan: contrast(#00ffff); + contrast-light: contrast(#fff, #111111, #eeeeee); + contrast-dark: contrast(#000, #111111, #eeeeee); + contrast-light-thresh: contrast(#fff, #111111, #eeeeee, 0.5); + contrast-dark-thresh: contrast(#000, #111111, #eeeeee, 0.5); + contrast-high-thresh: contrast(#555, #111111, #eeeeee, 0.6); + contrast-low-thresh: contrast(#555, #111111, #eeeeee, 0.1); + contrast: contrast(#aaaaaa); + contrast2: contrast(#222222, #101010); + contrast3: contrast(#222222, #101010, #dddddd); + contrast4: contrast(hsl(90, 100%, 50%),#000000,#ffffff,40%); + contrast5: contrast(hsl(90, 100%, 50%),#000000,#ffffff,60%); + contrast6: contrast(hsl(90, 100%, 50%),#000000,#ffffff,80%); + contrast7: contrast(hsl(90, 100%, 50%),#000000,#ffffff,90%); + contrast8: contrast(hsl(90, 100%, 50%),#000000,#ffffff,100%); format: %("rgb(%d, %d, %d)", @r, 128, 64); format-string: %("hello %s", "world"); format-multiple: %("hello %s %d", "earth", 2); @@ -17,42 +48,43 @@ hue: hue(hsl(98, 12%, 95%)); saturation: saturation(hsl(98, 12%, 95%)); lightness: lightness(hsl(98, 12%, 95%)); - red: red(#112233); - green: green(#112233); - blue: blue(#112233); + red: red(#f00); + green: green(#0f0); + blue: blue(#00f); alpha: alpha(rgba(10, 20, 30, 0.5)); luma: luma(rgb(100, 200, 30)); mix: mix(#ff0000, #0000ff, 50%); mix2: mix(rgba(100,0,0,1.0), rgba(0,100,0,0.5), 50%); - contrast: contrast(#aaaaaa); - contrast2: contrast(#222222, #101010); - contrast3: contrast(#222222, #101010, #dddddd); - contrast4: contrast(hsl(90, 100%, 50%),#000000,#ffffff,40%); - contrast5: contrast(hsl(90, 100%, 50%),#000000,#ffffff,60%); - contrast6: contrast(hsl(90, 100%, 50%),#000000,#ffffff,80%); - contrast7: contrast(hsl(90, 100%, 50%),#000000,#ffffff,90%); - contrast8: contrast(hsl(90, 100%, 50%),#000000,#ffffff,100%); - rounded: round(@r/3); - roundedpx: round(10px / 3); - percentage: percentage(10px / 50); color: color("#ff0011"); color2: color("#aaa"); - unit: unit(5, px); - unit2: unit(15em); + tint: tint(#777777, 13); + tint-full: tint(#777777, 100); + tint-percent: tint(#777777, 13%); + shade: shade(#777777, 13); + shade-full: shade(#777777, 100); + shade-percent: shade(#777777, 13%); + + hsv: hsv(5, 50%, 30%); + hsva: hsva(3, 50%, 30%, 0.2); - .is-a { - color: iscolor(#ddd); - color1: iscolor(red); - color2: iscolor(rgb(0, 0, 0)); - keyword: iskeyword(hello); - number: isnumber(32); - string: isstring("hello"); - pixel: ispixel(32px); - percent: ispercentage(32%); - em: isem(32em); - } + mix: mix(#ff0000, #ffff00, 80); + mix-0: mix(#ff0000, #ffff00, 0); + mix-100: mix(#ff0000, #ffff00, 100); + mix-weightless: mix(#ff0000, #ffff00); } #alpha { alpha: darken(hsla(25, 50%, 50%, 0.6), 10%); } + +#blendmodes { + multiply: multiply(#f60000, #f60000); + screen: screen(#f60000, #0000f6); + overlay: overlay(#f60000, #0000f6); + softlight: softlight(#f60000, #ffffff); + hardlight: hardlight(#f60000, #0000f6); + difference: difference(#f60000, #0000f6); + exclusion: exclusion(#f60000, #0000f6); + average: average(#f60000, #0000f6); + negation: negation(#f60000, #313131); +} From 2e7e37b3fdf72b9f6dda0a0425b6718456a67d9a Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 14:40:49 +1300 Subject: [PATCH 15/29] Color functions: hsv, hsva --- .../compiler/expressions/ColorFunctions.java | 88 ++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java index 7919691c..5fa386aa 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -62,8 +62,8 @@ public class ColorFunctions implements FunctionsPackage { FUNCTIONS.put(ARGB, new ARGB()); FUNCTIONS.put(HSL, new HSL()); FUNCTIONS.put(HSLA, new HSLA()); - // FUNCTIONS.put(HSV, new HSV()); - // FUNCTIONS.put(HSVA, new HSVA()); + FUNCTIONS.put(HSV, new HSV()); + FUNCTIONS.put(HSVA, new HSVA()); FUNCTIONS.put(HUE, new Hue()); FUNCTIONS.put(SATURATION, new Saturation()); @@ -249,6 +249,63 @@ protected int getMaxParameters() { } +class HSV extends AbstractColorFunction { + + @Override + protected Expression evaluate(List parameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((NumberExpression) parameters.get(0), (NumberExpression) parameters.get(1), (NumberExpression) parameters.get(2), token); + } + + private Expression evaluate(NumberExpression h, NumberExpression s, NumberExpression v, HiddenTokenAwareTree token) { + return hsva(number(h), number(s), number(v), 1.0, token); + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + + @Override + protected int getMinParameters() { + return 3; + } + + @Override + protected int getMaxParameters() { + return 3; + } + +} + +class HSVA extends AbstractColorFunction { + + @Override + protected Expression evaluate(List parameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((NumberExpression) parameters.get(0), (NumberExpression) parameters.get(1), (NumberExpression) parameters.get(2), + (NumberExpression) parameters.get(3), token); + } + + private Expression evaluate(NumberExpression h, NumberExpression s, NumberExpression v, NumberExpression a, HiddenTokenAwareTree token) { + return hsva(number(h), number(s), number(v), number(a), token); + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + + @Override + protected int getMinParameters() { + return 4; + } + + @Override + protected int getMaxParameters() { + return 4; + } + +} + class ARGB extends AbstractColorFunction { @Override @@ -581,6 +638,33 @@ static ColorExpression hsla(HSLAValue hsla, HiddenTokenAwareTree token) { return new ColorExpression.ColorWithAlphaExpression(token, hue(h + 1.0 / 3.0, m1, m2) * 255, hue(h, m1, m2) * 255, hue(h - 1.0 / 3.0, m1, m2) * 255, a); } + + static final int[][] hsvaPerm = new int[][] { + new int[] { 0, 3, 1 }, + new int[] { 2, 0, 1 }, + new int[] { 1, 0, 3 }, + new int[] { 1, 2, 0 }, + new int[] { 3, 1, 0 }, + new int[] { 0, 1, 2 } + }; + + static ColorExpression hsva(double h, double s, double v, double a, HiddenTokenAwareTree token) { + h = ((h % 360) / 360) * 360; + + int i = (int) Math.floor((h / 60) % 6); + double f = (h / 60) - i; + + double[] vs = new double[] { v, + v * (1 - s), + v * (1 - f * s), + v * (1 - (1 - f) * s) + }; + + return new ColorExpression.ColorWithAlphaExpression(token, vs[hsvaPerm[i][0]] * 255, + vs[hsvaPerm[i][1]] * 255, + vs[hsvaPerm[i][2]] * 255, + a); + } static double hue(double h, double m1, double m2) { h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h); From 47a33cb922051069d171433f8c8e445f86a51544 Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 15:18:03 +1300 Subject: [PATCH 16/29] Color functions: all blending functions --- .../compiler/expressions/ColorFunctions.java | 164 +++++++++++++++--- 1 file changed, 144 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java index 5fa386aa..dff4cd4d 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -86,15 +86,15 @@ public class ColorFunctions implements FunctionsPackage { FUNCTIONS.put(GREYSCALE, new Greyscale()); FUNCTIONS.put(CONTRAST, new Contrast()); -// FUNCTIONS.put(MULTIPLY, new Multiply()); -// FUNCTIONS.put(SCREEN, new Screen()); -// FUNCTIONS.put(OVERLAY, new Overlay()); -// FUNCTIONS.put(SOFTLIGHT, new Softlight()); -// FUNCTIONS.put(HARDLIGHT, new Hardlight()); -// FUNCTIONS.put(DIFFERENCE, new Difference()); -// FUNCTIONS.put(EXCLUSION, new Exclusion()); -// FUNCTIONS.put(AVERAGE, new Average()); -// FUNCTIONS.put(NEGATION, new Negation()); + FUNCTIONS.put(MULTIPLY, new Multiply()); + FUNCTIONS.put(SCREEN, new Screen()); + FUNCTIONS.put(OVERLAY, new Overlay()); + FUNCTIONS.put(SOFTLIGHT, new Softlight()); + FUNCTIONS.put(HARDLIGHT, new Hardlight()); + FUNCTIONS.put(DIFFERENCE, new Difference()); + FUNCTIONS.put(EXCLUSION, new Exclusion()); + FUNCTIONS.put(AVERAGE, new Average()); + FUNCTIONS.put(NEGATION, new Negation()); } private final ProblemsHandler problemsHandler; @@ -142,7 +142,7 @@ protected Expression evaluate(List parameters, ProblemsHandler probl } private Expression evaluate(NumberExpression r, NumberExpression g, NumberExpression b, HiddenTokenAwareTree token) { - return new ColorExpression(token, (int) Math.round(scaled(r, 255)), (int) Math.round(scaled(g, 255)), (int) Math.round(scaled(b, 255))); + return rgb(scaled(r, 255), scaled(g, 255), scaled(b, 255), token); } @Override @@ -171,8 +171,7 @@ protected Expression evaluate(List parameters, ProblemsHandler probl } private Expression evaluate(NumberExpression r, NumberExpression g, NumberExpression b, NumberExpression a, HiddenTokenAwareTree token) { - return new ColorExpression.ColorWithAlphaExpression(token, scaled(r, 255), scaled(g, 255), - scaled(b, 255), number(a)); + return rgba(scaled(r, 255), scaled(g, 255), scaled(b, 255), number(a), token); } @Override @@ -538,10 +537,10 @@ protected Expression evaluate(List splitParameters, ProblemsHandler double w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; double w2 = 1 - w1; - return new ColorExpression.ColorWithAlphaExpression(token, color1.getRed() * w1 + color2.getRed() * w2, + return rgba(color1.getRed() * w1 + color2.getRed() * w2, color1.getGreen() * w1 + color2.getGreen() * w2, color1.getBlue() * w1 + color2.getBlue() * w2, - color1.getAlpha() * p + color2.getAlpha() * (1 - p)); + color1.getAlpha() * p + color2.getAlpha() * (1 - p), token); } @Override @@ -619,7 +618,127 @@ protected boolean validateParameter(Expression parameter, int position, Problems } return false; } + +} + +class Multiply extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + return a * b / 255.0; + } + +} + +class Screen extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + return 255 - (255 - a) * (255 - b) / 255; + } + +} + +class Overlay extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + return a < 128 ? 2 * a * b / 255 : 255 - 2 * (255 - a) * (255 - b) / 255; + } + +} + +class Softlight extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + double t = b * a / 255; + return t + a * (255 - (255 - a) * (255 - b) / 255 - t) / 255; + } + +} + +class Hardlight extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + return b < 128 ? 2 * b * a / 255 : 255 - 2 * (255 - b) * (255 - a) / 255; + } + +} + +class Difference extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + return Math.abs(a - b); + } + +} + +class Exclusion extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + return a + b * (255 - a - a) / 255; + } + +} + +class Average extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + return (a + b) / 2; + } + +} + +class Negation extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + return 255 - Math.abs(255 - b - a); + } +} + +abstract class AbstractSimpleColorBlendFunction extends AbstractColorBlendFunction { + + @Override + protected Expression evaluate(ColorExpression color1, ColorExpression color2, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return rgb(evaluate(color1.getRed(), color2.getRed()), + evaluate(color1.getGreen(), color2.getGreen()), + evaluate(color1.getBlue(), color2.getBlue()), token); + } + + protected abstract double evaluate(double a, double b); + +} + +abstract class AbstractColorBlendFunction extends AbstractColorFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((ColorExpression)splitParameters.get(0), (ColorExpression)splitParameters.get(1), problemsHandler, token); + } + + protected abstract Expression evaluate(ColorExpression color1, ColorExpression color2, ProblemsHandler problemsHandler, HiddenTokenAwareTree token); + + @Override + protected int getMinParameters() { + return 2; + } + + @Override + protected int getMaxParameters() { + return 2; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.COLOR_EXPRESSION, problemsHandler); + } } @@ -628,6 +747,14 @@ abstract class AbstractColorFunction extends AbstractMultiParameterFunction { static double clamp(double val) { return Math.min(1, Math.max(0, val)); } + + static ColorExpression rgb(double r, double g, double b, HiddenTokenAwareTree token) { + return new ColorExpression(token, r, g, b); + } + + static ColorExpression rgba(double r, double g, double b, double a, HiddenTokenAwareTree token) { + return new ColorExpression.ColorWithAlphaExpression(token, r, g, b, a); + } static ColorExpression hsla(HSLAValue hsla, HiddenTokenAwareTree token) { double h = (hsla.h % 360) / 360, s = hsla.s, l = hsla.l, a = hsla.a; @@ -635,8 +762,8 @@ static ColorExpression hsla(HSLAValue hsla, HiddenTokenAwareTree token) { double m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; double m1 = l * 2 - m2; - return new ColorExpression.ColorWithAlphaExpression(token, hue(h + 1.0 / 3.0, m1, m2) * 255, hue(h, - m1, m2) * 255, hue(h - 1.0 / 3.0, m1, m2) * 255, a); + return rgba(hue(h + 1.0 / 3.0, m1, m2) * 255, hue(h, + m1, m2) * 255, hue(h - 1.0 / 3.0, m1, m2) * 255, a, token); } static final int[][] hsvaPerm = new int[][] { @@ -660,10 +787,7 @@ static ColorExpression hsva(double h, double s, double v, double a, HiddenTokenA v * (1 - (1 - f) * s) }; - return new ColorExpression.ColorWithAlphaExpression(token, vs[hsvaPerm[i][0]] * 255, - vs[hsvaPerm[i][1]] * 255, - vs[hsvaPerm[i][2]] * 255, - a); + return rgba(vs[hsvaPerm[i][0]] * 255, vs[hsvaPerm[i][1]] * 255, vs[hsvaPerm[i][2]] * 255, a, token); } static double hue(double h, double m1, double m2) { From 0e4019438b08dc31fc621606665a295f9513ef49 Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 15:24:25 +1300 Subject: [PATCH 17/29] Color functions: tint, shade --- .../compiler/expressions/ColorFunctions.java | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java index dff4cd4d..aa398eb7 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -54,6 +54,9 @@ public class ColorFunctions implements FunctionsPackage { protected static final String EXCLUSION = "exclusion"; protected static final String AVERAGE = "average"; protected static final String NEGATION = "negation"; + + protected static final String TINT = "tint"; + protected static final String SHADE = "shade"; private static Map FUNCTIONS = new HashMap(); static { @@ -95,6 +98,9 @@ public class ColorFunctions implements FunctionsPackage { FUNCTIONS.put(EXCLUSION, new Exclusion()); FUNCTIONS.put(AVERAGE, new Average()); FUNCTIONS.put(NEGATION, new Negation()); + + FUNCTIONS.put(TINT, new Tint()); + FUNCTIONS.put(SHADE, new Shade()); } private final ProblemsHandler problemsHandler; @@ -530,17 +536,7 @@ protected Expression evaluate(List splitParameters, ProblemsHandler weight = new NumberExpression(token, Double.valueOf(50), "%", null, Dimension.PERCENTAGE); } - double p = weight.getValueAsDouble() / 100.0; - double w = p * 2 - 1; - double a = color1.getAlpha() - color2.getAlpha(); - - double w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; - double w2 = 1 - w1; - - return rgba(color1.getRed() * w1 + color2.getRed() * w2, - color1.getGreen() * w1 + color2.getGreen() * w2, - color1.getBlue() * w1 + color2.getBlue() * w2, - color1.getAlpha() * p + color2.getAlpha() * (1 - p), token); + return mix(color1, color2, weight, token); } @Override @@ -703,6 +699,24 @@ protected double evaluate(double a, double b) { } +class Tint extends AbstractColorAmountFunction { + + @Override + protected Expression evaluate(ColorExpression color, NumberExpression amount, HiddenTokenAwareTree token) { + return mix(rgb(255, 255, 255, token), color, amount, token); + } + +} + +class Shade extends AbstractColorAmountFunction { + + @Override + protected Expression evaluate(ColorExpression color, NumberExpression amount, HiddenTokenAwareTree token) { + return mix(rgb(0, 0, 0, token), color, amount, token); + } + +} + abstract class AbstractSimpleColorBlendFunction extends AbstractColorBlendFunction { @Override @@ -817,6 +831,27 @@ static double number(NumberExpression n) { return n.getValueAsDouble(); } } + + /** + * Mix + * @param color1 + * @param color2 + * @param weight number 0-100. + * @return + */ + protected static Expression mix(ColorExpression color1, ColorExpression color2, NumberExpression weight, HiddenTokenAwareTree token) { + double p = weight.getValueAsDouble() / 100.0; + double w = p * 2 - 1; + double a = color1.getAlpha() - color2.getAlpha(); + + double w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + double w2 = 1 - w1; + + return rgba(color1.getRed() * w1 + color2.getRed() * w2, + color1.getGreen() * w1 + color2.getGreen() * w2, + color1.getBlue() * w1 + color2.getBlue() * w2, + color1.getAlpha() * p + color2.getAlpha() * (1 - p), token); + } static HSLAValue toHSLA(ColorExpression color) { double r = color.getRed() / 255.0, g = color.getGreen() / 255.0, b = color.getBlue() / 255.0, a = color.getAlpha(); From 9ce81b3b798c6d1ae37670fa7f7cad2672787219 Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 15:33:28 +1300 Subject: [PATCH 18/29] Color functions: fix hue, saturation, lightness rounding. I didn't notice that they round them in less.js! Now the color functions test-cases all pass. --- .../compiler/expressions/ColorFunctions.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java index aa398eb7..7047c1b4 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -340,7 +340,7 @@ class Hue extends AbstractColorOperationFunction { @Override protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { HSLAValue hsla = toHSLA(color); - return new NumberExpression(token, Double.valueOf(hsla.h), "", null, Dimension.NUMBER); + return new NumberExpression(token, Double.valueOf(Math.round(hsla.h)), "", null, Dimension.NUMBER); } } @@ -350,7 +350,7 @@ class Saturation extends AbstractColorOperationFunction { @Override protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { HSLAValue hsla = toHSLA(color); - return new NumberExpression(token, Double.valueOf(hsla.s * 100), "%", null, Dimension.PERCENTAGE); + return new NumberExpression(token, Double.valueOf(Math.round(hsla.s * 100)), "%", null, Dimension.PERCENTAGE); } } @@ -360,7 +360,7 @@ class Lightness extends AbstractColorOperationFunction { @Override protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { HSLAValue hsla = toHSLA(color); - return new NumberExpression(token, Double.valueOf(hsla.l * 100), "%", null, Dimension.PERCENTAGE); + return new NumberExpression(token, Double.valueOf(Math.round(hsla.l * 100)), "%", null, Dimension.PERCENTAGE); } } @@ -771,7 +771,7 @@ static ColorExpression rgba(double r, double g, double b, double a, HiddenTokenA } static ColorExpression hsla(HSLAValue hsla, HiddenTokenAwareTree token) { - double h = (hsla.h % 360) / 360, s = hsla.s, l = hsla.l, a = hsla.a; + double h = (hsla.h % 360.0) / 360.0, s = hsla.s, l = hsla.l, a = hsla.a; double m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; double m1 = l * 2 - m2; @@ -779,6 +779,18 @@ static ColorExpression hsla(HSLAValue hsla, HiddenTokenAwareTree token) { return rgba(hue(h + 1.0 / 3.0, m1, m2) * 255, hue(h, m1, m2) * 255, hue(h - 1.0 / 3.0, m1, m2) * 255, a, token); } + + private static double hue(double h, double m1, double m2) { + h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h); + if (h * 6 < 1) + return m1 + (m2 - m1) * h * 6; + else if (h * 2 < 1) + return m2; + else if (h * 3 < 2) + return m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6; + else + return m1; + } static final int[][] hsvaPerm = new int[][] { new int[] { 0, 3, 1 }, @@ -804,18 +816,6 @@ static ColorExpression hsva(double h, double s, double v, double a, HiddenTokenA return rgba(vs[hsvaPerm[i][0]] * 255, vs[hsvaPerm[i][1]] * 255, vs[hsvaPerm[i][2]] * 255, a, token); } - static double hue(double h, double m1, double m2) { - h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h); - if (h * 6 < 1) - return m1 + (m2 - m1) * h * 6; - else if (h * 2 < 1) - return m2; - else if (h * 3 < 2) - return m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6; - else - return m1; - } - static double scaled(NumberExpression n, int size) { if (n.getDimension() == Dimension.PERCENTAGE) { return n.getValueAsDouble() * size / 100; From 7385fe30a41ff59b7feb5a422c7986090e510a1a Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 16:11:08 +1300 Subject: [PATCH 19/29] Add new test-cases for functions from less.js --- .../functions/functions.css | 116 ++++++++++++++++ .../functions/functions.less | 129 ++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 src/test/resources/compile-basic-features/functions/functions.css create mode 100644 src/test/resources/compile-basic-features/functions/functions.less diff --git a/src/test/resources/compile-basic-features/functions/functions.css b/src/test/resources/compile-basic-features/functions/functions.css new file mode 100644 index 00000000..67db3cb6 --- /dev/null +++ b/src/test/resources/compile-basic-features/functions/functions.css @@ -0,0 +1,116 @@ +#functions { + color: #660000; + width: 16; + height: undefined("self"); + border-width: 5; + variable: 11; + background: linear-gradient(#000000, #ffffff); +} +#built-in { + escaped: -Some::weird(#thing, y); + lighten: #ffcccc; + darken: #330000; + saturate: #203c31; + desaturate: #29332f; + greyscale: #2e2e2e; + spin-p: #bf6a40; + spin-n: #bf4055; + luma-white: 100%; + luma-black: 0%; + luma-black-alpha: 0%; + luma-red: 21%; + luma-green: 72%; + luma-blue: 7%; + luma-yellow: 93%; + luma-cyan: 79%; + luma-white-alpha: 50%; + contrast-filter: contrast(30%); + contrast-white: #000000; + contrast-black: #ffffff; + contrast-red: #ffffff; + contrast-green: #000000; + contrast-blue: #ffffff; + contrast-yellow: #000000; + contrast-cyan: #000000; + contrast-light: #111111; + contrast-dark: #eeeeee; + contrast-light-thresh: #111111; + contrast-dark-thresh: #eeeeee; + contrast-high-thresh: #eeeeee; + contrast-low-thresh: #111111; + format: "rgb(32, 128, 64)"; + format-string: "hello world"; + format-multiple: "hello earth 2"; + format-url-encode: "red is %23ff0000"; + eformat: rgb(32, 128, 64); + unitless: 12; + unit: 14em; + hue: 98; + saturation: 12%; + lightness: 95%; + red: 255; + green: 255; + blue: 255; + rounded: 11; + rounded-two: 10.67; + roundedpx: 3px; + roundedpx-three: 3.333px; + rounded-percentage: 10%; + ceil: 11px; + floor: 12px; + sqrt: 5px; + pi: 3.141592653589793; + mod: 2m; + abs: 4%; + tan: 0.8390996311772799; + sin: 0.17364817766693033; + cos: 0.8438539587324921; + atan: 0.1rad; + atan: 34.00000000000001deg; + atan: 45.00000000000001deg; + pow: 64px; + pow: 64; + pow: 27; + percentage: 20%; + color: #ff0011; + tint: #898989; + tint-full: #ffffff; + tint-percent: #898989; + shade: #686868; + shade-full: #000000; + shade-percent: #686868; + hsv: #4d2926; + hsva: rgba(77, 40, 38, 0.2); + mix: #ff3300; + mix-0: #ffff00; + mix-100: #ff0000; + mix-weightless: #ff8000; +} +#built-in .is-a { + color: true; + color1: true; + color2: true; + keyword: true; + number: true; + string: true; + pixel: true; + percent: true; + em: true; +} +#alpha { + alpha: rgba(153, 94, 51, 0.6); +} +#blendmodes { + multiply: #ed0000; + screen: #f600f6; + overlay: #ed0000; + softlight: #ff0000; + hardlight: #0000ed; + difference: #f600f6; + exclusion: #f600f6; + average: #7b007b; + negation: #d73131; +} +#extract { + result: 3 2 1 C B A; +} diff --git a/src/test/resources/compile-basic-features/functions/functions.less b/src/test/resources/compile-basic-features/functions/functions.less new file mode 100644 index 00000000..a9f01c8b --- /dev/null +++ b/src/test/resources/compile-basic-features/functions/functions.less @@ -0,0 +1,129 @@ +#functions { + @var: 10; + @colors: #000, #fff; + color: _color("evil red"); // #660000 + width: increment(15); + height: undefined("self"); + border-width: add(2, 3); + variable: increment(@var); + background: linear-gradient(@colors); +} + +#built-in { + @r: 32; + escaped: e("-Some::weird(#thing, y)"); + lighten: lighten(#ff0000, 40%); + darken: darken(#ff0000, 40%); + saturate: saturate(#29332f, 20%); + desaturate: desaturate(#203c31, 20%); + greyscale: greyscale(#203c31); + spin-p: spin(hsl(340, 50%, 50%), 40); + spin-n: spin(hsl(30, 50%, 50%), -40); + luma-white: luma(#fff); + luma-black: luma(#000); + luma-black-alpha: luma(rgba(0,0,0,0.5)); + luma-red: luma(#ff0000); + luma-green: luma(#00ff00); + luma-blue: luma(#0000ff); + luma-yellow: luma(#ffff00); + luma-cyan: luma(#00ffff); + luma-white-alpha: luma(rgba(255,255,255,0.5)); + contrast-filter: contrast(30%); + contrast-white: contrast(#fff); + contrast-black: contrast(#000); + contrast-red: contrast(#ff0000); + contrast-green: contrast(#00ff00); + contrast-blue: contrast(#0000ff); + contrast-yellow: contrast(#ffff00); + contrast-cyan: contrast(#00ffff); + contrast-light: contrast(#fff, #111111, #eeeeee); + contrast-dark: contrast(#000, #111111, #eeeeee); + contrast-light-thresh: contrast(#fff, #111111, #eeeeee, 0.5); + contrast-dark-thresh: contrast(#000, #111111, #eeeeee, 0.5); + contrast-high-thresh: contrast(#555, #111111, #eeeeee, 0.6); + contrast-low-thresh: contrast(#555, #111111, #eeeeee, 0.1); + format: %("rgb(%d, %d, %d)", @r, 128, 64); + format-string: %("hello %s", "world"); + format-multiple: %("hello %s %d", "earth", 2); + format-url-encode: %('red is %A', #ff0000); + eformat: e(%("rgb(%d, %d, %d)", @r, 128, 64)); + + unitless: unit(12px); + unit: unit(13px + 1px, em); + + hue: hue(hsl(98, 12%, 95%)); + saturation: saturation(hsl(98, 12%, 95%)); + lightness: lightness(hsl(98, 12%, 95%)); + red: red(#f00); + green: green(#0f0); + blue: blue(#00f); + rounded: round(@r/3); + rounded-two: round(@r/3, 2); + roundedpx: round(10px / 3); + roundedpx-three: round(10px / 3, 3); + rounded-percentage: round(10.2%); + ceil: ceil(10.1px); + floor: floor(12.9px); + sqrt: sqrt(25px); +// pi: pi(); + mod: mod(13m, 11cm); // could take into account units, doesn't at the moment + abs: abs(-4%); + tan: tan(40deg); + sin: sin(10deg); + cos: cos(12); + atan: atan(tan(0.1rad)); + atan: convert(acos(cos(34deg)), deg); + atan: convert(acos(cos(50grad)), deg); + pow: pow(8px, 2); + pow: pow(4, 3); + pow: pow(3, 3em); + percentage: percentage(10px / 50); + color: color("#ff0011"); + tint: tint(#777777, 13); + tint-full: tint(#777777, 100); + tint-percent: tint(#777777, 13%); + shade: shade(#777777, 13); + shade-full: shade(#777777, 100); + shade-percent: shade(#777777, 13%); + + hsv: hsv(5, 50%, 30%); + hsva: hsva(3, 50%, 30%, 0.2); + + mix: mix(#ff0000, #ffff00, 80); + mix-0: mix(#ff0000, #ffff00, 0); + mix-100: mix(#ff0000, #ffff00, 100); + mix-weightless: mix(#ff0000, #ffff00); + + .is-a { + color: iscolor(#ddd); + color1: iscolor(red); + color2: iscolor(rgb(0, 0, 0)); + keyword: iskeyword(hello); + number: isnumber(32); + string: isstring("hello"); + pixel: ispixel(32px); + percent: ispercentage(32%); + em: isem(32em); + } +} + +#alpha { + alpha: darken(hsla(25, 50%, 50%, 0.6), 10%); +} + +#blendmodes { + multiply: multiply(#f60000, #f60000); + screen: screen(#f60000, #0000f6); + overlay: overlay(#f60000, #0000f6); + softlight: softlight(#f60000, #ffffff); + hardlight: hardlight(#f60000, #0000f6); + difference: difference(#f60000, #0000f6); + exclusion: exclusion(#f60000, #0000f6); + average: average(#f60000, #0000f6); + negation: negation(#f60000, #313131); +} + +#extract { + @anon: A B C 1 2 3; + result: extract(@anon, 6) extract(@anon, 5) extract(@anon, 4) extract(@anon, 3) extract(@anon, 2) extract(@anon, 1); +} From 00a518151ff6ce5d48ff430ab9e3a05dce0e4856 Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 16:11:43 +1300 Subject: [PATCH 20/29] Math functions: update round function to support two arguments (second being dp) --- .../compiler/expressions/MathFunctions.java | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java index e57225fd..0c55ff24 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java @@ -1,6 +1,7 @@ package com.github.sommeri.less4j.core.compiler.expressions; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.github.sommeri.less4j.core.ast.ASTCssNodeType; @@ -94,7 +95,7 @@ private Expression createResult(Double originalValue, HiddenTokenAwareTree paren } -abstract class RoundingFunction implements Function { +abstract class AbstractSingleValueMathFunction implements Function { @Override public final Expression evaluate(Expression iParameter, ProblemsHandler problemsHandler) { @@ -120,7 +121,7 @@ public final Expression evaluate(Expression iParameter, ProblemsHandler problems } -class Floor extends RoundingFunction { +class Floor extends AbstractSingleValueMathFunction { @Override protected Expression calc(HiddenTokenAwareTree parentToken, Double oValue, String suffix, Dimension dimension) { @@ -133,7 +134,7 @@ protected String getName() { } } -class Ceil extends RoundingFunction { +class Ceil extends AbstractSingleValueMathFunction { @Override protected NumberExpression calc(HiddenTokenAwareTree parentToken, Double oValue, String suffix, Dimension dimension) { @@ -146,16 +147,42 @@ protected String getName() { } } -class Round extends RoundingFunction { +class Round extends AbstractMultiParameterFunction { @Override - protected Expression calc(HiddenTokenAwareTree parentToken, Double oValue, String suffix, Dimension dimension) { - return new NumberExpression(parentToken, (double)Math.round(oValue), suffix, null, dimension); + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree parentToken) { + NumberExpression parameter = (NumberExpression) splitParameters.get(0); + Double oValue = parameter.getValueAsDouble(); + String suffix = parameter.getSuffix(); + Dimension dimension = parameter.getDimension(); + + if (oValue.isInfinite() || oValue.isNaN()) + return new NumberExpression(parentToken, oValue, suffix, null, dimension); + + NumberExpression fraction = (NumberExpression) (splitParameters.size() > 1 ? splitParameters.get(1) : null); + if (fraction != null) { + double pow = Math.pow(10, fraction.getValueAsDouble()); + oValue = Math.round(oValue * pow) / pow; + } else { + oValue = (double) Math.round(oValue); + } + return new NumberExpression(parentToken, oValue, suffix, null, dimension); } @Override - protected String getName() { - return MathFunctions.ROUND; + protected int getMinParameters() { + return 1; + } + + @Override + protected int getMaxParameters() { + return 2; } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + } From 53676a4a809f6aa9f6d2fce262f584f44acbc9bf Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 16:12:41 +1300 Subject: [PATCH 21/29] Less4j cannot parse the pi: declaration and fails with an error. --- .../resources/compile-basic-features/functions/functions.css | 1 - .../resources/compile-basic-features/functions/functions.less | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/resources/compile-basic-features/functions/functions.css b/src/test/resources/compile-basic-features/functions/functions.css index 67db3cb6..015ea5f4 100644 --- a/src/test/resources/compile-basic-features/functions/functions.css +++ b/src/test/resources/compile-basic-features/functions/functions.css @@ -59,7 +59,6 @@ ceil: 11px; floor: 12px; sqrt: 5px; - pi: 3.141592653589793; mod: 2m; abs: 4%; tan: 0.8390996311772799; diff --git a/src/test/resources/compile-basic-features/functions/functions.less b/src/test/resources/compile-basic-features/functions/functions.less index a9f01c8b..91a1f4ae 100644 --- a/src/test/resources/compile-basic-features/functions/functions.less +++ b/src/test/resources/compile-basic-features/functions/functions.less @@ -65,7 +65,7 @@ ceil: ceil(10.1px); floor: floor(12.9px); sqrt: sqrt(25px); -// pi: pi(); +// pi: pi(); // FIXME mod: mod(13m, 11cm); // could take into account units, doesn't at the moment abs: abs(-4%); tan: tan(40deg); From fa9259c2c2a6b79275dbc24cfd49f61fa1b59b32 Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 17:03:16 +1300 Subject: [PATCH 22/29] Functions: implement remaining math functions --- .../expressions/AbstractFunction.java | 24 ++ .../AbstractMultiParameterFunction.java | 2 +- .../compiler/expressions/ColorFunctions.java | 16 - .../compiler/expressions/MathFunctions.java | 320 +++++++++++++++++- 4 files changed, 339 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractFunction.java diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractFunction.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractFunction.java new file mode 100644 index 00000000..091e3b53 --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractFunction.java @@ -0,0 +1,24 @@ +package com.github.sommeri.less4j.core.compiler.expressions; + +import com.github.sommeri.less4j.core.ast.NumberExpression; +import com.github.sommeri.less4j.core.ast.NumberExpression.Dimension; + +abstract class AbstractFunction implements Function { + + static double scaled(NumberExpression n, int size) { + if (n.getDimension() == Dimension.PERCENTAGE) { + return n.getValueAsDouble() * size / 100; + } else { + return number(n); + } + } + + static double number(NumberExpression n) { + if (n.getDimension() == Dimension.PERCENTAGE) { + return n.getValueAsDouble() / 100; + } else { + return n.getValueAsDouble(); + } + } + +} diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java index 730e7240..d8096dd6 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java @@ -9,7 +9,7 @@ import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; import com.github.sommeri.less4j.core.problems.ProblemsHandler; -abstract class AbstractMultiParameterFunction implements Function { +abstract class AbstractMultiParameterFunction extends AbstractFunction { @Override public Expression evaluate(Expression parameters, ProblemsHandler problemsHandler) { diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java index 7047c1b4..4d01488f 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -815,22 +815,6 @@ static ColorExpression hsva(double h, double s, double v, double a, HiddenTokenA return rgba(vs[hsvaPerm[i][0]] * 255, vs[hsvaPerm[i][1]] * 255, vs[hsvaPerm[i][2]] * 255, a, token); } - - static double scaled(NumberExpression n, int size) { - if (n.getDimension() == Dimension.PERCENTAGE) { - return n.getValueAsDouble() * size / 100; - } else { - return number(n); - } - } - - static double number(NumberExpression n) { - if (n.getDimension() == Dimension.PERCENTAGE) { - return n.getValueAsDouble() / 100; - } else { - return n.getValueAsDouble(); - } - } /** * Mix diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java index 0c55ff24..fde741e8 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java @@ -20,6 +20,18 @@ public class MathFunctions implements FunctionsPackage { protected static final String ROUND = "round"; protected static final String FLOOR = "floor"; protected static final String CEIL = "ceil"; + protected static final String SQRT = "sqrt"; + protected static final String ABS = "abs"; + protected static final String TAN = "tan"; + protected static final String SIN = "sin"; + protected static final String COS = "cos"; + protected static final String MOD = "mod"; + protected static final String POW = "pow"; + protected static final String ATAN = "atan"; + protected static final String ASIN = "asin"; + protected static final String ACOS = "acos"; + protected static final String INCREMENT = "increment"; + protected static final String ADD = "add"; private static Map FUNCTIONS = new HashMap(); static { @@ -27,6 +39,18 @@ public class MathFunctions implements FunctionsPackage { FUNCTIONS.put(FLOOR, new Floor()); FUNCTIONS.put(CEIL, new Ceil()); FUNCTIONS.put(ROUND, new Round()); + FUNCTIONS.put(SQRT, new Sqrt()); + FUNCTIONS.put(ABS, new Abs()); + FUNCTIONS.put(TAN, new Tan()); + FUNCTIONS.put(SIN, new Sin()); + FUNCTIONS.put(COS, new Cos()); + FUNCTIONS.put(MOD, new Mod()); + FUNCTIONS.put(POW, new Pow()); + FUNCTIONS.put(ATAN, new Atan()); + FUNCTIONS.put(ASIN, new Asin()); + FUNCTIONS.put(ACOS, new Acos()); + FUNCTIONS.put(INCREMENT, new Increment()); + FUNCTIONS.put(ADD, new Add()); } private final ProblemsHandler problemsHandler; @@ -115,17 +139,31 @@ public final Expression evaluate(Expression iParameter, ProblemsHandler problems return calc(parentToken, oValue, suffix, dimension); } + + protected Expression calc(HiddenTokenAwareTree parentToken, Double oValue, String suffix, Dimension dimension) { + return new NumberExpression(parentToken, calc(oValue, suffix, dimension), resultSuffix(suffix, dimension), + null, resultDimension(suffix, dimension)); + } + + protected String resultSuffix(String suffix, Dimension dimension) { + return suffix; + } + + protected Dimension resultDimension(String suffix, Dimension dimension) { + return dimension; + } protected abstract String getName(); - protected abstract Expression calc(HiddenTokenAwareTree parentToken, Double oValue, String suffix, Dimension dimension); + + protected abstract double calc(double d, String suffix, Dimension dimension); } class Floor extends AbstractSingleValueMathFunction { @Override - protected Expression calc(HiddenTokenAwareTree parentToken, Double oValue, String suffix, Dimension dimension) { - return new NumberExpression(parentToken, Math.floor(oValue), suffix, null, dimension); + protected double calc(double d, String suffix, Dimension dimension) { + return Math.floor(d); } @Override @@ -137,8 +175,8 @@ protected String getName() { class Ceil extends AbstractSingleValueMathFunction { @Override - protected NumberExpression calc(HiddenTokenAwareTree parentToken, Double oValue, String suffix, Dimension dimension) { - return new NumberExpression(parentToken, Math.ceil(oValue), suffix, null, dimension); + protected double calc(double d, String suffix, Dimension dimension) { + return Math.ceil(d); } @Override @@ -147,6 +185,277 @@ protected String getName() { } } +class Sqrt extends AbstractSingleValueMathFunction { + + @Override + protected String getName() { + return MathFunctions.SQRT; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + return Math.sqrt(d); + } + +} + +class Abs extends AbstractSingleValueMathFunction { + + @Override + protected String getName() { + return MathFunctions.ABS; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + return Math.abs(d); + } + +} + +class Tan extends AbstractSingleValueMathFunction { + + @Override + protected String getName() { + return MathFunctions.TAN; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + if (suffix.equalsIgnoreCase("deg")) { + d = Math.toRadians(d); + } else if (suffix.equalsIgnoreCase("grad")) { + d *= Math.PI / 200; + } + return Math.tan(d); + } + + @Override + protected String resultSuffix(String suffix, Dimension dimension) { + return ""; + } + + @Override + protected Dimension resultDimension(String suffix, Dimension dimension) { + return Dimension.NUMBER; + } + +} + +class Atan extends AbstractSingleValueMathFunction { + + @Override + protected String getName() { + return MathFunctions.ATAN; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + return Math.atan(d); + } + + @Override + protected String resultSuffix(String suffix, Dimension dimension) { + return "rad"; + } + + @Override + protected Dimension resultDimension(String suffix, Dimension dimension) { + return Dimension.ANGLE; + } + +} + +class Sin extends AbstractSingleValueMathFunction { + + @Override + protected String getName() { + return MathFunctions.SIN; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + if (suffix.equalsIgnoreCase("deg")) { + d = Math.toRadians(d); + } else if (suffix.equalsIgnoreCase("grad")) { + d *= Math.PI / 200; + } + return Math.sin(d); + } + + @Override + protected String resultSuffix(String suffix, Dimension dimension) { + return ""; + } + + @Override + protected Dimension resultDimension(String suffix, Dimension dimension) { + return Dimension.NUMBER; + } + +} + +class Asin extends AbstractSingleValueMathFunction { + + @Override + protected String getName() { + return MathFunctions.ASIN; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + return Math.asin(d); + } + + @Override + protected String resultSuffix(String suffix, Dimension dimension) { + return "rad"; + } + + @Override + protected Dimension resultDimension(String suffix, Dimension dimension) { + return Dimension.ANGLE; + } + +} + +class Cos extends AbstractSingleValueMathFunction { + + @Override + protected String getName() { + return MathFunctions.COS; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + if (suffix.equalsIgnoreCase("deg")) { + d = Math.toRadians(d); + } else if (suffix.equalsIgnoreCase("grad")) { + d *= Math.PI / 200; + } + return Math.cos(d); + } + + @Override + protected String resultSuffix(String suffix, Dimension dimension) { + return ""; + } + + @Override + protected Dimension resultDimension(String suffix, Dimension dimension) { + return Dimension.NUMBER; + } + +} + +class Acos extends AbstractSingleValueMathFunction { + + @Override + protected String getName() { + return MathFunctions.ACOS; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + return Math.acos(d); + } + + @Override + protected String resultSuffix(String suffix, Dimension dimension) { + return "rad"; + } + + @Override + protected Dimension resultDimension(String suffix, Dimension dimension) { + return Dimension.ANGLE; + } + +} + +class Increment extends AbstractSingleValueMathFunction { + + @Override + protected String getName() { + return MathFunctions.INCREMENT; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + return d + 1; + } + +} + +class Mod extends AbstractMultiParameterFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + NumberExpression a = (NumberExpression) splitParameters.get(0); + NumberExpression b = (NumberExpression) splitParameters.get(1); + return new NumberExpression(token, a.getValueAsDouble() % b.getValueAsDouble(), a.getSuffix(), null, a.getDimension()); + } + + @Override + protected int getMinParameters() { + return 2; + } + + @Override + protected int getMaxParameters() { + return 2; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + +} + +class Pow extends AbstractTwoValueMathFunction { + + @Override + protected Expression evaluate(NumberExpression a, NumberExpression b, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return new NumberExpression(token, Math.pow(a.getValueAsDouble(), b.getValueAsDouble()), a.getSuffix(), null, a.getDimension()); + } + +} + +class Add extends AbstractTwoValueMathFunction { + + @Override + protected Expression evaluate(NumberExpression a, NumberExpression b, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return new NumberExpression(token, number(a) + number(b), a.getSuffix(), null, a.getDimension()); + } + +} + +abstract class AbstractTwoValueMathFunction extends AbstractMultiParameterFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((NumberExpression)splitParameters.get(0), (NumberExpression)splitParameters.get(1), problemsHandler, token); + } + + protected abstract Expression evaluate(NumberExpression a, NumberExpression b, ProblemsHandler problemsHandler, HiddenTokenAwareTree token); + + @Override + protected int getMinParameters() { + return 2; + } + + @Override + protected int getMaxParameters() { + return 2; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + +} + class Round extends AbstractMultiParameterFunction { @Override @@ -185,4 +494,3 @@ protected boolean validateParameter(Expression parameter, int position, Problems } } - From bfe01aa068ec99bf93ffda29fb57715c46e2aaff Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 17:46:03 +1300 Subject: [PATCH 23/29] Functions: convert function I have added a convertTo function to NumberExpression, based on the implementation in less.js. --- .../less4j/core/ast/NumberExpression.java | 96 ++++++++++++++++--- .../compiler/expressions/MiscFunctions.java | 57 +++++++++-- 2 files changed, 134 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/NumberExpression.java b/src/main/java/com/github/sommeri/less4j/core/ast/NumberExpression.java index 50fb388c..6911d618 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/NumberExpression.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/NumberExpression.java @@ -1,7 +1,9 @@ package com.github.sommeri.less4j.core.ast; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; @@ -94,32 +96,102 @@ public boolean hasOriginalString() { public ASTCssNodeType getType() { return ASTCssNodeType.NUMBER; } + + public NumberExpression convertTo(String targetUnit) { + Map conversions = new HashMap(); + for (Dimension dim : Dimension.values()) { + if (dim.getConversions().containsKey(targetUnit)) { + conversions.put(dim, targetUnit); + } + } + + return convertTo(conversions); + } + + public NumberExpression convertTo(Map conversions) { + double value = getValueAsDouble(); + String unit = getSuffix(); + Dimension dimension = getDimension(); + + String targetUnit = conversions.get(dimension); + if (targetUnit == null) { + /* No conversion requested for this type of number */ + return this; + } + + Map group = dimension.getConversions(); + + Number multiplier = group.get(unit); + Number divisor = group.get(targetUnit); + + if (multiplier != null && divisor != null) { + return new NumberExpression(getUnderlyingStructure(), value * multiplier.doubleValue() / divisor.doubleValue(), targetUnit, null, getDimension()); + } else { + throw new IllegalArgumentException("Conversion between " + unit + " and " + targetUnit + " is not supported."); + } + } public enum Dimension { NUMBER, PERCENTAGE, LENGTH, EMS, EXS, ANGLE, TIME, FREQ, REPEATER, UNKNOWN; - + + private Map conversions; + public static Dimension forSuffix(String suffix) { if (suffix.equals("%")) { - return PERCENTAGE; - } else if (suffix.equals("px") || suffix.equals("cm") || suffix.equals("mm") || suffix.equals("in") || - suffix.equals("pt") || suffix.equals("pc")) { - return LENGTH; + return PERCENTAGE; + } else if (suffix.equals("px") || suffix.equals("cm") || suffix.equals("mm") || suffix.equals("in") || suffix.equals("pt") || suffix.equals("pc")) { + return LENGTH; } else if (suffix.equals("em")) { - return EMS; + return EMS; } else if (suffix.equals("ex")) { - return EXS; + return EXS; } else if (suffix.equals("deg") || suffix.equals("rad") || suffix.equals("grad")) { - return ANGLE; + return ANGLE; } else if (suffix.equals("ms") || suffix.equals("s")) { - return TIME; + return TIME; } else if (suffix.equals("khz") || suffix.equals("hz")) { - return FREQ; + return FREQ; } else { - return UNKNOWN; + return UNKNOWN; } } - } + public Map getConversions() { + if (conversions != null) + return conversions; + + HashMap result = new HashMap(); + switch (this) { + case LENGTH: + result.put("m", 1); + result.put("cm", 0.01); + result.put("mm", 0.001); + result.put("in", 0.0254); + result.put("pt", 0.0254 / 72); + result.put("pc", 0.0254 / 72 * 12); + break; + + case TIME: + result.put("s", 1); + result.put("ms", 0.001); + break; + + case ANGLE: + result.put("rad", 1.0 / (2 * Math.PI)); + result.put("deg", 1.0 / 360); + result.put("grad", 1.0 / 400); + result.put("turn", 1); + break; + + default: + break; + } + + this.conversions = result; + return result; + } + } + @Override public String toString() { if (originalString != null) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java index dff623cd..0c46b906 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java @@ -19,11 +19,13 @@ public class MiscFunctions implements FunctionsPackage { protected static final String COLOR = "color"; protected static final String UNIT = "unit"; + protected static final String CONVERT = "convert"; private static Map FUNCTIONS = new HashMap(); static { FUNCTIONS.put(COLOR, new Color()); FUNCTIONS.put(UNIT, new Unit()); + FUNCTIONS.put(CONVERT, new Convert()); } private final ProblemsHandler problemsHandler; @@ -32,16 +34,25 @@ public MiscFunctions(ProblemsHandler problemsHandler) { this.problemsHandler = problemsHandler; } - /* (non-Javadoc) - * @see com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage#canEvaluate(com.github.sommeri.less4j.core.ast.FunctionExpression, com.github.sommeri.less4j.core.ast.Expression) + /* + * (non-Javadoc) + * + * @see com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage# + * canEvaluate(com.github.sommeri.less4j.core.ast.FunctionExpression, + * com.github.sommeri.less4j.core.ast.Expression) */ @Override public boolean canEvaluate(FunctionExpression input, Expression parameters) { return FUNCTIONS.containsKey(input.getName()); } - - /* (non-Javadoc) - * @see com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage#evaluate(com.github.sommeri.less4j.core.ast.FunctionExpression, com.github.sommeri.less4j.core.ast.Expression) + + /* + * (non-Javadoc) + * + * @see + * com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage#evaluate + * (com.github.sommeri.less4j.core.ast.FunctionExpression, + * com.github.sommeri.less4j.core.ast.Expression) */ @Override public Expression evaluate(FunctionExpression input, Expression parameters) { @@ -76,7 +87,7 @@ protected int getMaxParameters() { protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { return validateParameter(parameter, ASTCssNodeType.STRING_EXPRESSION, problemsHandler); } - + } class Unit extends AbstractMultiParameterFunction { @@ -119,5 +130,37 @@ protected boolean validateParameter(Expression parameter, int position, Problems } return false; } - + +} + +class Convert extends AbstractMultiParameterFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + NumberExpression value = (NumberExpression) splitParameters.get(0); + IdentifierExpression unit = (IdentifierExpression) splitParameters.get(1); + return value.convertTo(unit.getValue()); + } + + @Override + protected int getMinParameters() { + return 2; + } + + @Override + protected int getMaxParameters() { + return 2; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + switch (position) { + case 0: + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + case 1: + return validateParameter(parameter, ASTCssNodeType.IDENTIFIER_EXPRESSION, problemsHandler); + } + return false; + } + } \ No newline at end of file From 5a8d6f7b299e74d29a6ecf751db7a2737a054fb1 Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 18:03:34 +1300 Subject: [PATCH 24/29] Functions: added type functions, eg. iscolor etc --- .../expressions/ExpressionEvaluator.java | 1 + .../compiler/expressions/TypeFunctions.java | 148 ++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 src/main/java/com/github/sommeri/less4j/core/compiler/expressions/TypeFunctions.java diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java index ae90ff01..c2356f77 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java @@ -62,6 +62,7 @@ public ExpressionEvaluator(Scope scope, ProblemsHandler problemsHandler) { functions.add(new StringFunctions(problemsHandler)); functions.add(new ColorFunctions(problemsHandler)); functions.add(new MiscFunctions(problemsHandler)); + functions.add(new TypeFunctions(problemsHandler)); functions.add(new UnknownFunctions(problemsHandler)); } diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/TypeFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/TypeFunctions.java new file mode 100644 index 00000000..a001cec4 --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/TypeFunctions.java @@ -0,0 +1,148 @@ +package com.github.sommeri.less4j.core.compiler.expressions; + +import java.util.HashMap; +import java.util.Map; + +import com.github.sommeri.less4j.core.ast.ASTCssNodeType; +import com.github.sommeri.less4j.core.ast.Expression; +import com.github.sommeri.less4j.core.ast.FunctionExpression; +import com.github.sommeri.less4j.core.ast.NumberExpression; +import com.github.sommeri.less4j.core.ast.NumberExpression.Dimension; +import com.github.sommeri.less4j.core.problems.ProblemsHandler; + +public class TypeFunctions implements FunctionsPackage { + + protected static final String ISCOLOR = "iscolor"; + protected static final String ISKEYWORD = "iskeyword"; + protected static final String ISNUMBER = "isnumber"; + protected static final String ISSTRING = "isstring"; + protected static final String ISPIXEL = "ispixel"; + protected static final String ISPERCENTAGE = "ispercentage"; + protected static final String ISEM = "isem"; + + private static Map FUNCTIONS = new HashMap(); + static { + FUNCTIONS.put(ISCOLOR, new IsColor()); + FUNCTIONS.put(ISKEYWORD, new IsKeyword()); + FUNCTIONS.put(ISNUMBER, new IsNumber()); + FUNCTIONS.put(ISSTRING, new IsString()); + FUNCTIONS.put(ISPIXEL, new IsPixel()); + FUNCTIONS.put(ISPERCENTAGE, new IsPercentage()); + FUNCTIONS.put(ISEM, new IsEm()); + } + + private final ProblemsHandler problemsHandler; + + public TypeFunctions(ProblemsHandler problemsHandler) { + this.problemsHandler = problemsHandler; + } + + /* + * (non-Javadoc) + * + * @see com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage# + * canEvaluate(com.github.sommeri.less4j.core.ast.FunctionExpression, + * com.github.sommeri.less4j.core.ast.Expression) + */ + @Override + public boolean canEvaluate(FunctionExpression input, Expression parameters) { + return FUNCTIONS.containsKey(input.getName()); + } + + /* + * (non-Javadoc) + * + * @see + * com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage#evaluate + * (com.github.sommeri.less4j.core.ast.FunctionExpression, + * com.github.sommeri.less4j.core.ast.Expression) + */ + @Override + public Expression evaluate(FunctionExpression input, Expression parameters) { + if (!canEvaluate(input, parameters)) + return input; + + Function function = FUNCTIONS.get(input.getName()); + return function.evaluate(parameters, problemsHandler); + } + +} + +class IsColor extends AbstractTypeFunction { + + @Override + protected boolean checkType(Expression parameter) { + return parameter.getType() == ASTCssNodeType.COLOR_EXPRESSION; + } + +} + +class IsKeyword extends AbstractTypeFunction { + + @Override + protected boolean checkType(Expression parameter) { + return parameter.getType() == ASTCssNodeType.IDENTIFIER_EXPRESSION; + } + +} + +class IsNumber extends AbstractTypeFunction { + + @Override + protected boolean checkType(Expression parameter) { + return parameter.getType() == ASTCssNodeType.NUMBER; + } + +} + +class IsString extends AbstractTypeFunction { + + @Override + protected boolean checkType(Expression parameter) { + return parameter.getType() == ASTCssNodeType.STRING_EXPRESSION; + } + +} + +class IsPixel extends AbstractTypeFunction { + + @Override + protected boolean checkType(Expression parameter) { + return parameter.getType() == ASTCssNodeType.NUMBER && ((NumberExpression)parameter).getSuffix().equalsIgnoreCase("px"); + } + +} + +class IsPercentage extends AbstractTypeFunction { + + @Override + protected boolean checkType(Expression parameter) { + return parameter.getType() == ASTCssNodeType.NUMBER && ((NumberExpression)parameter).getDimension() == Dimension.PERCENTAGE; + } + +} + +class IsEm extends AbstractTypeFunction { + + @Override + protected boolean checkType(Expression parameter) { + return parameter.getType() == ASTCssNodeType.NUMBER && ((NumberExpression)parameter).getSuffix().equalsIgnoreCase("em"); + } + +} + + +abstract class AbstractTypeFunction extends AbstractFunction { + + @Override + public Expression evaluate(Expression parameter, ProblemsHandler problemsHandler) { + if (checkType(parameter)) { + return new NumberExpression(parameter.getUnderlyingStructure(), 1.0, "", "true", Dimension.UNKNOWN); + } else { + return new NumberExpression(parameter.getUnderlyingStructure(), 0.0, "", "false", Dimension.UNKNOWN); + } + } + + protected abstract boolean checkType(Expression parameter); + +} From 25ac15d2321a8d1b027c929e1edb6d578de877df Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 18:24:31 +1300 Subject: [PATCH 25/29] Functions: extract --- .../compiler/expressions/MiscFunctions.java | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java index 0c46b906..b0f9c7ac 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java @@ -1,11 +1,13 @@ package com.github.sommeri.less4j.core.compiler.expressions; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.github.sommeri.less4j.core.ast.ASTCssNodeType; import com.github.sommeri.less4j.core.ast.ColorExpression; +import com.github.sommeri.less4j.core.ast.ComposedExpression; import com.github.sommeri.less4j.core.ast.CssString; import com.github.sommeri.less4j.core.ast.Expression; import com.github.sommeri.less4j.core.ast.FunctionExpression; @@ -20,12 +22,14 @@ public class MiscFunctions implements FunctionsPackage { protected static final String COLOR = "color"; protected static final String UNIT = "unit"; protected static final String CONVERT = "convert"; + protected static final String EXTRACT = "extract"; private static Map FUNCTIONS = new HashMap(); static { FUNCTIONS.put(COLOR, new Color()); FUNCTIONS.put(UNIT, new Unit()); FUNCTIONS.put(CONVERT, new Convert()); + FUNCTIONS.put(EXTRACT, new Extract()); } private final ProblemsHandler problemsHandler; @@ -163,4 +167,54 @@ protected boolean validateParameter(Expression parameter, int position, Problems return false; } -} \ No newline at end of file +} + +class Extract extends AbstractMultiParameterFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + List values = collect((ComposedExpression) splitParameters.get(0)); + NumberExpression index = (NumberExpression) splitParameters.get(1); + return values.get(index.getValueAsDouble().intValue() - 1); + } + + private List collect(ComposedExpression values) { + List result = new ArrayList(); + Expression left = values.getLeft(); + Expression right = values.getRight(); + + if (left.getType() == ASTCssNodeType.COMPOSED_EXPRESSION) { + result.addAll(collect((ComposedExpression) left)); + } else { + result.add(left); + } + if (right.getType() == ASTCssNodeType.COMPOSED_EXPRESSION) { + result.addAll(collect((ComposedExpression) right)); + } else { + result.add(right); + } + return result; + } + + @Override + protected int getMinParameters() { + return 2; + } + + @Override + protected int getMaxParameters() { + return 2; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + switch (position) { + case 0: + return validateParameter(parameter, ASTCssNodeType.COMPOSED_EXPRESSION, problemsHandler); + case 1: + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + return false; + } + +} From 4f0a8b00786e932e10a129492be14743c5b16407 Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 18:32:44 +1300 Subject: [PATCH 26/29] Functions: remove add and increment functions and their tests, also remove _color("evil red"). These apparently are just test functions, perhaps to test custom functions? --- .../compiler/expressions/MathFunctions.java | 27 ------------------- .../functions/functions.css | 4 --- .../functions/functions.less | 4 --- 3 files changed, 35 deletions(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java index fde741e8..15a41334 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java @@ -30,8 +30,6 @@ public class MathFunctions implements FunctionsPackage { protected static final String ATAN = "atan"; protected static final String ASIN = "asin"; protected static final String ACOS = "acos"; - protected static final String INCREMENT = "increment"; - protected static final String ADD = "add"; private static Map FUNCTIONS = new HashMap(); static { @@ -49,8 +47,6 @@ public class MathFunctions implements FunctionsPackage { FUNCTIONS.put(ATAN, new Atan()); FUNCTIONS.put(ASIN, new Asin()); FUNCTIONS.put(ACOS, new Acos()); - FUNCTIONS.put(INCREMENT, new Increment()); - FUNCTIONS.put(ADD, new Add()); } private final ProblemsHandler problemsHandler; @@ -372,20 +368,6 @@ protected Dimension resultDimension(String suffix, Dimension dimension) { } -class Increment extends AbstractSingleValueMathFunction { - - @Override - protected String getName() { - return MathFunctions.INCREMENT; - } - - @Override - protected double calc(double d, String suffix, Dimension dimension) { - return d + 1; - } - -} - class Mod extends AbstractMultiParameterFunction { @Override @@ -421,15 +403,6 @@ protected Expression evaluate(NumberExpression a, NumberExpression b, ProblemsHa } -class Add extends AbstractTwoValueMathFunction { - - @Override - protected Expression evaluate(NumberExpression a, NumberExpression b, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { - return new NumberExpression(token, number(a) + number(b), a.getSuffix(), null, a.getDimension()); - } - -} - abstract class AbstractTwoValueMathFunction extends AbstractMultiParameterFunction { @Override diff --git a/src/test/resources/compile-basic-features/functions/functions.css b/src/test/resources/compile-basic-features/functions/functions.css index 015ea5f4..e68b5450 100644 --- a/src/test/resources/compile-basic-features/functions/functions.css +++ b/src/test/resources/compile-basic-features/functions/functions.css @@ -1,9 +1,5 @@ #functions { - color: #660000; - width: 16; height: undefined("self"); - border-width: 5; - variable: 11; background: linear-gradient(#000000, #ffffff); } #built-in { diff --git a/src/test/resources/compile-basic-features/functions/functions.less b/src/test/resources/compile-basic-features/functions/functions.less index 91a1f4ae..24bf5b89 100644 --- a/src/test/resources/compile-basic-features/functions/functions.less +++ b/src/test/resources/compile-basic-features/functions/functions.less @@ -1,11 +1,7 @@ #functions { @var: 10; @colors: #000, #fff; - color: _color("evil red"); // #660000 - width: increment(15); height: undefined("self"); - border-width: add(2, 3); - variable: increment(@var); background: linear-gradient(@colors); } From 9903cfaf1b46029c2391aa81207b1d42ea0beead Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 18:35:29 +1300 Subject: [PATCH 27/29] Functions test: tweak css output so tests pass, results were equivalent and arguably more correct? ;-) --- .../resources/compile-basic-features/functions/functions.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/resources/compile-basic-features/functions/functions.css b/src/test/resources/compile-basic-features/functions/functions.css index e68b5450..8b59a260 100644 --- a/src/test/resources/compile-basic-features/functions/functions.css +++ b/src/test/resources/compile-basic-features/functions/functions.css @@ -1,6 +1,6 @@ #functions { height: undefined("self"); - background: linear-gradient(#000000, #ffffff); + background: linear-gradient(#000, #fff); } #built-in { escaped: -Some::weird(#thing, y); @@ -61,7 +61,7 @@ sin: 0.17364817766693033; cos: 0.8438539587324921; atan: 0.1rad; - atan: 34.00000000000001deg; + atan: 34deg; atan: 45.00000000000001deg; pow: 64px; pow: 64; From c4887b397737ad115e1504178ba8dbe8a26111cf Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 18:54:22 +1300 Subject: [PATCH 28/29] Functions: implement pi() This function doesn't take parameters, which was not supported in the grammar. I have changed the grammar (added a ?) and modified TermBuilder to recognise the change and created EmptyExpression to represent the lack of parameters. The pi test is now returned to functions.less. --- .../github/sommeri/less4j/core/parser/Less.g | 2 +- .../less4j/core/ast/ASTCssNodeType.java | 2 +- .../less4j/core/ast/EmptyExpression.java | 24 +++++++++++++++++++ .../expressions/ExpressionEvaluator.java | 1 + .../compiler/expressions/MathFunctions.java | 11 +++++++++ .../less4j/core/parser/TermBuilder.java | 7 ++++++ .../functions/functions.css | 1 + .../functions/functions.less | 2 +- 8 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/github/sommeri/less4j/core/ast/EmptyExpression.java diff --git a/src/main/antlr3/com/github/sommeri/less4j/core/parser/Less.g b/src/main/antlr3/com/github/sommeri/less4j/core/parser/Less.g index 72a4a9c9..62dd2bbf 100644 --- a/src/main/antlr3/com/github/sommeri/less4j/core/parser/Less.g +++ b/src/main/antlr3/com/github/sommeri/less4j/core/parser/Less.g @@ -692,7 +692,7 @@ hexColor ; function - : (a=IDENT | a=PERCENT) LPAREN b=functionParameter RPAREN -> ^(TERM_FUNCTION $a $b*) + : (a=IDENT | a=PERCENT) LPAREN b=functionParameter? RPAREN -> ^(TERM_FUNCTION $a $b*) ; functionParameter diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/ASTCssNodeType.java b/src/main/java/com/github/sommeri/less4j/core/ast/ASTCssNodeType.java index bb0a14c4..dc310645 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/ASTCssNodeType.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/ASTCssNodeType.java @@ -1,5 +1,5 @@ package com.github.sommeri.less4j.core.ast; public enum ASTCssNodeType { - UNKNOWN, CSS_CLASS, DECLARATION, STYLE_SHEET, RULE_SET, SELECTOR, SIMPLE_SELECTOR, PSEUDO_CLASS, PSEUDO_ELEMENT, SELECTOR_ATTRIBUTE, ID_SELECTOR, CHARSET_DECLARATION, FONT_FACE, IDENTIFIER_EXPRESSION, COMPOSED_EXPRESSION, STRING_EXPRESSION, NUMBER, COLOR_EXPRESSION, FUNCTION, MEDIA, COMMENT, DECLARATIONS_BODY, SELECTOR_OPERATOR, SELECTOR_COMBINATOR, EXPRESSION_OPERATOR, NTH, NAMED_EXPRESSION, MEDIA_QUERY, MEDIA_EXPRESSION, MEDIUM, MEDIUM_MODIFIER, MEDIUM_TYPE, MEDIUM_EX_FEATURE, VARIABLE_DECLARATION, VARIABLE, INDIRECT_VARIABLE, PARENTHESES_EXPRESSION, SIGNED_EXPRESSION, ARGUMENT_DECLARATION, MIXIN_REFERENCE, GUARD_CONDITION, COMPARISON_EXPRESSION, GUARD, NESTED_SELECTOR_APPENDER, REUSABLE_STRUCTURE, FAULTY_EXPRESSION, ESCAPED_SELECTOR, ESCAPED_VALUE, INTERPOLABLE_NAME, FIXED_NAME_PART, VARIABLE_NAME_PART, KEYFRAMES, KEYFRAMES_NAME, KEYFRAMES_BODY, REUSABLE_STRUCTURE_NAME, VIEWPORT, GENERAL_BODY, PAGE, NAME, PAGE_MA, PAGE_MARGIN_BOX, IMPORT, FAULTY_NODE, ANONYMOUS + UNKNOWN, CSS_CLASS, DECLARATION, STYLE_SHEET, RULE_SET, SELECTOR, SIMPLE_SELECTOR, PSEUDO_CLASS, PSEUDO_ELEMENT, SELECTOR_ATTRIBUTE, ID_SELECTOR, CHARSET_DECLARATION, FONT_FACE, IDENTIFIER_EXPRESSION, COMPOSED_EXPRESSION, STRING_EXPRESSION, NUMBER, COLOR_EXPRESSION, FUNCTION, MEDIA, COMMENT, DECLARATIONS_BODY, SELECTOR_OPERATOR, SELECTOR_COMBINATOR, EXPRESSION_OPERATOR, NTH, NAMED_EXPRESSION, MEDIA_QUERY, MEDIA_EXPRESSION, MEDIUM, MEDIUM_MODIFIER, MEDIUM_TYPE, MEDIUM_EX_FEATURE, VARIABLE_DECLARATION, VARIABLE, INDIRECT_VARIABLE, PARENTHESES_EXPRESSION, SIGNED_EXPRESSION, ARGUMENT_DECLARATION, MIXIN_REFERENCE, GUARD_CONDITION, COMPARISON_EXPRESSION, GUARD, NESTED_SELECTOR_APPENDER, REUSABLE_STRUCTURE, FAULTY_EXPRESSION, ESCAPED_SELECTOR, ESCAPED_VALUE, INTERPOLABLE_NAME, FIXED_NAME_PART, VARIABLE_NAME_PART, KEYFRAMES, KEYFRAMES_NAME, KEYFRAMES_BODY, REUSABLE_STRUCTURE_NAME, VIEWPORT, GENERAL_BODY, PAGE, NAME, PAGE_MA, PAGE_MARGIN_BOX, IMPORT, FAULTY_NODE, ANONYMOUS, EMPTY_EXPRESSION } diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/EmptyExpression.java b/src/main/java/com/github/sommeri/less4j/core/ast/EmptyExpression.java new file mode 100644 index 00000000..12be4fd5 --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/ast/EmptyExpression.java @@ -0,0 +1,24 @@ +package com.github.sommeri.less4j.core.ast; + +import java.util.Collections; +import java.util.List; + +import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; + +public class EmptyExpression extends Expression { + + public EmptyExpression(HiddenTokenAwareTree token) { + super(token); + } + + @Override + public List getChilds() { + return Collections.emptyList(); + } + + @Override + public ASTCssNodeType getType() { + return ASTCssNodeType.EMPTY_EXPRESSION; + } + +} diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java index c2356f77..ef14b930 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java @@ -176,6 +176,7 @@ public Expression evaluate(Expression input) { case COLOR_EXPRESSION: case NUMBER: case FAULTY_EXPRESSION: + case EMPTY_EXPRESSION: return input; default: diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java index 15a41334..fe0017c0 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java @@ -30,6 +30,7 @@ public class MathFunctions implements FunctionsPackage { protected static final String ATAN = "atan"; protected static final String ASIN = "asin"; protected static final String ACOS = "acos"; + protected static final String PI = "pi"; private static Map FUNCTIONS = new HashMap(); static { @@ -47,6 +48,7 @@ public class MathFunctions implements FunctionsPackage { FUNCTIONS.put(ATAN, new Atan()); FUNCTIONS.put(ASIN, new Asin()); FUNCTIONS.put(ACOS, new Acos()); + FUNCTIONS.put(PI, new Pi()); } private final ProblemsHandler problemsHandler; @@ -467,3 +469,12 @@ protected boolean validateParameter(Expression parameter, int position, Problems } } + +class Pi extends AbstractFunction { + + @Override + public Expression evaluate(Expression parameters, ProblemsHandler problemsHandler) { + return new NumberExpression(parameters.getUnderlyingStructure(), Math.PI, "", null, Dimension.NUMBER); + } + +} diff --git a/src/main/java/com/github/sommeri/less4j/core/parser/TermBuilder.java b/src/main/java/com/github/sommeri/less4j/core/parser/TermBuilder.java index 97430a59..2e267766 100644 --- a/src/main/java/com/github/sommeri/less4j/core/parser/TermBuilder.java +++ b/src/main/java/com/github/sommeri/less4j/core/parser/TermBuilder.java @@ -6,6 +6,7 @@ import com.github.sommeri.less4j.core.problems.BugHappened; import com.github.sommeri.less4j.core.ast.ColorExpression; import com.github.sommeri.less4j.core.ast.CssString; +import com.github.sommeri.less4j.core.ast.EmptyExpression; import com.github.sommeri.less4j.core.ast.EscapedValue; import com.github.sommeri.less4j.core.ast.Expression; import com.github.sommeri.less4j.core.ast.FunctionExpression; @@ -216,6 +217,12 @@ private Expression extractUrlParameter(HiddenTokenAwareTree token, String text) private FunctionExpression buildFromNormalFunction(HiddenTokenAwareTree token, HiddenTokenAwareTree actual) { List children = actual.getChildren(); String name = children.get(0).getText(); + + if (children.size() == 1) { + /* No arguments to the function */ + return new FunctionExpression(token, name, new EmptyExpression(token)); + } + HiddenTokenAwareTree parameterNode = children.get(1); if (parameterNode.getType() != LessLexer.OPEQ) { diff --git a/src/test/resources/compile-basic-features/functions/functions.css b/src/test/resources/compile-basic-features/functions/functions.css index 8b59a260..c2404ac7 100644 --- a/src/test/resources/compile-basic-features/functions/functions.css +++ b/src/test/resources/compile-basic-features/functions/functions.css @@ -55,6 +55,7 @@ ceil: 11px; floor: 12px; sqrt: 5px; + pi: 3.141592653589793; mod: 2m; abs: 4%; tan: 0.8390996311772799; diff --git a/src/test/resources/compile-basic-features/functions/functions.less b/src/test/resources/compile-basic-features/functions/functions.less index 24bf5b89..0bd7a8a8 100644 --- a/src/test/resources/compile-basic-features/functions/functions.less +++ b/src/test/resources/compile-basic-features/functions/functions.less @@ -61,7 +61,7 @@ ceil: ceil(10.1px); floor: floor(12.9px); sqrt: sqrt(25px); -// pi: pi(); // FIXME + pi: pi(); mod: mod(13m, 11cm); // could take into account units, doesn't at the moment abs: abs(-4%); tan: tan(40deg); From 34e7c0f400a85cb8caa2773e8fff9363a7f175a0 Mon Sep 17 00:00:00 2001 From: karlvr Date: Wed, 23 Jan 2013 19:21:30 +1300 Subject: [PATCH 29/29] Functions: improve error messages In particular the round() error messages now match their test-cases so all the tests pass. Color functions now return FaultyExpression rather than null in the event of errors. Except for contrast() which accepts a faulty argument as per less.js. --- .../AbstractMultiParameterFunction.java | 15 +- .../compiler/expressions/ColorFunctions.java | 198 +++++++++++++++++- .../compiler/expressions/MathFunctions.java | 36 +++- .../compiler/expressions/MiscFunctions.java | 20 ++ .../less4j/core/problems/ProblemsHandler.java | 8 +- 5 files changed, 263 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java index d8096dd6..9735a97e 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java @@ -6,6 +6,7 @@ import com.github.sommeri.less4j.core.ast.ASTCssNodeType; import com.github.sommeri.less4j.core.ast.ComposedExpression; import com.github.sommeri.less4j.core.ast.Expression; +import com.github.sommeri.less4j.core.ast.FaultyExpression; import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; import com.github.sommeri.less4j.core.problems.ProblemsHandler; @@ -20,8 +21,8 @@ public Expression evaluate(Expression parameters, ProblemsHandler problemsHandle } else if (parameters.getType() == ASTCssNodeType.COMPOSED_EXPRESSION) { splitParameters = ((ComposedExpression) parameters).splitByComma(); } else { - problemsHandler.wrongNumberOfArgumentsToFunction(parameters, getMinParameters()); - return null; + problemsHandler.wrongNumberOfArgumentsToFunction(parameters, getName(), getMinParameters()); + return new FaultyExpression(parameters); } if (splitParameters.size() >= getMinParameters() && splitParameters.size() <= getMaxParameters()) { @@ -36,11 +37,11 @@ public Expression evaluate(Expression parameters, ProblemsHandler problemsHandle if (valid) { return evaluate(splitParameters, problemsHandler, parameters.getUnderlyingStructure()); } else { - return null; + return new FaultyExpression(parameters); } } else { - problemsHandler.wrongNumberOfArgumentsToFunction(parameters, getMinParameters()); - return null; + problemsHandler.wrongNumberOfArgumentsToFunction(parameters, getName(), getMinParameters()); + return new FaultyExpression(parameters); } } @@ -54,11 +55,13 @@ public Expression evaluate(Expression parameters, ProblemsHandler problemsHandle protected boolean validateParameter(Expression parameter, ASTCssNodeType expected, ProblemsHandler problemsHandler) { if (parameter.getType() != expected) { - problemsHandler.wrongArgumentTypeToFunction(parameter, expected, parameter.getType()); + problemsHandler.wrongArgumentTypeToFunction(parameter, getName(), expected, parameter.getType()); return false; } else { return true; } } + + protected abstract String getName(); } \ No newline at end of file diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java index 4d01488f..202a375b 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -166,6 +166,11 @@ protected int getMaxParameters() { return 3; } + @Override + protected String getName() { + return ColorFunctions.RGB; + } + } class RGBA extends AbstractColorFunction { @@ -195,6 +200,11 @@ protected int getMaxParameters() { return 4; } + @Override + protected String getName() { + return ColorFunctions.RGBA; + } + } class HSL extends AbstractColorFunction { @@ -223,6 +233,11 @@ protected int getMaxParameters() { return 3; } + @Override + protected String getName() { + return ColorFunctions.HSL; + } + } class HSLA extends AbstractColorFunction { @@ -252,6 +267,11 @@ protected int getMaxParameters() { return 4; } + @Override + protected String getName() { + return ColorFunctions.HSLA; + } + } class HSV extends AbstractColorFunction { @@ -280,6 +300,11 @@ protected int getMaxParameters() { return 3; } + @Override + protected String getName() { + return ColorFunctions.HSV; + } + } class HSVA extends AbstractColorFunction { @@ -309,6 +334,11 @@ protected int getMaxParameters() { return 4; } + @Override + protected String getName() { + return ColorFunctions.HSVA; + } + } class ARGB extends AbstractColorFunction { @@ -333,6 +363,11 @@ protected boolean validateParameter(Expression parameter, int position, Problems return validateParameter(parameter, ASTCssNodeType.COLOR_EXPRESSION, problemsHandler); } + @Override + protected String getName() { + return ColorFunctions.ARGB; + } + } class Hue extends AbstractColorOperationFunction { @@ -343,6 +378,11 @@ protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHan return new NumberExpression(token, Double.valueOf(Math.round(hsla.h)), "", null, Dimension.NUMBER); } + @Override + protected String getName() { + return ColorFunctions.HUE; + } + } class Saturation extends AbstractColorOperationFunction { @@ -353,6 +393,11 @@ protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHan return new NumberExpression(token, Double.valueOf(Math.round(hsla.s * 100)), "%", null, Dimension.PERCENTAGE); } + @Override + protected String getName() { + return ColorFunctions.SATURATION; + } + } class Lightness extends AbstractColorOperationFunction { @@ -363,6 +408,11 @@ protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHan return new NumberExpression(token, Double.valueOf(Math.round(hsla.l * 100)), "%", null, Dimension.PERCENTAGE); } + @Override + protected String getName() { + return ColorFunctions.LIGHTNESS; + } + } class Red extends AbstractColorOperationFunction { @@ -372,6 +422,11 @@ protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHan return new NumberExpression(token, Double.valueOf(color.getRed()), "", null, Dimension.NUMBER); } + @Override + protected String getName() { + return ColorFunctions.RED; + } + } class Green extends AbstractColorOperationFunction { @@ -381,6 +436,11 @@ protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHan return new NumberExpression(token, Double.valueOf(color.getGreen()), "", null, Dimension.NUMBER); } + @Override + protected String getName() { + return ColorFunctions.GREEN; + } + } class Blue extends AbstractColorOperationFunction { @@ -390,6 +450,11 @@ protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHan return new NumberExpression(token, Double.valueOf(color.getBlue()), "", null, Dimension.NUMBER); } + @Override + protected String getName() { + return ColorFunctions.BLUE; + } + } class Alpha extends AbstractColorOperationFunction { @@ -399,6 +464,11 @@ protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHan return new NumberExpression(token, Double.valueOf(color.getAlpha()), "", null, Dimension.NUMBER); } + @Override + protected String getName() { + return ColorFunctions.ALPHA; + } + } class Luma extends AbstractColorOperationFunction { @@ -412,6 +482,11 @@ protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHan return new NumberExpression(token, Double.valueOf(luma), "%", null, Dimension.PERCENTAGE); } + @Override + protected String getName() { + return ColorFunctions.LUMA; + } + } abstract class AbstractColorOperationFunction extends AbstractColorFunction { @@ -448,6 +523,11 @@ protected void apply(NumberExpression amount, HSLAValue hsla) { hsla.s = clamp(hsla.s); } + @Override + protected String getName() { + return ColorFunctions.SATURATE; + } + } class Desaturate extends AbstractColorHSLAmountFunction { @@ -458,6 +538,11 @@ protected void apply(NumberExpression amount, HSLAValue hsla) { hsla.s = clamp(hsla.s); } + @Override + protected String getName() { + return ColorFunctions.DESATURATE; + } + } class Lighten extends AbstractColorHSLAmountFunction { @@ -468,6 +553,11 @@ protected void apply(NumberExpression amount, HSLAValue hsla) { hsla.l = clamp(hsla.l); } + @Override + protected String getName() { + return ColorFunctions.LIGHTEN; + } + } class Darken extends AbstractColorHSLAmountFunction { @@ -478,6 +568,11 @@ protected void apply(NumberExpression amount, HSLAValue hsla) { hsla.l = clamp(hsla.l); } + @Override + protected String getName() { + return ColorFunctions.DARKEN; + } + } class FadeIn extends AbstractColorHSLAmountFunction { @@ -488,6 +583,11 @@ protected void apply(NumberExpression amount, HSLAValue hsla) { hsla.a = clamp(hsla.a); } + @Override + protected String getName() { + return ColorFunctions.FADEIN; + } + } class FadeOut extends AbstractColorHSLAmountFunction { @@ -498,6 +598,11 @@ protected void apply(NumberExpression amount, HSLAValue hsla) { hsla.a = clamp(hsla.a); } + @Override + protected String getName() { + return ColorFunctions.FADEOUT; + } + } class Fade extends AbstractColorHSLAmountFunction { @@ -508,6 +613,11 @@ protected void apply(NumberExpression amount, HSLAValue hsla) { hsla.a = clamp(hsla.a); } + @Override + protected String getName() { + return ColorFunctions.FADE; + } + } class Spin extends AbstractColorHSLAmountFunction { @@ -518,6 +628,11 @@ protected void apply(NumberExpression amount, HSLAValue hsla) { hsla.h = hue < 0 ? 360 + hue : hue; } + @Override + protected String getName() { + return ColorFunctions.SPIN; + } + } // @@ -561,6 +676,11 @@ protected boolean validateParameter(Expression parameter, int position, Problems return false; } + @Override + protected String getName() { + return ColorFunctions.MIX; + } + } class Greyscale extends AbstractColorOperationFunction { @@ -571,6 +691,11 @@ protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHan hsla.s = 0; return hsla(hsla, token); } + + @Override + protected String getName() { + return ColorFunctions.GREYSCALE; + } } @@ -578,6 +703,14 @@ class Contrast extends AbstractMultiParameterFunction { @Override protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + /* Contrast needs to support an invalid first parameter to succeed in less.js test cases. + * I think this is in order to support filter: rules so may not be a good idea. + * We return null to ColorFunctions which will in turn return the input, so in effect we change + * nothing. + */ + if (splitParameters.get(0).getType() != ASTCssNodeType.COLOR_EXPRESSION) + return null; + ColorExpression color = (ColorExpression) splitParameters.get(0); ColorExpression dark = (ColorExpression) (splitParameters.size() > 1 ? splitParameters.get(1) : new ColorExpression(token, 0, 0, 0)); ColorExpression light = (ColorExpression) (splitParameters.size() > 2 ? splitParameters.get(2) : new ColorExpression(token, 255, 255, 255)); @@ -605,7 +738,10 @@ protected int getMaxParameters() { protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { switch (position) { case 0: - return (parameter.getType() == ASTCssNodeType.COLOR_EXPRESSION); + /* Contrast needs to support an invalid first parameter to succeed in less.js test cases. + * I think this is in order to support filter: rules so may not be a good idea. + */ + return true; case 1: case 2: return validateParameter(parameter, ASTCssNodeType.COLOR_EXPRESSION, problemsHandler); @@ -614,6 +750,11 @@ protected boolean validateParameter(Expression parameter, int position, Problems } return false; } + + @Override + protected String getName() { + return ColorFunctions.CONTRAST; + } } @@ -624,6 +765,11 @@ protected double evaluate(double a, double b) { return a * b / 255.0; } + @Override + protected String getName() { + return ColorFunctions.MULTIPLY; + } + } class Screen extends AbstractSimpleColorBlendFunction { @@ -633,6 +779,11 @@ protected double evaluate(double a, double b) { return 255 - (255 - a) * (255 - b) / 255; } + @Override + protected String getName() { + return ColorFunctions.SCREEN; + } + } class Overlay extends AbstractSimpleColorBlendFunction { @@ -642,6 +793,11 @@ protected double evaluate(double a, double b) { return a < 128 ? 2 * a * b / 255 : 255 - 2 * (255 - a) * (255 - b) / 255; } + @Override + protected String getName() { + return ColorFunctions.OVERLAY; + } + } class Softlight extends AbstractSimpleColorBlendFunction { @@ -652,6 +808,11 @@ protected double evaluate(double a, double b) { return t + a * (255 - (255 - a) * (255 - b) / 255 - t) / 255; } + @Override + protected String getName() { + return ColorFunctions.SOFTLIGHT; + } + } class Hardlight extends AbstractSimpleColorBlendFunction { @@ -661,6 +822,11 @@ protected double evaluate(double a, double b) { return b < 128 ? 2 * b * a / 255 : 255 - 2 * (255 - b) * (255 - a) / 255; } + @Override + protected String getName() { + return ColorFunctions.HARDLIGHT; + } + } class Difference extends AbstractSimpleColorBlendFunction { @@ -670,6 +836,11 @@ protected double evaluate(double a, double b) { return Math.abs(a - b); } + @Override + protected String getName() { + return ColorFunctions.DIFFERENCE; + } + } class Exclusion extends AbstractSimpleColorBlendFunction { @@ -679,6 +850,11 @@ protected double evaluate(double a, double b) { return a + b * (255 - a - a) / 255; } + @Override + protected String getName() { + return ColorFunctions.EXCLUSION; + } + } class Average extends AbstractSimpleColorBlendFunction { @@ -688,6 +864,11 @@ protected double evaluate(double a, double b) { return (a + b) / 2; } + @Override + protected String getName() { + return ColorFunctions.AVERAGE; + } + } class Negation extends AbstractSimpleColorBlendFunction { @@ -697,6 +878,11 @@ protected double evaluate(double a, double b) { return 255 - Math.abs(255 - b - a); } + @Override + protected String getName() { + return ColorFunctions.NEGATION; + } + } class Tint extends AbstractColorAmountFunction { @@ -705,6 +891,11 @@ class Tint extends AbstractColorAmountFunction { protected Expression evaluate(ColorExpression color, NumberExpression amount, HiddenTokenAwareTree token) { return mix(rgb(255, 255, 255, token), color, amount, token); } + + @Override + protected String getName() { + return ColorFunctions.TINT; + } } @@ -714,6 +905,11 @@ class Shade extends AbstractColorAmountFunction { protected Expression evaluate(ColorExpression color, NumberExpression amount, HiddenTokenAwareTree token) { return mix(rgb(0, 0, 0, token), color, amount, token); } + + @Override + protected String getName() { + return ColorFunctions.SHADE; + } } diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java index fe0017c0..608b29b0 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java @@ -370,7 +370,22 @@ protected Dimension resultDimension(String suffix, Dimension dimension) { } -class Mod extends AbstractMultiParameterFunction { +abstract class AbtractMultiParameterMathFunction extends AbstractMultiParameterFunction { + + @Override + protected boolean validateParameter(Expression parameter, ASTCssNodeType expected, ProblemsHandler problemsHandler) { + if (expected == ASTCssNodeType.NUMBER && parameter.getType() != expected) { + /* There is a special problemsHandler method for reporting math functions not getting numbers. */ + problemsHandler.mathFunctionParameterNotANumber(getName(), parameter); + return false; + } else { + return super.validateParameter(parameter, expected, problemsHandler); + } + } + +} + +class Mod extends AbtractMultiParameterMathFunction { @Override protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { @@ -393,6 +408,11 @@ protected int getMaxParameters() { protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); } + + @Override + protected String getName() { + return MathFunctions.MOD; + } } @@ -402,10 +422,15 @@ class Pow extends AbstractTwoValueMathFunction { protected Expression evaluate(NumberExpression a, NumberExpression b, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { return new NumberExpression(token, Math.pow(a.getValueAsDouble(), b.getValueAsDouble()), a.getSuffix(), null, a.getDimension()); } + + @Override + protected String getName() { + return MathFunctions.POW; + } } -abstract class AbstractTwoValueMathFunction extends AbstractMultiParameterFunction { +abstract class AbstractTwoValueMathFunction extends AbtractMultiParameterMathFunction { @Override protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { @@ -431,7 +456,7 @@ protected boolean validateParameter(Expression parameter, int position, Problems } -class Round extends AbstractMultiParameterFunction { +class Round extends AbtractMultiParameterMathFunction { @Override protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree parentToken) { @@ -468,6 +493,11 @@ protected boolean validateParameter(Expression parameter, int position, Problems return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); } + @Override + protected String getName() { + return MathFunctions.ROUND; + } + } class Pi extends AbstractFunction { diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java index b0f9c7ac..1aca2d36 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java @@ -92,6 +92,11 @@ protected boolean validateParameter(Expression parameter, int position, Problems return validateParameter(parameter, ASTCssNodeType.STRING_EXPRESSION, problemsHandler); } + @Override + protected String getName() { + return MiscFunctions.COLOR; + } + } class Unit extends AbstractMultiParameterFunction { @@ -135,6 +140,11 @@ protected boolean validateParameter(Expression parameter, int position, Problems return false; } + @Override + protected String getName() { + return MiscFunctions.UNIT; + } + } class Convert extends AbstractMultiParameterFunction { @@ -167,6 +177,11 @@ protected boolean validateParameter(Expression parameter, int position, Problems return false; } + @Override + protected String getName() { + return MiscFunctions.CONVERT; + } + } class Extract extends AbstractMultiParameterFunction { @@ -216,5 +231,10 @@ protected boolean validateParameter(Expression parameter, int position, Problems } return false; } + + @Override + protected String getName() { + return MiscFunctions.EXTRACT; + } } diff --git a/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java b/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java index c87386af..7e6e1135 100644 --- a/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java +++ b/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java @@ -73,12 +73,12 @@ public void errFormatWrongFirstParameter(Expression param) { collector.addError(new CompilationError(param, "First argument of format function must be either string or escaped value.")); } - public void wrongNumberOfArgumentsToFunction(Expression param, int expectedArguments) { - collector.addError(new CompilationError(param, "Wrong number of arguments to function, should be " + expectedArguments + ".")); + public void wrongNumberOfArgumentsToFunction(Expression param, String function, int expectedArguments) { + collector.addError(new CompilationError(param, "Wrong number of arguments to function '" + function + "', should be " + expectedArguments + ".")); } - public void wrongArgumentTypeToFunction(Expression param, ASTCssNodeType expected, ASTCssNodeType received) { - collector.addError(new CompilationError(param, "Wrong argument type to function, expected " + expected + " saw " + received + ".")); + public void wrongArgumentTypeToFunction(Expression param, String function, ASTCssNodeType expected, ASTCssNodeType received) { + collector.addError(new CompilationError(param, "Wrong argument type to function '" + function + "', expected " + expected + " saw " + received + ".")); } public void variablesCycle(List cycle) {