From: Jeremias Maerki Date: Wed, 30 Jun 2010 12:36:00 +0000 (+0000) Subject: Added support for the cie-lab-color() function that is found in the current XSL 2... X-Git-Tag: fop-1_1rc1old~290^2~25 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=bad25566ec069e65be02053aaac8f6cbabaa3c38;p=xmlgraphics-fop.git Added support for the cie-lab-color() function that is found in the current XSL 2.0 design notes. Some refactoring of ColorUtil to reduce code duplication. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_Color@959286 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/java/org/apache/fop/fo/expr/CIELabColorFunction.java b/src/java/org/apache/fop/fo/expr/CIELabColorFunction.java new file mode 100644 index 000000000..d027a9571 --- /dev/null +++ b/src/java/org/apache/fop/fo/expr/CIELabColorFunction.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.fo.expr; +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.datatypes.PercentBase; +import org.apache.fop.datatypes.PercentBaseContext; +import org.apache.fop.fo.properties.ColorProperty; +import org.apache.fop.fo.properties.Property; + +/** + * Implements the cie-lab-color() function. + * @since XSL-FO 2.0 + */ +class CIELabColorFunction extends FunctionBase { + + /** + * cie-lab-color() takes 2 times 3 arguments. + * {@inheritDoc} + */ + public int nbArgs() { + return 2 * 3; + } + + public PercentBase getPercentBase() { + return new CIELabPercentBase(); + } + + /** {@inheritDoc} */ + public Property eval(Property[] args, + PropertyInfo pInfo) throws PropertyException { + + float red = args[0].getNumber().floatValue(); + float green = args[1].getNumber().floatValue(); + float blue = args[2].getNumber().floatValue(); + /* Verify sRGB replacement arguments */ + if ((red < 0 || red > 255) + || (green < 0 || green > 255) + || (blue < 0 || blue > 255)) { + throw new PropertyException("sRGB color values out of range. " + + "Arguments to cie-lab-color() must be [0..255] or [0%..100%]"); + } + + float l = args[3].getNumber().floatValue(); + float a = args[4].getNumber().floatValue(); + float b = args[5].getNumber().floatValue(); + if (l < 0 || l > 100) { + throw new PropertyException("L* value out of range. Valid range: [0..100]"); + } + if (a < -127 || a > 127 || b < -127 || b > 127) { + throw new PropertyException("a* and b* values out of range. Valid range: [-127..+127]"); + } + + StringBuffer sb = new StringBuffer(); + sb.append("cie-lab-color(" + red + "," + green + "," + blue + "," + + l + "," + a + "," + b + ")"); + FOUserAgent ua = (pInfo == null) + ? null + : (pInfo.getFO() == null ? null : pInfo.getFO().getUserAgent()); + return ColorProperty.getInstance(ua, sb.toString()); + } + + private static class CIELabPercentBase implements PercentBase { + public int getDimension() { + return 0; + } + + public double getBaseValue() { + return 1.0f; + } + + public int getBaseLength(PercentBaseContext context) { + return 0; + } + + } + +} diff --git a/src/java/org/apache/fop/fo/expr/PropertyParser.java b/src/java/org/apache/fop/fo/expr/PropertyParser.java index 91218deec..9ef45befe 100644 --- a/src/java/org/apache/fop/fo/expr/PropertyParser.java +++ b/src/java/org/apache/fop/fo/expr/PropertyParser.java @@ -69,7 +69,8 @@ public final class PropertyParser extends PropertyTokenizer { FUNCTION_TABLE.put("label-end", new LabelEndFunction()); FUNCTION_TABLE.put("body-start", new BodyStartFunction()); FUNCTION_TABLE.put("rgb-icc", new ICCColorFunction()); - FUNCTION_TABLE.put("rgb-named-color", new NamedColorFunction()); + FUNCTION_TABLE.put("rgb-named-color", new NamedColorFunction()); //since XSL-FO 2.0 + FUNCTION_TABLE.put("cie-lab-color", new CIELabColorFunction()); //since XSL-FO 2.0 FUNCTION_TABLE.put("cmyk", new CMYKcolorFunction()); //non-standard!!! /** diff --git a/src/java/org/apache/fop/pdf/PDFCIELabColorSpace.java b/src/java/org/apache/fop/pdf/PDFCIELabColorSpace.java new file mode 100644 index 000000000..4d1d7f7ff --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFCIELabColorSpace.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.pdf; + +/** + * This class represents a "CIE L*a*b*" color space. It is expected that the components have + * the following ranges: L* [0..100], a* and b* [-127..127] + */ +public class PDFCIELabColorSpace extends PDFArray implements PDFColorSpace { + + /** + * Creates a new "CIE L*a*b*" color space. Valid value ranges for the white and black point + * are [0..1] as per the PDF spec. + * @param whitePoint the white point + * @param blackPoint the optional black point (may be null) + */ + public PDFCIELabColorSpace(float[] whitePoint, float[] blackPoint) { + super(); + + add(new PDFName("Lab")); + PDFDictionary dict = new PDFDictionary(); + dict.put("WhitePoint", toPDFArray("White point", whitePoint)); + if (whitePoint[1] != 1f) { + throw new IllegalArgumentException("The white point's Y coordinate must be 1.0"); + } + if (blackPoint != null) { + dict.put("BlackPoint", toPDFArray("Black point", blackPoint)); + } + dict.put("Range", new PDFArray(dict, new int[] {-128, 128, -128, 128})); + add(dict); + } + + private PDFArray toPDFArray(String name, float[] whitePoint) { + PDFArray wp = new PDFArray(); + if (whitePoint == null || whitePoint.length != 3) { + throw new IllegalArgumentException(name + " must be given an have 3 components"); + } + for (int i = 0; i < 3; i++) { + wp.add(whitePoint[i]); + } + return wp; + } + + /** {@inheritDoc} */ + public String getName() { + return "CS" + this.getObjectNumber(); + } + + /** {@inheritDoc} */ + public int getNumComponents() { + return 3; + } + + /** {@inheritDoc} */ + public boolean isCMYKColorSpace() { + return false; + } + + /** {@inheritDoc} */ + public boolean isDeviceColorSpace() { + return false; + } + + /** {@inheritDoc} */ + public boolean isGrayColorSpace() { + return false; + } + + /** {@inheritDoc} */ + public boolean isRGBColorSpace() { + return false; + } + +} diff --git a/src/java/org/apache/fop/pdf/PDFColorHandler.java b/src/java/org/apache/fop/pdf/PDFColorHandler.java index 4a5908ae1..3ae16b6e6 100644 --- a/src/java/org/apache/fop/pdf/PDFColorHandler.java +++ b/src/java/org/apache/fop/pdf/PDFColorHandler.java @@ -24,10 +24,12 @@ import java.awt.color.ColorSpace; import java.awt.color.ICC_ColorSpace; import java.awt.color.ICC_Profile; import java.text.DecimalFormat; +import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.xmlgraphics.java2d.color.CIELabColorSpace; import org.apache.xmlgraphics.java2d.color.ColorExt; import org.apache.xmlgraphics.java2d.color.ColorUtil; import org.apache.xmlgraphics.java2d.color.DeviceCMYKColorSpace; @@ -46,6 +48,8 @@ public class PDFColorHandler { private PDFResources resources; + private Map cieLabColorSpaces; + public PDFColorHandler(PDFResources resources) { this.resources = resources; } @@ -73,10 +77,17 @@ public class PDFColorHandler { return; } } + if (log.isDebugEnabled() && alt.length > 0) { + log.debug("None of the alternative colors are supported. Using fallback: " + + color); + } } //Fallback - establishColorFromColor(codeBuffer, color, fill); + boolean established = establishColorFromColor(codeBuffer, color, fill); + if (!established) { + establishDeviceRGB(codeBuffer, color, fill); + } } private boolean establishColorFromColor(StringBuffer codeBuffer, Color color, boolean fill) { @@ -93,11 +104,17 @@ public class PDFColorHandler { PDFSeparationColorSpace sepcs = getSeparationColorSpace((NamedColorSpace)cs); establishColor(codeBuffer, sepcs, color, fill); return true; + } else if (cs instanceof CIELabColorSpace) { + CIELabColorSpace labcs = (CIELabColorSpace)cs; + PDFCIELabColorSpace pdflab = getCIELabColorSpace(labcs); + selectColorSpace(codeBuffer, pdflab, fill); + float[] comps = color.getColorComponents(null); + float[] nativeComps = labcs.toNativeComponents(comps); + writeColor(codeBuffer, nativeComps, labcs.getNumComponents(), (fill ? "sc" : "SC")); + return true; } } - //Fallback (RGB) Color - establishDeviceRGB(codeBuffer, color, fill); - return true; + return false; } private PDFICCBasedColorSpace getICCBasedColorSpace(ICC_ColorSpace cs) { @@ -130,15 +147,44 @@ public class PDFColorHandler { return sepcs; } + private PDFCIELabColorSpace getCIELabColorSpace(CIELabColorSpace labCS) { + if (this.cieLabColorSpaces == null) { + this.cieLabColorSpaces = new java.util.HashMap(); + } + float[] wp = labCS.getWhitePoint(); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < 3; i++) { + if (i > 0) { + sb.append(','); + } + sb.append(wp[i]); + } + String key = sb.toString(); + PDFCIELabColorSpace cielab = (PDFCIELabColorSpace)this.cieLabColorSpaces.get(key); + if (cielab == null) { + //color space is not in the PDF, yet + float[] wp1 = new float[] {wp[0] / 100f, wp[1] / 100f, wp[2] / 100f}; + cielab = new PDFCIELabColorSpace(wp1, null); + getDocument().registerObject(cielab); + this.resources.addColorSpace(cielab); + this.cieLabColorSpaces.put(key, cielab); + } + return cielab; + } + private void establishColor(StringBuffer codeBuffer, PDFColorSpace pdfcs, Color color, boolean fill) { + selectColorSpace(codeBuffer, pdfcs, fill); + writeColor(codeBuffer, color, pdfcs.getNumComponents(), (fill ? "sc" : "SC")); + } + + private void selectColorSpace(StringBuffer codeBuffer, PDFColorSpace pdfcs, boolean fill) { codeBuffer.append(new PDFName(pdfcs.getName())); if (fill) { codeBuffer.append(" cs "); } else { codeBuffer.append(" CS "); } - writeColor(codeBuffer, color, pdfcs.getNumComponents(), (fill ? "sc" : "SC")); } private void establishDeviceRGB(StringBuffer codeBuffer, Color color, boolean fill) { diff --git a/src/java/org/apache/fop/pdf/PDFName.java b/src/java/org/apache/fop/pdf/PDFName.java index 42c39ef52..756d2fd0a 100644 --- a/src/java/org/apache/fop/pdf/PDFName.java +++ b/src/java/org/apache/fop/pdf/PDFName.java @@ -82,6 +82,14 @@ public class PDFName extends PDFObject { return this.name; } + /** + * Returns the name without the leading slash. + * @return the name without the leading slash + */ + public String getName() { + return this.name.substring(1); + } + /** {@inheritDoc} */ public boolean equals(Object obj) { if (!(obj instanceof PDFName)) { diff --git a/src/java/org/apache/fop/util/ColorUtil.java b/src/java/org/apache/fop/util/ColorUtil.java index 71c97f84e..775db4dca 100644 --- a/src/java/org/apache/fop/util/ColorUtil.java +++ b/src/java/org/apache/fop/util/ColorUtil.java @@ -29,6 +29,7 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.xmlgraphics.java2d.color.CIELabColorSpace; import org.apache.xmlgraphics.java2d.color.ColorExt; import org.apache.xmlgraphics.java2d.color.ColorSpaces; import org.apache.xmlgraphics.java2d.color.DeviceCMYKColorSpace; @@ -130,6 +131,8 @@ public final class ColorUtil { parsedColor = parseAsFopRgbIcc(foUserAgent, value); } else if (value.startsWith("fop-rgb-named-color")) { parsedColor = parseAsFopRgbNamedColor(foUserAgent, value); + } else if (value.startsWith("cie-lab-color")) { + parsedColor = parseAsCIELabColor(foUserAgent, value); } else if (value.startsWith("cmyk")) { parsedColor = parseAsCMYK(value); } @@ -234,33 +237,9 @@ public final class ColorUtil { throw new PropertyException( "Invalid number of arguments: rgb(" + value + ")"); } - float red = 0.0f, green = 0.0f, blue = 0.0f; - String str = args[0].trim(); - if (str.endsWith("%")) { - red = Float.parseFloat(str.substring(0, - str.length() - 1)) / 100f; - } else { - red = Float.parseFloat(str) / 255f; - } - str = args[1].trim(); - if (str.endsWith("%")) { - green = Float.parseFloat(str.substring(0, - str.length() - 1)) / 100f; - } else { - green = Float.parseFloat(str) / 255f; - } - str = args[2].trim(); - if (str.endsWith("%")) { - blue = Float.parseFloat(str.substring(0, - str.length() - 1)) / 100f; - } else { - blue = Float.parseFloat(str) / 255f; - } - if ((red < 0.0 || red > 1.0) - || (green < 0.0 || green > 1.0) - || (blue < 0.0 || blue > 1.0)) { - throw new PropertyException("Color values out of range"); - } + float red = parseComponent255(args[0], value); + float green = parseComponent255(args[1], value); + float blue = parseComponent255(args[2], value); parsedColor = new ColorExt(red, green, blue, null); } catch (PropertyException pe) { //simply re-throw @@ -276,6 +255,38 @@ public final class ColorUtil { return parsedColor; } + private static float parseComponent255(String str, String function) + throws PropertyException { + float component; + str = str.trim(); + if (str.endsWith("%")) { + component = Float.parseFloat(str.substring(0, + str.length() - 1)) / 100f; + } else { + component = Float.parseFloat(str) / 255f; + } + if ((component < 0.0 || component > 1.0)) { + throw new PropertyException("Color value out of range for " + function + ": " + + str + ". Valid range: [0..255] or [0%..100%]"); + } + return component; + } + + private static float parseComponent1(String argument, String function) + throws PropertyException { + return parseComponent(argument, 0f, 1f, function); + } + + private static float parseComponent(String argument, float min, float max, String function) + throws PropertyException { + float component = Float.parseFloat(argument.trim()); + if ((component < min || component > max)) { + throw new PropertyException("Color value out of range for " + function + ": " + + argument + ". Valid range: [" + min + ".." + max + "]"); + } + return component; + } + /** * parse a color given in the #.... format. * @@ -338,17 +349,9 @@ public final class ColorUtil { } //Set up fallback sRGB value - float red = 0, green = 0, blue = 0; - red = Float.parseFloat(args[0].trim()); - green = Float.parseFloat(args[1].trim()); - blue = Float.parseFloat(args[2].trim()); - /* Verify rgb replacement arguments */ - if ((red < 0 || red > 1) - || (green < 0 || green > 1) - || (blue < 0 || blue > 1)) { - throw new PropertyException("Color values out of range. " - + "Fallback RGB arguments to fop-rgb-icc() must be [0..1]"); - } + float red = parseComponent1(args[0], value); + float green = parseComponent1(args[1], value); + float blue = parseComponent1(args[2], value); Color sRGB = new ColorExt(red, green, blue, null); /* Get and verify ICC profile name */ @@ -435,20 +438,13 @@ public final class ColorUtil { try { if (args.length != 6) { - throw new PropertyException("rgb-named() function must have 6 arguments"); + throw new PropertyException("rgb-named-color() function must have 6 arguments"); } //Set up fallback sRGB value - float red = Float.parseFloat(args[0].trim()); - float green = Float.parseFloat(args[1].trim()); - float blue = Float.parseFloat(args[2].trim()); - /* Verify rgb replacement arguments */ - if ((red < 0 || red > 1) - || (green < 0 || green > 1) - || (blue < 0 || blue > 1)) { - throw new PropertyException("Color values out of range. " - + "Fallback RGB arguments to fop-rgb-named-color() must be [0..1]"); - } + float red = parseComponent1(args[0], value); + float green = parseComponent1(args[1], value); + float blue = parseComponent1(args[2], value); Color sRGB = new ColorExt(red, green, blue, null); /* Get and verify ICC profile name */ @@ -518,6 +514,55 @@ public final class ColorUtil { return parsedColor; } + /** + * Parse a color specified using the cie-lab-color() function. + * + * @param value the function call + * @return a color if possible + * @throws PropertyException if the format is wrong. + */ + private static Color parseAsCIELabColor(FOUserAgent foUserAgent, String value) + throws PropertyException { + Color parsedColor; + int poss = value.indexOf("("); + int pose = value.indexOf(")"); + if (poss != -1 && pose != -1) { + String[] args = value.substring(poss + 1, pose).split(","); + + try { + if (args.length != 6) { + throw new PropertyException("cie-lab-color() function must have 6 arguments"); + } + + //Set up fallback sRGB value + float red = parseComponent255(args[0], value); + float green = parseComponent255(args[1], value); + float blue = parseComponent255(args[2], value); + + float l = parseComponent(args[3], 0f, 100f, value); + float a = parseComponent(args[4], -127f, 127f, value); + float b = parseComponent(args[5], -127f, 127f, value); + + //Assuming the XSL-FO spec uses the D50 white point + CIELabColorSpace cs = ColorSpaces.getCIELabColorSpaceD50(); + //use toColor() to have components normalized + Color labColor = cs.toColor(l, a, b, 1.0f); + parsedColor = new ColorExt(red, green, blue, new Color[] {labColor}); + + } catch (PropertyException pe) { + //simply re-throw + throw pe; + } catch (Exception e) { + //wrap in a PropertyException + throw new PropertyException(e); + } + } else { + throw new PropertyException("Unknown color format: " + value + + ". Must be cie-lab-color(r,g,b,Lightness,a-value,b-value)"); + } + return parsedColor; + } + private static String unescapeString(String iccProfileSrc) { if (iccProfileSrc.startsWith("\"") || iccProfileSrc.startsWith("'")) { iccProfileSrc = iccProfileSrc.substring(1); @@ -549,43 +594,10 @@ public final class ColorUtil { throw new PropertyException( "Invalid number of arguments: cmyk(" + value + ")"); } - float cyan = 0.0f, magenta = 0.0f, yellow = 0.0f, black = 0.0f; - String str = args[0].trim(); - if (str.endsWith("%")) { - cyan = Float.parseFloat(str.substring(0, - str.length() - 1)) / 100.0f; - } else { - cyan = Float.parseFloat(str); - } - str = args[1].trim(); - if (str.endsWith("%")) { - magenta = Float.parseFloat(str.substring(0, - str.length() - 1)) / 100.0f; - } else { - magenta = Float.parseFloat(str); - } - str = args[2].trim(); - if (str.endsWith("%")) { - yellow = Float.parseFloat(str.substring(0, - str.length() - 1)) / 100.0f; - } else { - yellow = Float.parseFloat(str); - } - str = args[3].trim(); - if (str.endsWith("%")) { - black = Float.parseFloat(str.substring(0, - str.length() - 1)) / 100.0f; - } else { - black = Float.parseFloat(str); - } - - if ((cyan < 0.0 || cyan > 1.0) - || (magenta < 0.0 || magenta > 1.0) - || (yellow < 0.0 || yellow > 1.0) - || (black < 0.0 || black > 1.0)) { - throw new PropertyException("Color values out of range" - + "Arguments to cmyk() must be in the range [0%-100%] or [0.0-1.0]"); - } + float cyan = parseComponent1(args[0], value); + float magenta = parseComponent1(args[1], value); + float yellow = parseComponent1(args[2], value); + float black = parseComponent1(args[3], value); float[] comps = new float[] {cyan, magenta, yellow, black}; parsedColor = DeviceCMYKColorSpace.createColorExt(comps); } catch (PropertyException pe) { @@ -671,6 +683,9 @@ public final class ColorUtil { if (icc == null) { return toRGBFunctionCall(color); } + if (icc.getColorSpace() instanceof CIELabColorSpace) { + return toCIELabFunctionCall(color, icc); + } StringBuffer sb = new StringBuffer(40); String functionName; @@ -706,6 +721,20 @@ public final class ColorUtil { return functionName + sb.toString(); } + private static String toCIELabFunctionCall(ColorExt color, Color cieLab) { + StringBuffer sb = new StringBuffer("cie-lab-color("); + sb.append(color.getRed()).append(','); + sb.append(color.getGreen()).append(','); + sb.append(color.getBlue()); + CIELabColorSpace cs = (CIELabColorSpace)cieLab.getColorSpace(); + float[] lab = cs.toNativeComponents(cieLab.getColorComponents(null)); + for (int i = 0; i < 3; i++) { + sb.append(',').append(lab[i]); + } + sb.append(')'); + return sb.toString(); + } + private static Color createColor(int r, int g, int b) { return new ColorExt(r, g, b, null); }