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-ffa450edef68pull/20/head
@@ -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; | |||
} | |||
} | |||
} |
@@ -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!!! | |||
/** |
@@ -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; | |||
} | |||
} |
@@ -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) { |
@@ -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)) { |
@@ -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); | |||
} |