diff options
Diffstat (limited to 'src/java/org/apache/fop')
35 files changed, 934 insertions, 152 deletions
diff --git a/src/java/org/apache/fop/apps/FopFactory.java b/src/java/org/apache/fop/apps/FopFactory.java index d5cc4dee9..363d68059 100644 --- a/src/java/org/apache/fop/apps/FopFactory.java +++ b/src/java/org/apache/fop/apps/FopFactory.java @@ -19,17 +19,22 @@ package org.apache.fop.apps; +import java.awt.color.ColorSpace; +import java.awt.color.ICC_ColorSpace; +import java.awt.color.ICC_Profile; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.MalformedURLException; import java.util.Collection; import java.util.Collections; +import java.util.Map; import java.util.Set; import javax.xml.transform.Source; import javax.xml.transform.TransformerException; import javax.xml.transform.URIResolver; +import javax.xml.transform.stream.StreamSource; import org.xml.sax.SAXException; @@ -119,11 +124,16 @@ public class FopFactory { private Set ignoredNamespaces = new java.util.HashSet(); + /** Map with cached ICC based ColorSpace objects. */ + private Map colorSpaceMap = null; + /** * Main constructor. */ protected FopFactory() { this.elementMappingRegistry = new ElementMappingRegistry(this); + // Use a synchronized Map - I am not really sure this is needed, but better safe than sorry. + this.colorSpaceMap = Collections.synchronizedMap(new java.util.HashMap()); } /** @@ -632,7 +642,63 @@ public class FopFactory { } return source; } + + /** + * Create (if needed) and return an ICC ColorSpace instance. + * + * The ICC profile source is taken from the src attribute of the color-profile FO element. + * If the ICC ColorSpace is not yet in the cache a new one is created and stored in the cache. + * + * The FOP URI resolver is used to try and locate the ICC file. + * If that fails null is returned. + * + * @param base a base URI to resolve relative URIs + * @param iccProfileSrc ICC Profile source to return a ColorSpace for + * @return ICC ColorSpace object or null if ColorSpace could not be created + */ + public ColorSpace getColorSpace(String base, String iccProfileSrc) { + ColorSpace colorSpace = null; + if (!this.colorSpaceMap.containsKey(base + iccProfileSrc)) { + try { + ICC_Profile iccProfile = null; + // First attempt to use the FOP URI resolver to locate the ICC + // profile + Source src = this.resolveURI(iccProfileSrc, base); + if (src != null && src instanceof StreamSource) { + // FOP URI resolver found ICC profile - create ICC profile + // from the Source + iccProfile = ICC_Profile.getInstance(((StreamSource) src) + .getInputStream()); + } else { + // TODO - Would it make sense to fall back on VM ICC + // resolution + // Problem is the cache might be more difficult to maintain + // + // FOP URI resolver did not find ICC profile - perhaps the + // Java VM can find it? + // iccProfile = ICC_Profile.getInstance(iccProfileSrc); + } + if (iccProfile != null) { + colorSpace = new ICC_ColorSpace(iccProfile); + } + } catch (IOException e) { + // Ignore exception - will be logged a bit further down + // (colorSpace == null case) + } - + if (colorSpace != null) { + // Put in cache (not when VM resolved it as we can't control + this.colorSpaceMap.put(base + iccProfileSrc, colorSpace); + } else { + // TODO To avoid an excessive amount of warnings perhaps + // register a null ColorMap in the colorSpaceMap + log.warn("Color profile '" + iccProfileSrc + "' not found."); + } + } else { + colorSpace = (ColorSpace) this.colorSpaceMap.get(base + + iccProfileSrc); + } + return colorSpace; + } } diff --git a/src/java/org/apache/fop/area/AreaTreeParser.java b/src/java/org/apache/fop/area/AreaTreeParser.java index b6d1e8163..fce776e22 100644 --- a/src/java/org/apache/fop/area/AreaTreeParser.java +++ b/src/java/org/apache/fop/area/AreaTreeParser.java @@ -928,16 +928,15 @@ public class AreaTreeParser { area.addTrait(trait, value); } else if (cl == Color.class) { try { - area.addTrait(trait, ColorUtil.parseColorString(value)); + area.addTrait(trait, ColorUtil.parseColorString(this.userAgent, value)); } catch (PropertyException e) { throw new IllegalArgumentException(e.getMessage()); } } else if (cl == Background.class) { Background bkg = new Background(); try { - Color col = ColorUtil - .parseColorString(attributes - .getValue("bkg-color")); + Color col = ColorUtil.parseColorString( + this.userAgent, attributes.getValue("bkg-color")); bkg.setColor(col); } catch (PropertyException e) { throw new IllegalArgumentException(e.getMessage()); @@ -970,7 +969,7 @@ public class AreaTreeParser { } area.addTrait(trait, bkg); } else if (cl == BorderProps.class) { - area.addTrait(trait, BorderProps.valueOf(value)); + area.addTrait(trait, BorderProps.valueOf(this.userAgent, value)); } } else { if (trait == Trait.FONT) { diff --git a/src/java/org/apache/fop/area/Trait.java b/src/java/org/apache/fop/area/Trait.java index 6db47949a..3cddc8006 100644 --- a/src/java/org/apache/fop/area/Trait.java +++ b/src/java/org/apache/fop/area/Trait.java @@ -571,7 +571,7 @@ public class Trait implements Serializable { public String toString() { StringBuffer sb = new StringBuffer(); if (color != null) { - sb.append("color=").append(ColorUtil.colorTOsRGBString(color)); + sb.append("color=").append(ColorUtil.colorToString(color)); } if (url != null) { if (color != null) { diff --git a/src/java/org/apache/fop/fo/FOPropertyMapping.java b/src/java/org/apache/fop/fo/FOPropertyMapping.java index 2cb2da653..48a58b94a 100644 --- a/src/java/org/apache/fop/fo/FOPropertyMapping.java +++ b/src/java/org/apache/fop/fo/FOPropertyMapping.java @@ -22,6 +22,7 @@ package org.apache.fop.fo; import java.util.HashMap; import java.util.Map; +import org.apache.fop.apps.FOUserAgent; import org.apache.fop.datatypes.LengthBase; import org.apache.fop.fo.expr.PropertyException; import org.apache.fop.fo.properties.BackgroundPositionShorthandParser; @@ -564,7 +565,9 @@ public final class FOPropertyMapping implements Constants { Property p, PropertyList propertyList, FObj fo) throws PropertyException { String nameval = p.getNCname(); if (nameval != null) { - return new ColorProperty(nameval); + FObj fobj = (fo == null ? propertyList.getFObj() : fo); + FOUserAgent ua = (fobj == null ? null : fobj.getUserAgent()); + return new ColorProperty(ua, nameval); } return super.convertPropertyDatatype(p, propertyList, fo); } @@ -1289,7 +1292,7 @@ public final class FOPropertyMapping implements Constants { m.addEnum("mathematical", getEnumProperty(EN_MATHEMATICAL, "MATHEMATICAL")); m.addEnum("central", getEnumProperty(EN_CENTRAL, "CENTRAL")); m.addEnum("middle", getEnumProperty(EN_MIDDLE, "MIDDLE")); - m.addEnum("text-after-edge", getEnumProperty(EN_TEXT_AFTER_EDGE, "TEXT_AFTER_EDGE" )); + m.addEnum("text-after-edge", getEnumProperty(EN_TEXT_AFTER_EDGE, "TEXT_AFTER_EDGE")); m.addEnum("text-before-edge", getEnumProperty(EN_TEXT_BEFORE_EDGE, "TEXT_BEFORE_EDGE")); m.setDefault("auto"); m.addShorthand(s_generics[PR_VERTICAL_ALIGN]); diff --git a/src/java/org/apache/fop/fo/FOText.java b/src/java/org/apache/fop/fo/FOText.java index 70f0fd5bf..f2c4c4e54 100644 --- a/src/java/org/apache/fop/fo/FOText.java +++ b/src/java/org/apache/fop/fo/FOText.java @@ -145,7 +145,7 @@ public class FOText extends FONode { * @see org.apache.fop.fo.FONode#clone(FONode, boolean) */ public FONode clone(FONode parent, boolean removeChildren) - throws FOPException { + throws FOPException { FOText ft = (FOText) super.clone(parent, removeChildren); if (removeChildren) { //not really removing, but just make sure the char array @@ -164,8 +164,7 @@ public class FOText extends FONode { public void bind(PropertyList pList) throws FOPException { commonFont = pList.getFontProps(); commonHyphenation = pList.getHyphenationProps(); - - color = pList.get(Constants.PR_COLOR).getColor(); + color = pList.get(Constants.PR_COLOR).getColor(getUserAgent()); lineHeight = pList.get(Constants.PR_LINE_HEIGHT).getSpace(); letterSpacing = pList.get(Constants.PR_LETTER_SPACING); whiteSpaceCollapse = pList.get(Constants.PR_WHITE_SPACE_COLLAPSE).getEnum(); diff --git a/src/java/org/apache/fop/fo/PropertyList.java b/src/java/org/apache/fop/fo/PropertyList.java index 8b7c67abd..4ec6ec26d 100644 --- a/src/java/org/apache/fop/fo/PropertyList.java +++ b/src/java/org/apache/fop/fo/PropertyList.java @@ -421,7 +421,7 @@ public abstract class PropertyList { } } catch (PropertyException e) { log.error("Ignoring property: " - + attributeName + "=\"" + attributeValue + "\""); + + attributeName + "=\"" + attributeValue + "\" (" + e.getMessage() + ")"); } } } diff --git a/src/java/org/apache/fop/fo/expr/CMYKcolorFunction.java b/src/java/org/apache/fop/fo/expr/CMYKcolorFunction.java new file mode 100644 index 000000000..a66e8492a --- /dev/null +++ b/src/java/org/apache/fop/fo/expr/CMYKcolorFunction.java @@ -0,0 +1,50 @@ +/* + * 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.fo.properties.ColorProperty; +import org.apache.fop.fo.properties.Property; + +/** + * Implements the cmyk() function. + */ +class CMYKcolorFunction extends FunctionBase { + + /** + * cmyk takes four arguments. + * @see org.apache.fop.fo.expr.Function#nbArgs() + */ + public int nbArgs() { + return 4; + } + + /** @see org.apache.fop.fo.expr.Function */ + public Property eval(Property[] args, + PropertyInfo pInfo) throws PropertyException { + StringBuffer sb = new StringBuffer(); + sb.append("cmyk(" + args[0] + "," + args[1] + "," + args[2] + "," + args[3] + ")"); + FOUserAgent ua = (pInfo == null) + ? null + : (pInfo.getFO() == null ? null : pInfo.getFO().getUserAgent()); + return new ColorProperty(ua, sb.toString()); + } + + +} diff --git a/src/java/org/apache/fop/fo/expr/ICCColorFunction.java b/src/java/org/apache/fop/fo/expr/ICCColorFunction.java new file mode 100644 index 000000000..fce073467 --- /dev/null +++ b/src/java/org/apache/fop/fo/expr/ICCColorFunction.java @@ -0,0 +1,77 @@ +/* + * 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.fo.pagination.ColorProfile; +import org.apache.fop.fo.pagination.Declarations; +import org.apache.fop.fo.properties.ColorProperty; +import org.apache.fop.fo.properties.Property; + +/** + * Implements the rgb-icc() function. + */ +class ICCColorFunction extends FunctionBase { + + /** + * rgb-icc takes a variable number of arguments. + * At least 4 should be passed - returns -4 + * @see org.apache.fop.fo.expr.Function#nbArgs() + */ + public int nbArgs() { + return -4; + } + + /** @see org.apache.fop.fo.expr.Function */ + public Property eval(Property[] args, + PropertyInfo pInfo) throws PropertyException { + StringBuffer sb = new StringBuffer(); + + // Map color profile NCNAME to src from declarations/color-profile element + String colorProfileName = args[3].getString(); + Declarations decls = pInfo.getFO().getRoot().getDeclarations(); + ColorProfile cp = decls.getColorProfile(colorProfileName); + if (cp == null) { + PropertyException pe = new PropertyException("The " + colorProfileName + + " color profile was not declared"); + pe.setPropertyInfo(pInfo); + throw pe; + } + String src = cp.getSrc(); + + // rgb-icc is replaced with fop-rgb-icc which has an extra fifth argument containing the + // color profile src attribute as it is defined in the color-profile declarations element. + sb.append("fop-rgb-icc(" + args[0]); + for (int ix = 1; ix < args.length; ix++) { + if (ix == 3) { + sb.append("," + colorProfileName); + sb.append(",\"" + src + "\""); + } else { + sb.append("," + args[ix]); + } + } + sb.append(")"); + FOUserAgent ua = (pInfo == null + ? null + : (pInfo.getFO() == null ? null : pInfo.getFO().getUserAgent())); + return new ColorProperty(ua, sb.toString()); + } + + +} diff --git a/src/java/org/apache/fop/fo/expr/NCnameProperty.java b/src/java/org/apache/fop/fo/expr/NCnameProperty.java index 7188fb953..8e2f8e27f 100644 --- a/src/java/org/apache/fop/fo/expr/NCnameProperty.java +++ b/src/java/org/apache/fop/fo/expr/NCnameProperty.java @@ -21,6 +21,7 @@ package org.apache.fop.fo.expr; import java.awt.Color; +import org.apache.fop.apps.FOUserAgent; import org.apache.fop.fo.properties.Property; import org.apache.fop.util.ColorUtil; @@ -42,13 +43,17 @@ public class NCnameProperty extends Property { /** * If a system color, return the corresponding value. * + * @param foUserAgent + * Reference to FOP user agent - keeps track of cached ColorMaps for ICC colors * @return Color object corresponding to the NCName */ - public Color getColor() { + public Color getColor(FOUserAgent foUserAgent) { try { - return ColorUtil.parseColorString(ncName); + return ColorUtil.parseColorString(foUserAgent, ncName); } catch (PropertyException e) { - //TODO: This should probably print an error message? + //Not logging this error since for properties like "border" you would get an awful + //lot of error messages for things like "solid" not being valid colors. + //log.error("Can't create color value: " + e.getMessage()); return null; } } diff --git a/src/java/org/apache/fop/fo/expr/NumericProperty.java b/src/java/org/apache/fop/fo/expr/NumericProperty.java index f00b9951c..4c112964d 100644 --- a/src/java/org/apache/fop/fo/expr/NumericProperty.java +++ b/src/java/org/apache/fop/fo/expr/NumericProperty.java @@ -20,6 +20,8 @@ package org.apache.fop.fo.expr; import java.awt.Color; + +import org.apache.fop.apps.FOUserAgent; import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.PercentBaseContext; import org.apache.fop.datatypes.Numeric; @@ -105,8 +107,8 @@ public class NumericProperty extends Property implements Numeric, Length { return null; } - /** @see org.apache.fop.fo.properties.Property#getColor() */ - public Color getColor() { + /** @see org.apache.fop.fo.properties.Property#getColor(FOUserAgent) */ + public Color getColor(FOUserAgent foUserAgent) { // TODO: try converting to numeric number and then to color return null; } diff --git a/src/java/org/apache/fop/fo/expr/PropertyParser.java b/src/java/org/apache/fop/fo/expr/PropertyParser.java index 699094cc7..5b5956f44 100644 --- a/src/java/org/apache/fop/fo/expr/PropertyParser.java +++ b/src/java/org/apache/fop/fo/expr/PropertyParser.java @@ -19,6 +19,7 @@ package org.apache.fop.fo.expr; +import org.apache.fop.apps.FOUserAgent; import org.apache.fop.datatypes.Numeric; import org.apache.fop.datatypes.PercentBase; import org.apache.fop.fo.properties.ColorProperty; @@ -30,6 +31,8 @@ import org.apache.fop.fo.properties.Property; import org.apache.fop.fo.properties.StringProperty; import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; /** * Class to parse XSL-FO property expressions. @@ -62,12 +65,12 @@ public final class PropertyParser extends PropertyTokenizer { new PPColWidthFunction()); FUNCTION_TABLE.put("label-end", new LabelEndFunction()); FUNCTION_TABLE.put("body-start", new BodyStartFunction()); + FUNCTION_TABLE.put("rgb-icc", new ICCColorFunction()); + FUNCTION_TABLE.put("cmyk", new CMYKcolorFunction()); //non-standard!!! /** * * NOT YET IMPLEMENTED!!! - * FUNCTION_TABLE.put("icc-color", new ICCcolorFunction()); * FUNCTION_TABLE.put("system-font", new SystemFontFunction()); - * * FUNCTION_TABLE.put("merge-property-values", new MergePropsFunction()); */ } @@ -295,7 +298,10 @@ public final class PropertyParser extends PropertyTokenizer { break; case TOK_COLORSPEC: - prop = new ColorProperty(currentTokenValue); + FOUserAgent ua = (propInfo == null) + ? null + : (propInfo.getFO() == null ? null : propInfo.getFO().getUserAgent()); + prop = new ColorProperty(ua, currentTokenValue); break; case TOK_FUNCTION_LPAR: @@ -307,7 +313,12 @@ public final class PropertyParser extends PropertyTokenizer { next(); // Push new function (for function context: getPercentBase()) propInfo.pushFunction(function); - prop = function.eval(parseArgs(function), propInfo); + if (function.nbArgs() < 0) { + // Negative nbArgs --> function with variable number of arguments + prop = function.eval(parseVarArgs(function), propInfo); + } else { + prop = function.eval(parseArgs(function), propInfo); + } propInfo.popFunction(); return prop; @@ -362,6 +373,54 @@ public final class PropertyParser extends PropertyTokenizer { } return args; } + + /** + * + * Parse a comma separated list of function arguments. Each argument + * may itself be an expression. This method consumes the closing right + * parenthesis of the argument list. + * + * The method differs from parseArgs in that it accepts a variable + * number of arguments. + * + * @param function The function object for which the arguments are + * collected. + * @return An array of Property objects representing the arguments + * found. + * @throws PropertyException If the number of arguments found isn't equal + * to the number expected. + * + * TODO Merge this with parseArgs? + */ + Property[] parseVarArgs(Function function) throws PropertyException { + // For variable argument functions the minimum number of arguments is returned as a + // negative integer from the nbArgs method + int nbArgs = -function.nbArgs(); + List args = new LinkedList(); + Property prop; + if (currentToken == TOK_RPAR) { + // No args: func() + next(); + } else { + while (true) { + prop = parseAdditiveExpr(); + args.add(prop); + // ignore extra args + if (currentToken != TOK_COMMA) { + break; + } + next(); + } + expectRpar(); + } + if (nbArgs > args.size()) { + throw new PropertyException("Expected at least " + nbArgs + + ", but got " + args.size() + " args for function"); + } + Property[] propArray = new Property[args.size()]; + args.toArray(propArray); + return propArray; + } /** diff --git a/src/java/org/apache/fop/fo/expr/RGBColorFunction.java b/src/java/org/apache/fop/fo/expr/RGBColorFunction.java index d74d1b7f4..805d8014f 100644 --- a/src/java/org/apache/fop/fo/expr/RGBColorFunction.java +++ b/src/java/org/apache/fop/fo/expr/RGBColorFunction.java @@ -19,6 +19,7 @@ package org.apache.fop.fo.expr; +import org.apache.fop.apps.FOUserAgent; import org.apache.fop.datatypes.PercentBaseContext; import org.apache.fop.datatypes.PercentBase; import org.apache.fop.fo.properties.ColorProperty; @@ -46,7 +47,10 @@ class RGBColorFunction extends FunctionBase { /** @see org.apache.fop.fo.expr.Function */ public Property eval(Property[] args, PropertyInfo pInfo) throws PropertyException { - return new ColorProperty("rgb(" + args[0] + "," + args[1] + "," + args[2] + ")"); + FOUserAgent ua = (pInfo == null) + ? null + : (pInfo.getFO() == null ? null : pInfo.getFO().getUserAgent()); + return new ColorProperty(ua, "rgb(" + args[0] + "," + args[1] + "," + args[2] + ")"); } diff --git a/src/java/org/apache/fop/fo/expr/SystemColorFunction.java b/src/java/org/apache/fop/fo/expr/SystemColorFunction.java index 678a56ef8..ea9e4562e 100644 --- a/src/java/org/apache/fop/fo/expr/SystemColorFunction.java +++ b/src/java/org/apache/fop/fo/expr/SystemColorFunction.java @@ -19,6 +19,7 @@ package org.apache.fop.fo.expr; +import org.apache.fop.apps.FOUserAgent; import org.apache.fop.fo.properties.ColorProperty; import org.apache.fop.fo.properties.Property; @@ -35,7 +36,10 @@ class SystemColorFunction extends FunctionBase { /** @see org.apache.fop.fo.expr.Function */ public Property eval(Property[] args, PropertyInfo pInfo) throws PropertyException { - return new ColorProperty("system-color(" + args[0] + ")"); + FOUserAgent ua = (pInfo == null) + ? null + : (pInfo.getFO() == null ? null : pInfo.getFO().getUserAgent()); + return new ColorProperty(ua, "system-color(" + args[0] + ")"); } diff --git a/src/java/org/apache/fop/fo/flow/BidiOverride.java b/src/java/org/apache/fop/fo/flow/BidiOverride.java index 8546459fb..3df774be9 100644 --- a/src/java/org/apache/fop/fo/flow/BidiOverride.java +++ b/src/java/org/apache/fop/fo/flow/BidiOverride.java @@ -87,7 +87,7 @@ public class BidiOverride extends FObjMixed { commonAural = pList.getAuralProps(); commonFont = pList.getFontProps(); commonRelativePosition = pList.getRelativePositionProps(); - prColor = pList.get(PR_COLOR).getColor(); + prColor = pList.get(PR_COLOR).getColor(getUserAgent()); // prDirection = pList.get(PR_DIRECTION); // prLetterSpacing = pList.get(PR_LETTER_SPACING); lineHeight = pList.get(PR_LINE_HEIGHT).getSpace(); diff --git a/src/java/org/apache/fop/fo/flow/Block.java b/src/java/org/apache/fop/fo/flow/Block.java index b5dd2626f..f52ad36b2 100644 --- a/src/java/org/apache/fop/fo/flow/Block.java +++ b/src/java/org/apache/fop/fo/flow/Block.java @@ -110,7 +110,7 @@ public class Block extends FObjMixed { breakAfter = pList.get(PR_BREAK_AFTER).getEnum(); breakBefore = pList.get(PR_BREAK_BEFORE).getEnum(); - color = pList.get(PR_COLOR).getColor(); + color = pList.get(PR_COLOR).getColor(getUserAgent()); textDepth = pList.get(PR_TEXT_DEPTH).getLength(); textAltitude = pList.get(PR_TEXT_ALTITUDE).getLength(); hyphenationKeep = pList.get(PR_HYPHENATION_KEEP).getEnum(); diff --git a/src/java/org/apache/fop/fo/flow/Character.java b/src/java/org/apache/fop/fo/flow/Character.java index 5afe52eb4..cb32c9a49 100644 --- a/src/java/org/apache/fop/fo/flow/Character.java +++ b/src/java/org/apache/fop/fo/flow/Character.java @@ -117,7 +117,7 @@ public class Character extends FObj { alignmentBaseline = pList.get(PR_ALIGNMENT_BASELINE).getEnum(); baselineShift = pList.get(PR_BASELINE_SHIFT).getLength(); character = pList.get(PR_CHARACTER).getCharacter(); - color = pList.get(PR_COLOR).getColor(); + color = pList.get(PR_COLOR).getColor(getUserAgent()); dominantBaseline = pList.get(PR_DOMINANT_BASELINE).getEnum(); textDepth = pList.get(PR_TEXT_DEPTH).getLength(); textAltitude = pList.get(PR_TEXT_ALTITUDE).getLength(); diff --git a/src/java/org/apache/fop/fo/flow/InitialPropertySet.java b/src/java/org/apache/fop/fo/flow/InitialPropertySet.java index e68c11857..4fd1eae08 100644 --- a/src/java/org/apache/fop/fo/flow/InitialPropertySet.java +++ b/src/java/org/apache/fop/fo/flow/InitialPropertySet.java @@ -73,7 +73,7 @@ public class InitialPropertySet extends FObj { commonBorderPaddingBackground = pList.getBorderPaddingBackgroundProps(); commonFont = pList.getFontProps(); commonRelativePosition = pList.getRelativePositionProps(); - color = pList.get(PR_COLOR).getColor(); + color = pList.get(PR_COLOR).getColor(getUserAgent()); id = pList.get(PR_ID).getString(); // letterSpacing = pList.get(PR_LETTER_SPACING); lineHeight = pList.get(PR_LINE_HEIGHT).getSpace(); diff --git a/src/java/org/apache/fop/fo/flow/InlineLevel.java b/src/java/org/apache/fop/fo/flow/InlineLevel.java index 06e7c775a..cae0fac1d 100644 --- a/src/java/org/apache/fop/fo/flow/InlineLevel.java +++ b/src/java/org/apache/fop/fo/flow/InlineLevel.java @@ -65,7 +65,7 @@ public abstract class InlineLevel extends FObjMixed { commonMarginInline = pList.getMarginInlineProps(); commonAural = pList.getAuralProps(); commonFont = pList.getFontProps(); - color = pList.get(PR_COLOR).getColor(); + color = pList.get(PR_COLOR).getColor(getUserAgent()); lineHeight = pList.get(PR_LINE_HEIGHT).getSpace(); visibility = pList.get(PR_VISIBILITY).getEnum(); } diff --git a/src/java/org/apache/fop/fo/flow/PageNumber.java b/src/java/org/apache/fop/fo/flow/PageNumber.java index 06f621be5..aee4a9afb 100644 --- a/src/java/org/apache/fop/fo/flow/PageNumber.java +++ b/src/java/org/apache/fop/fo/flow/PageNumber.java @@ -112,7 +112,7 @@ public class PageNumber extends FObj { wrapOption = pList.get(PR_WRAP_OPTION).getEnum(); // implicit properties - color = pList.get(Constants.PR_COLOR).getColor(); + color = pList.get(Constants.PR_COLOR).getColor(getUserAgent()); } /** diff --git a/src/java/org/apache/fop/fo/flow/PageNumberCitation.java b/src/java/org/apache/fop/fo/flow/PageNumberCitation.java index 9c63b4c1b..d6f0b940c 100644 --- a/src/java/org/apache/fop/fo/flow/PageNumberCitation.java +++ b/src/java/org/apache/fop/fo/flow/PageNumberCitation.java @@ -117,7 +117,7 @@ public class PageNumberCitation extends FObj { wrapOption = pList.get(PR_WRAP_OPTION).getEnum(); // implicit properties - color = pList.get(Constants.PR_COLOR).getColor(); + color = pList.get(Constants.PR_COLOR).getColor(getUserAgent()); } /** diff --git a/src/java/org/apache/fop/fo/pagination/ColorProfile.java b/src/java/org/apache/fop/fo/pagination/ColorProfile.java index ff77b9eac..fe379c6af 100644 --- a/src/java/org/apache/fop/fo/pagination/ColorProfile.java +++ b/src/java/org/apache/fop/fo/pagination/ColorProfile.java @@ -19,24 +19,16 @@ package org.apache.fop.fo.pagination; -// Java -import java.awt.Color; -import java.awt.color.ICC_ColorSpace; -import java.awt.color.ICC_Profile; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - import org.apache.fop.apps.FOPException; import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObj; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; + import org.xml.sax.Locator; /** * The fo:color-profile formatting object. - * TODO: This needs to be implemented properly! * This loads the color profile when needed and resolves a requested color. */ public class ColorProfile extends FObj { @@ -45,8 +37,6 @@ public class ColorProfile extends FObj { private String colorProfileName; private int renderingIntent; // End of property values - - private ICC_ColorSpace colorSpace = null; /** * @see org.apache.fop.fo.FONode#FONode(FONode) @@ -80,38 +70,6 @@ public class ColorProfile extends FObj { return colorProfileName; } - /** - * Get the color specified with the color values from the color profile. - * The default values are used if the profile could not be loaded - * or the value is not found. - * @param colorVals integer array containing the color profile? - * @param defR integer value for red channel (0-255)? - * @param defG integer value for green channel (0-255)? - * @param defB integer value for blue channel (0-255)? - * @return the ColorType object corresponding to the input - */ - public Color getColor(int[] colorVals, int defR, int defG, int defB) { - // float[] rgbvals = colorSpace.toRGB(colorVals); - // return new ColorType(rgbvals); - return null; - } - - /** - * Load the color profile. - */ - private void load() { - try { - URL url = new URL(src); - InputStream is = url.openStream(); - ICC_Profile iccProfile = ICC_Profile.getInstance(is); - colorSpace = new ICC_ColorSpace(iccProfile); - } catch (IOException ioe) { - getLogger().error("Could not read Color Profile src", ioe); - } catch (IllegalArgumentException iae) { - getLogger().error("Color Profile src not an ICC Profile", iae); - } - } - /** @see org.apache.fop.fo.FONode#getLocalName() */ public String getLocalName() { return "color-profile"; @@ -123,4 +81,29 @@ public class ColorProfile extends FObj { public int getNameId() { return FO_COLOR_PROFILE; } + + /** + * Get src attribute + * + * @return Value of color-profile src attribute + */ + public String getSrc() { + return this.src; + } + + /** + * Get rendering-intent attribute + * + * Returned value is one of + * Constants.EN_AUTO + * Constants.EN_PERCEPTUAL + * Constants.EN_RELATIVE_COLOMETRIC + * Constants.EN_SATURATION + * Constants.EN_ABSOLUTE_COLORMETRIC + * + * @return Rendering intent attribute + */ + public int getRenderingIntent() { + return this.renderingIntent; + } } diff --git a/src/java/org/apache/fop/fo/pagination/Declarations.java b/src/java/org/apache/fop/fo/pagination/Declarations.java index a3a96361a..4f8273caf 100644 --- a/src/java/org/apache/fop/fo/pagination/Declarations.java +++ b/src/java/org/apache/fop/fo/pagination/Declarations.java @@ -83,15 +83,7 @@ public class Declarations extends FObj { if (node.getName().equals("fo:color-profile")) { ColorProfile cp = (ColorProfile)node; if (!"".equals(cp.getColorProfileName())) { - if (colorProfiles == null) { - colorProfiles = new java.util.HashMap(); - } - if (colorProfiles.get(cp.getColorProfileName()) != null) { - // duplicate names - getLogger().warn("Duplicate fo:color-profile profile name : " - + cp.getColorProfileName()); - } - colorProfiles.put(cp.getColorProfileName(), cp); + addColorProfile(cp); } else { getLogger().warn("color-profile-name required for color profile"); } @@ -104,6 +96,18 @@ public class Declarations extends FObj { childNodes = null; } + private void addColorProfile(ColorProfile cp) { + if (colorProfiles == null) { + colorProfiles = new java.util.HashMap(); + } + if (colorProfiles.get(cp.getColorProfileName()) != null) { + // duplicate names + getLogger().warn("Duplicate fo:color-profile profile name: " + + cp.getColorProfileName()); + } + colorProfiles.put(cp.getColorProfileName(), cp); + } + /** * @see org.apache.fop.fo.FObj#getName() */ @@ -117,4 +121,22 @@ public class Declarations extends FObj { public int getNameId() { return FO_DECLARATIONS; } + + /** + * Return ColorProfile with given name. + * + * @param cpName Name of ColorProfile, i.e. the value of the color-profile-name attribute of + * the fo:color-profile element + * @return The org.apache.fop.fo.pagination.ColorProfile object associated with this + * color-profile-name or null + */ + public ColorProfile getColorProfile(String cpName) { + ColorProfile profile = null; + if (this.colorProfiles != null) { + profile = (ColorProfile)this.colorProfiles.get(cpName); + } + return profile; + } + + } diff --git a/src/java/org/apache/fop/fo/properties/ColorProperty.java b/src/java/org/apache/fop/fo/properties/ColorProperty.java index bc42ff451..c49546862 100644 --- a/src/java/org/apache/fop/fo/properties/ColorProperty.java +++ b/src/java/org/apache/fop/fo/properties/ColorProperty.java @@ -21,6 +21,7 @@ package org.apache.fop.fo.properties; import java.awt.Color; +import org.apache.fop.apps.FOUserAgent; import org.apache.fop.fo.FObj; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.expr.PropertyException; @@ -74,7 +75,9 @@ public class ColorProperty extends Property { if (p instanceof ColorProperty) { return p; } - Color val = p.getColor(); + FObj fobj = (fo == null ? propertyList.getFObj() : fo); + FOUserAgent ua = (fobj == null ? null : fobj.getUserAgent()); + Color val = p.getColor(ua); if (val != null) { return new ColorProperty(val); } @@ -83,17 +86,17 @@ public class ColorProperty extends Property { } - /** * Set the color given a particular String. For a full List of supported * values please see ColorUtil. * + * @param foUserAgent FOP user agent * @param value RGB value as String to be parsed * @throws PropertyException if the value can't be parsed * @see ColorUtil#parseColorString(String) */ - public ColorProperty(String value) throws PropertyException { - this.color = ColorUtil.parseColorString(value); + public ColorProperty(FOUserAgent foUserAgent, String value) throws PropertyException { + this.color = ColorUtil.parseColorString(foUserAgent, value); } /** @@ -107,9 +110,10 @@ public class ColorProperty extends Property { /** * Returns an AWT instance of this color + * @param foUserAgent FOP user agent * @return float the AWT color represented by this ColorType instance */ - public Color getColor() { + public Color getColor(FOUserAgent foUserAgent) { return color; } @@ -117,7 +121,7 @@ public class ColorProperty extends Property { * @see java.lang.Object#toString() */ public String toString() { - return ColorUtil.colorTOsRGBString(color); + return ColorUtil.colorToString(color); } /** diff --git a/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java b/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java index 5d7d36b4b..53b90fea9 100755 --- a/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java +++ b/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java @@ -144,7 +144,8 @@ public class CommonBorderPaddingBackground implements Cloneable { public CommonBorderPaddingBackground(PropertyList pList, FObj fobj) throws PropertyException { backgroundAttachment = pList.get(Constants.PR_BACKGROUND_ATTACHMENT).getEnum(); - backgroundColor = pList.get(Constants.PR_BACKGROUND_COLOR).getColor(); + backgroundColor = pList.get(Constants.PR_BACKGROUND_COLOR).getColor( + fobj == null ? null : fobj.getUserAgent()); if (backgroundColor.getAlpha() == 0) { backgroundColor = null; } @@ -206,9 +207,12 @@ public class CommonBorderPaddingBackground implements Cloneable { // If style = none, force width to 0, don't get Color (spec 7.7.20) int style = pList.get(styleProp).getEnum(); if (style != Constants.EN_NONE) { + FOUserAgent ua = (pList == null) + ? null + : (pList.getFObj() == null ? null : pList.getFObj().getUserAgent()); setBorderInfo(new BorderInfo(style, pList.get(widthProp).getCondLength(), - pList.get(colorProp).getColor()), side); + pList.get(colorProp).getColor(ua)), side); } } @@ -290,7 +294,7 @@ public class CommonBorderPaddingBackground implements Cloneable { public Color getBorderColor(int side) { if (borderInfo[side] != null) { - return borderInfo[side].mColor; + return borderInfo[side].getColor(); } else { return null; } diff --git a/src/java/org/apache/fop/fo/properties/CommonTextDecoration.java b/src/java/org/apache/fop/fo/properties/CommonTextDecoration.java index 37aa474f0..d56d9a101 100644 --- a/src/java/org/apache/fop/fo/properties/CommonTextDecoration.java +++ b/src/java/org/apache/fop/fo/properties/CommonTextDecoration.java @@ -23,6 +23,8 @@ import java.util.Iterator; import java.util.List; import java.awt.Color; + +import org.apache.fop.apps.FOUserAgent; import org.apache.fop.fo.Constants; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.expr.PropertyException; @@ -76,6 +78,9 @@ public class CommonTextDecoration { while (i.hasNext()) { Property prop = (Property)i.next(); int propEnum = prop.getEnum(); + FOUserAgent ua = (pList == null) + ? null + : (pList.getFObj() == null ? null : pList.getFObj().getUserAgent()); if (propEnum == Constants.EN_NONE) { if (deco != null) { deco.decoration = 0; @@ -86,33 +91,33 @@ public class CommonTextDecoration { deco = new CommonTextDecoration(); } deco.decoration |= UNDERLINE; - deco.underColor = pList.get(Constants.PR_COLOR).getColor(); + deco.underColor = pList.get(Constants.PR_COLOR).getColor(ua); } else if (propEnum == Constants.EN_NO_UNDERLINE) { if (deco != null) { deco.decoration &= OVERLINE | LINE_THROUGH | BLINK; - deco.underColor = pList.get(Constants.PR_COLOR).getColor(); + deco.underColor = pList.get(Constants.PR_COLOR).getColor(ua); } } else if (propEnum == Constants.EN_OVERLINE) { if (deco == null) { deco = new CommonTextDecoration(); } deco.decoration |= OVERLINE; - deco.overColor = pList.get(Constants.PR_COLOR).getColor(); + deco.overColor = pList.get(Constants.PR_COLOR).getColor(ua); } else if (propEnum == Constants.EN_NO_OVERLINE) { if (deco != null) { deco.decoration &= UNDERLINE | LINE_THROUGH | BLINK; - deco.overColor = pList.get(Constants.PR_COLOR).getColor(); + deco.overColor = pList.get(Constants.PR_COLOR).getColor(ua); } } else if (propEnum == Constants.EN_LINE_THROUGH) { if (deco == null) { deco = new CommonTextDecoration(); } deco.decoration |= LINE_THROUGH; - deco.throughColor = pList.get(Constants.PR_COLOR).getColor(); + deco.throughColor = pList.get(Constants.PR_COLOR).getColor(ua); } else if (propEnum == Constants.EN_NO_LINE_THROUGH) { if (deco != null) { deco.decoration &= UNDERLINE | OVERLINE | BLINK; - deco.throughColor = pList.get(Constants.PR_COLOR).getColor(); + deco.throughColor = pList.get(Constants.PR_COLOR).getColor(ua); } } else if (propEnum == Constants.EN_BLINK) { if (deco == null) { diff --git a/src/java/org/apache/fop/fo/properties/NumberProperty.java b/src/java/org/apache/fop/fo/properties/NumberProperty.java index b2fdcce68..c8d44e417 100644 --- a/src/java/org/apache/fop/fo/properties/NumberProperty.java +++ b/src/java/org/apache/fop/fo/properties/NumberProperty.java @@ -21,6 +21,7 @@ package org.apache.fop.fo.properties; import java.awt.Color; +import org.apache.fop.apps.FOUserAgent; import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.Numeric; import org.apache.fop.datatypes.PercentBaseContext; @@ -176,9 +177,10 @@ public class NumberProperty extends Property implements Numeric { /** * Convert NumberProperty to a Color. Not sure why this is needed. + * @param foUserAgent FOUserAgent * @return Color that corresponds to black */ - public Color getColor() { + public Color getColor(FOUserAgent foUserAgent) { // TODO: Implement somehow // Convert numeric value to color ??? // Convert to hexadecimal and then try to make it into a color? diff --git a/src/java/org/apache/fop/fo/properties/Property.java b/src/java/org/apache/fop/fo/properties/Property.java index 432a14811..0cab9da01 100644 --- a/src/java/org/apache/fop/fo/properties/Property.java +++ b/src/java/org/apache/fop/fo/properties/Property.java @@ -25,6 +25,7 @@ import java.awt.Color; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.fop.apps.FOUserAgent; import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.Numeric; import org.apache.fop.fo.Constants; @@ -74,9 +75,10 @@ public class Property { /** * This method expects to be overridden by subclasses + * @param foUserAgent FOP user agent * @return ColorType property value */ - public Color getColor() { + public Color getColor(FOUserAgent foUserAgent) { return null; } diff --git a/src/java/org/apache/fop/pdf/PDFColor.java b/src/java/org/apache/fop/pdf/PDFColor.java index 2892f8e0d..ac644650c 100644 --- a/src/java/org/apache/fop/pdf/PDFColor.java +++ b/src/java/org/apache/fop/pdf/PDFColor.java @@ -19,10 +19,15 @@ package org.apache.fop.pdf; -// Java +import java.awt.Color; +import java.awt.color.ColorSpace; +import java.awt.color.ICC_ColorSpace; +import java.io.IOException; import java.util.List; import java.util.ArrayList; +import org.apache.fop.util.ColorExt; + /** * PDF Color object. * This is used to output color to a PDF content stream. @@ -38,6 +43,12 @@ public class PDFColor extends PDFPathPaint { private double magenta = -1.0; private double yellow = -1.0; private double black = -1.0; + + // TODO - It would probably be better to reorganize PDFPathPaint/PDFColor/PDFColorSpace + // class hierarchy. However, at this early stages of my FOP understanding, I can + // not really oversee the consequences of such a switch (nor whether it would be + // appropriate). + private ColorExt colorExt = null; /** * Create a PDF color with double values ranging from 0 to 1 @@ -54,20 +65,90 @@ public class PDFColor extends PDFPathPaint { this.green = theGreen; this.blue = theBlue; } - + + /** + * Create PDFColor for the given document and based on the java.awt.Color object + * + * In case the java.awt.Color is an instance of the ColorExt class a PDFICCStream is added to + * the PDFDocument that is being created + * + * @param pdfDoc PDFDocument that is being created + * @param col Color object from which to create this PDFColor + */ + public PDFColor(PDFDocument pdfDoc, Color col) { + this(col); + // TODO - 1) There is a potential conflict when FOP and Batik elements use the same color + // profile name for different profiles. + // 2) In case the same color profile is used with different names it will be + // included multiple times in the PDF + // + if (colorExt != null + && pdfDoc.getResources().getColorSpace(colorExt.getIccProfileName()) == null) { + PDFICCStream pdfIccStream = new PDFICCStream(); + ColorSpace ceCs = colorExt.getOrigColorSpace(); + try { + pdfIccStream.setColorSpace(((ICC_ColorSpace)ceCs).getProfile(), null); + pdfIccStream.setData( + ((ICC_ColorSpace)colorExt.getColorSpace()).getProfile().getData()); + } catch (IOException ioe) { + log.error("Failed to set profile data for " + colorExt.getIccProfileName()); + } + pdfDoc.registerObject(pdfIccStream); + pdfDoc.getFactory().makeICCBasedColorSpace( + null, colorExt.getIccProfileName(), pdfIccStream); + if (log.isInfoEnabled()) { + log.info("Adding PDFICCStream " + colorExt.getIccProfileName() + + " for " + colorExt.getIccProfileSrc()); + } + } + } + /** * Create a PDF color from a java.awt.Color object. + * + * Different Color objects are handled differently. Cases recognized are. + * + * 1. CMYK color + * 2. ColorExt color + * 3. 'Normal' java.awt.Color (RGB case assumed) * - * @param col the sRGB color + * @param col the java.awt.Color object for which to create a PDFColor object */ public PDFColor(java.awt.Color col) { - this.colorSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); - float[] comps = new float[3]; - comps = col.getColorComponents(comps); - - this.red = comps[0]; - this.green = comps[1]; - this.blue = comps[2]; + ColorSpace cs = col.getColorSpace(); + ColorExt ce = null; + if (col instanceof ColorExt) { + ce = (ColorExt)col; + cs = ce.getOrigColorSpace(); + } + if (cs != null && cs.getType() == ColorSpace.TYPE_CMYK) { + // CMYK case + this.colorSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_CMYK); + float[] cmyk = (ce == null + ? col.getColorComponents(null) + : ce.getOriginalColorComponents()); + this.cyan = cmyk[0]; + this.magenta = cmyk[1]; + this.yellow = cmyk[2]; + this.black = cmyk[3]; + } else if (ce != null) { + // ColorExt (ICC) case + this.colorExt = ce; + float[] rgb = col.getRGBColorComponents(null); + this.red = rgb[0]; + this.green = rgb[1]; + this.blue = rgb[2]; + // TODO - See earlier todo + this.colorSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); + } else { + // Default (RGB) Color + this.colorSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); + float[] comps = new float[3]; + comps = col.getColorComponents(comps); + this.red = comps[0]; + this.green = comps[1]; + this.blue = comps[2]; + } } /** @@ -268,9 +349,26 @@ public class PDFColor extends PDFPathPaint { public String getColorSpaceOut(boolean fillNotStroke) { StringBuffer p = new StringBuffer(""); - double tempDouble; - - if (this.colorSpace.getColorSpace() + if (this.colorExt != null) { + if (fillNotStroke) { + p.append("/" + this.colorExt.getIccProfileName() + " cs "); + } else { + p.append("/" + this.colorExt.getIccProfileName() + " CS "); + } + float[] colorArgs; + colorArgs = this.colorExt.getOriginalColorComponents(); + if (colorArgs == null) { + colorArgs = this.colorExt.getColorComponents(null); + } + for (int ix = 0; ix < colorArgs.length; ix++) { + p.append(colorArgs[ix] + " "); + } + if (fillNotStroke) { + p.append("sc\n"); + } else { + p.append("SC\n"); + } + } else if (this.colorSpace.getColorSpace() == PDFDeviceColorSpace.DEVICE_RGB) { // colorspace is RGB // according to pdfspec 12.1 p.399 // if the colors are the same then just use the g or G operator @@ -454,7 +552,8 @@ public class PDFColor extends PDFPathPaint { } PDFColor color = (PDFColor)obj; - if (color.red == this.red && color.green == this.green + if (color.red == this.red + && color.green == this.green && color.blue == this.blue) { return true; } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index ae75b9290..966aef2e5 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -1331,7 +1331,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * written to the current stream. */ protected void setColor(Color col, boolean fill, StringBuffer pdf) { - PDFColor color = new PDFColor(col); + PDFColor color = new PDFColor(this.pdfDoc, col); closeText(); diff --git a/src/java/org/apache/fop/render/xml/XMLRenderer.java b/src/java/org/apache/fop/render/xml/XMLRenderer.java index c81dd1c1d..c1f85be87 100644 --- a/src/java/org/apache/fop/render/xml/XMLRenderer.java +++ b/src/java/org/apache/fop/render/xml/XMLRenderer.java @@ -384,7 +384,7 @@ public class XMLRenderer extends PrintRenderer { } } else if (clazz.equals(Color.class)) { Color c = (Color)value; - addAttribute(name, ColorUtil.colorTOsRGBString(c)); + addAttribute(name, ColorUtil.colorToString(c)); } else if (key == Trait.START_INDENT || key == Trait.END_INDENT) { if (((Integer)value).intValue() != 0) { addAttribute(name, value.toString()); diff --git a/src/java/org/apache/fop/svg/PDFGraphics2D.java b/src/java/org/apache/fop/svg/PDFGraphics2D.java index 0e821975d..254a93cba 100644 --- a/src/java/org/apache/fop/svg/PDFGraphics2D.java +++ b/src/java/org/apache/fop/svg/PDFGraphics2D.java @@ -42,6 +42,7 @@ import org.apache.fop.fonts.LazyFont; import org.apache.fop.image.JpegImage; import org.apache.fop.fonts.CIDFont; import org.apache.fop.render.pdf.FopPDFImage; +import org.apache.fop.util.ColorExt; import org.apache.xmlgraphics.java2d.AbstractGraphics2D; import org.apache.xmlgraphics.java2d.GraphicContext; @@ -821,8 +822,12 @@ public class PDFGraphics2D extends AbstractGraphics2D { */ protected void applyColor(Color col, boolean fill) { preparePainting(); + Color c = col; - if (c.getColorSpace().getType() + if (col instanceof ColorExt) { + PDFColor currentColour = new PDFColor(this.pdfDoc, col); + currentStream.write(currentColour.getColorSpaceOut(fill)); + } else if (c.getColorSpace().getType() == ColorSpace.TYPE_RGB) { PDFColor currentColour = new PDFColor(c.getRed(), c.getGreen(), c.getBlue()); diff --git a/src/java/org/apache/fop/traits/BorderProps.java b/src/java/org/apache/fop/traits/BorderProps.java index 2a00920eb..64c31ff88 100644 --- a/src/java/org/apache/fop/traits/BorderProps.java +++ b/src/java/org/apache/fop/traits/BorderProps.java @@ -23,6 +23,7 @@ import java.awt.Color; import java.io.Serializable; import java.util.StringTokenizer; +import org.apache.fop.apps.FOUserAgent; import org.apache.fop.fo.Constants; import org.apache.fop.fo.expr.PropertyException; import org.apache.fop.util.ColorUtil; @@ -154,10 +155,11 @@ public class BorderProps implements Serializable { /** * Returns a BorderProps represtation of a string of the format as written by * BorderProps.toString(). + * @param foUserAgent FOP user agent caching ICC profiles * @param s the string * @return a BorderProps instance */ - public static BorderProps valueOf(String s) { + public static BorderProps valueOf(FOUserAgent foUserAgent, String s) { if (s.startsWith("(") && s.endsWith(")")) { s = s.substring(1, s.length() - 1); StringTokenizer st = new StringTokenizer(s, ","); @@ -175,7 +177,7 @@ public class BorderProps implements Serializable { } Color c; try { - c = ColorUtil.parseColorString(color); + c = ColorUtil.parseColorString(foUserAgent, color); } catch (PropertyException e) { throw new IllegalArgumentException(e.getMessage()); } @@ -192,7 +194,7 @@ public class BorderProps implements Serializable { sbuf.append('('); sbuf.append(getStyleString()); sbuf.append(','); - sbuf.append(ColorUtil.colorTOsRGBString(color)); + sbuf.append(ColorUtil.colorToString(color)); sbuf.append(','); sbuf.append(width); if (mode != SEPARATE) { diff --git a/src/java/org/apache/fop/util/CMYKColorSpace.java b/src/java/org/apache/fop/util/CMYKColorSpace.java index 00a3f32a2..655c0b3d9 100644 --- a/src/java/org/apache/fop/util/CMYKColorSpace.java +++ b/src/java/org/apache/fop/util/CMYKColorSpace.java @@ -51,7 +51,10 @@ public class CMYKColorSpace extends ColorSpace { * @see java.awt.color.ColorSpace#toRGB(float[]) */ public float[] toRGB(float[] colorvalue) { - throw new UnsupportedOperationException("NYI"); + return new float [] { + (1 - colorvalue[0]) * (1 - colorvalue[3]), + (1 - colorvalue[1]) * (1 - colorvalue[3]), + (1 - colorvalue[2]) * (1 - colorvalue[3])}; } /** diff --git a/src/java/org/apache/fop/util/ColorExt.java b/src/java/org/apache/fop/util/ColorExt.java new file mode 100644 index 000000000..bd2c95a33 --- /dev/null +++ b/src/java/org/apache/fop/util/ColorExt.java @@ -0,0 +1,189 @@ +/* + * 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.util; + +import java.awt.Color; +import java.awt.color.ColorSpace; + +/** + * Color helper class. + * <p> + * This class extends java.awt.Color class keeping track of the original color + * property values specified by the fo user in a rgb-icc call. + */ +public final class ColorExt extends Color { + // + private static final long serialVersionUID = 1L; + + // Values of fop-rgb-icc arguments + private float rgbReplacementRed; + private float rgbReplacementGreen; + private float rgbReplacementBlue; + + private String iccProfileName; + private String iccProfileSrc; + private ColorSpace colorSpace; + + private float[] colorValues; + + /* + * Helper for createFromFoRgbIcc + */ + private ColorExt(ColorSpace colorSpace, float[] colorValues, float opacity) { + super(colorSpace, colorValues, opacity); + } + + /* + * Helper for createFromSvgIccColor + */ + private ColorExt(float red, float green, float blue, float opacity) { + super(red, green, blue, opacity); + } + + /** + * Create ColorExt object backup up FO's rgb-icc color function + * + * @param redReplacement + * Red part of RGB replacement color that will be used when ICC + * profile can not be loaded + * @param greenReplacement + * Green part of RGB replacement color that will be used when ICC + * profile can not be loaded + * @param blueReplacement + * Blue part of RGB replacement color that will be used when ICC + * profile can not be loaded + * @param profileName + * Name of ICC profile + * @param profileSrc + * Source of ICC profile + * @param colorSpace + * ICC ColorSpace for the ICC profile + * @param iccValues + * color values + * @return the requested color object + */ + public static ColorExt createFromFoRgbIcc(float redReplacement, + float greenReplacement, float blueReplacement, String profileName, + String profileSrc, ColorSpace colorSpace, float[] iccValues) { + ColorExt ce = new ColorExt(colorSpace, iccValues, 1.0f); + ce.rgbReplacementRed = redReplacement; + ce.rgbReplacementGreen = greenReplacement; + ce.rgbReplacementBlue = blueReplacement; + ce.iccProfileName = profileName; + ce.iccProfileSrc = profileSrc; + ce.colorSpace = colorSpace; + ce.colorValues = iccValues; + return ce; + } + + /** + * Create ColorExt object backing up SVG's icc-color function. + * + * @param red + * Red value resulting from the conversion from the user provided + * (icc) color values to the batik (rgb) color space + * @param green + * Green value resulting from the conversion from the user + * provided (icc) color values to the batik (rgb) color space + * @param blue + * Blue value resulting from the conversion from the user + * provided (icc) color values to the batik (rgb) color space + * @param opacity + * Opacity + * @param profileName + * ICC profile name + * @param profileHref + * the URI to the color profile + * @param profileCS + * ICC ColorSpace profile + * @param colorValues + * ICC color values + * @return the requested color object + */ + public static ColorExt createFromSvgIccColor(float red, float green, + float blue, float opacity, String profileName, String profileHref, + ColorSpace profileCS, float[] colorValues) { + ColorExt ce = new ColorExt(red, green, blue, opacity); + ce.rgbReplacementRed = -1; + ce.rgbReplacementGreen = -1; + ce.rgbReplacementBlue = -1; + ce.iccProfileName = profileName; + ce.iccProfileSrc = profileHref; + ce.colorSpace = profileCS; + ce.colorValues = colorValues; + return ce; + + } + + /** + * Get ICC profile name + * + * @return ICC profile name + */ + public String getIccProfileName() { + return this.iccProfileName; + } + + /** + * Get ICC profile source + * + * @return ICC profile source + */ + public String getIccProfileSrc() { + return this.iccProfileSrc; + } + + /** + * @return the original ColorSpace + */ + public ColorSpace getOrigColorSpace() { + return this.colorSpace; + } + + /** + * @return the original color values + */ + public float[] getOriginalColorComponents() { + return this.colorValues; + } + + /** + * Create string representation of fop-rgb-icc function call to map this + * ColorExt settings + * @return the string representing the internal fop-rgb-icc() function call + */ + public String toFunctionCall() { + StringBuffer sb = new StringBuffer(40); + sb.append("fop-rgb-icc("); + sb.append(this.rgbReplacementRed + ","); + sb.append(this.rgbReplacementGreen + ","); + sb.append(this.rgbReplacementBlue + ","); + sb.append(this.iccProfileName + ","); + sb.append("\"" + this.iccProfileSrc + "\""); + float[] colorComponents = this.getColorComponents(null); + for (int ix = 0; ix < colorComponents.length; ix++) { + sb.append(","); + sb.append(colorComponents[ix]); + } + sb.append(")"); + return sb.toString(); + } + +} diff --git a/src/java/org/apache/fop/util/ColorUtil.java b/src/java/org/apache/fop/util/ColorUtil.java index 652924025..37762b1e8 100644 --- a/src/java/org/apache/fop/util/ColorUtil.java +++ b/src/java/org/apache/fop/util/ColorUtil.java @@ -20,10 +20,16 @@ package org.apache.fop.util; import java.awt.Color; +import java.awt.color.ColorSpace; import java.util.Collections; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.StringTokenizer; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.fop.apps.FOUserAgent; import org.apache.fop.fo.expr.PropertyException; /** @@ -31,19 +37,21 @@ import org.apache.fop.fo.expr.PropertyException; * <p> * This class supports parsing string values into color values and creating * color values for strings. It provides a list of standard color names. - * <p> - * TODO: Add support for color Profiles. */ public final class ColorUtil { /** + * * keeps all the predefined and parsed colors. * <p> * This map is used to predefine given colors, as well as speeding up * parsing of already parsed colors. */ private static Map colorMap = null; - + + /** Logger instance */ + protected static Log log = LogFactory.getLog(ColorUtil.class); + static { initializeColorMap(); } @@ -68,8 +76,11 @@ public final class ColorUtil { * <li>system-color(colorname)</li> * <li>transparent</li> * <li>colorname</li> + * <li>fop-rgb-icc</li> + * <li>cmyk</li> * </ul> * + * @param foUserAgent FOUserAgent object * @param value * the string to parse. * @return a Color representing the string if possible @@ -77,7 +88,8 @@ public final class ColorUtil { * if the string is not parsable or does not follow any of the * given formats. */ - public static Color parseColorString(String value) throws PropertyException { + public static Color parseColorString(FOUserAgent foUserAgent, String value) + throws PropertyException { if (value == null) { return null; } @@ -96,18 +108,23 @@ public final class ColorUtil { parsedColor = parseAsJavaAWTColor(value); } else if (value.startsWith("system-color(")) { parsedColor = parseAsSystemColor(value); + } else if (value.startsWith("fop-rgb-icc")) { + parsedColor = parseAsFopRgbIcc(foUserAgent, value); + } else if (value.startsWith("cmyk")) { + parsedColor = parseAsCMYK(value); } - + if (parsedColor == null) { - throw new PropertyException("Unkown Color: " + value); + throw new PropertyException("Unknown Color: " + value); } colorMap.put(value, parsedColor); } - // TODO: Check if this is really necessary - return new Color(parsedColor.getRed(), parsedColor.getGreen(), - parsedColor.getBlue(), parsedColor.getAlpha()); + // TODO - Returned Color object can be one from the static colorMap cache. + // That means it should be treated as read only for the rest of its lifetime. + // Not sure that is the case though. + return parsedColor; } /** @@ -282,6 +299,174 @@ public final class ColorUtil { } /** + * Parse a color specified using the fop-rgb-icc() function. + * + * @param value the function call + * @return a color if possible + * @throws PropertyException if the format is wrong. + */ + private static Color parseAsFopRgbIcc(FOUserAgent foUserAgent, String value) + throws PropertyException { + Color parsedColor; + int poss = value.indexOf("("); + int pose = value.indexOf(")"); + if (poss != -1 && pose != -1) { + value = value.substring(poss + 1, pose); + StringTokenizer st = new StringTokenizer(value, ","); + try { + float red = 0.0f, green = 0.0f, blue = 0.0f; + if (st.hasMoreTokens()) { + String str = st.nextToken().trim(); + red = Float.parseFloat(str); + } + if (st.hasMoreTokens()) { + String str = st.nextToken().trim(); + green = Float.parseFloat(str); + } + if (st.hasMoreTokens()) { + String str = st.nextToken().trim(); + blue = Float.parseFloat(str); + } + /* Verify rgb replacement arguments */ + 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"); + } + /* Get and verify ICC profile name */ + String iccProfileName = null; + if (st.hasMoreTokens()) { + iccProfileName = st.nextToken().trim(); + } + if (iccProfileName == null || iccProfileName.length() == 0) { + throw new PropertyException("ICC profile name missing"); + } + /* Get and verify ICC profile source */ + String iccProfileSrc = null; + if (st.hasMoreTokens()) { + iccProfileSrc = st.nextToken().trim(); + // Strip quotes + iccProfileSrc = iccProfileSrc.substring(1, iccProfileSrc.length() - 1); + } + if (iccProfileSrc == null || iccProfileSrc.length() == 0) { + throw new PropertyException("ICC profile source missing"); + } + /* ICC profile arguments */ + List iccArgList = new LinkedList(); + while (st.hasMoreTokens()) { + String str = st.nextToken().trim(); + iccArgList.add(new Float(str)); + } + /* Copy ICC profile arguments from list to array */ + float[] iccComponents = new float[iccArgList.size()]; + for (int ix = 0; ix < iccArgList.size(); ix++) { + iccComponents[ix] = ((Float)iccArgList.get(ix)).floatValue(); + } + /* Ask FOP factory to get ColorSpace for the specified ICC profile source */ + ColorSpace colorSpace = (foUserAgent != null + ? foUserAgent.getFactory().getColorSpace( + foUserAgent.getBaseURL(), iccProfileSrc) : null); + if (colorSpace != null) { + // ColorSpace available - create ColorExt (keeps track of replacement rgb + // values for possible later colorTOsRGBString call + parsedColor = ColorExt.createFromFoRgbIcc(red, green, blue, + iccProfileName, iccProfileSrc, colorSpace, iccComponents); + } else { + // ICC profile could not be loaded - use rgb replacement values */ + log.warn("Color profile '" + iccProfileSrc + + "' not found. Using rgb replacement values."); + parsedColor = new Color(red, green, blue); + } + } catch (Exception e) { + throw new PropertyException( + "Arguments to rgb-icc() must be [0..255] or [0%..100%]"); + } + } else { + throw new PropertyException("Unknown color format: " + value + + ". Must be fop-rgb-icc(r,g,b,NCNAME,\"src\",....)"); + } + return parsedColor; + } + + /** + * Parse a color given with the cmyk() function. + * + * @param value + * the complete line + * @return a color if possible + * @throws PropertyException + * if the format is wrong. + */ + private static Color parseAsCMYK(String value) throws PropertyException { + Color parsedColor; + int poss = value.indexOf("("); + int pose = value.indexOf(")"); + if (poss != -1 && pose != -1) { + value = value.substring(poss + 1, pose); + StringTokenizer st = new StringTokenizer(value, ","); + try { + float cyan = 0.0f, magenta = 0.0f, yellow = 0.0f, black = 0.0f; + if (st.hasMoreTokens()) { + String str = st.nextToken().trim(); + if (str.endsWith("%")) { + cyan = Float.parseFloat(str.substring(0, + str.length() - 1)) / 100.0f; + } else { + cyan = Float.parseFloat(str); + } + } + if (st.hasMoreTokens()) { + String str = st.nextToken().trim(); + if (str.endsWith("%")) { + magenta = Float.parseFloat(str.substring(0, + str.length() - 1)) / 100.0f; + } else { + magenta = Float.parseFloat(str); + } + } + if (st.hasMoreTokens()) { + String str = st.nextToken().trim(); + if (str.endsWith("%")) { + yellow = Float.parseFloat(str.substring(0, + str.length() - 1)) / 100.0f; + } else { + yellow = Float.parseFloat(str); + } + } + if (st.hasMoreTokens()) { + String str = st.nextToken().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"); + } + float[] cmyk = new float[] {cyan, magenta, yellow, black}; + CMYKColorSpace cmykCs = CMYKColorSpace.getInstance(); + float[] rgb = cmykCs.toRGB(cmyk); + parsedColor = ColorExt.createFromFoRgbIcc(rgb[0], rgb[1], rgb[2], + null, "#CMYK", cmykCs, cmyk); + + + } catch (Exception e) { + throw new PropertyException( + "Arguments to cmyk() must be in the range [0%-100%] or [0.0-1.0]"); + } + } else { + throw new PropertyException("Unknown color format: " + value + + ". Must be cmyk(c,m,y,k)"); + } + return parsedColor; + } + + /** * Creates a re-parsable string representation of the given color. * <p> * First, the color will be converted into the sRGB colorspace. It will then @@ -291,33 +476,42 @@ public final class ColorUtil { * the color to represent. * @return a re-parsable string representadion. */ - public static String colorTOsRGBString(Color color) { - StringBuffer sbuf = new StringBuffer(10); - sbuf.append('#'); - String s = Integer.toHexString(color.getRed()); - if (s.length() == 1) { - sbuf.append('0'); - } - sbuf.append(s); - s = Integer.toHexString(color.getGreen()); - if (s.length() == 1) { - sbuf.append('0'); - } - sbuf.append(s); - s = Integer.toHexString(color.getBlue()); - if (s.length() == 1) { - sbuf.append('0'); - } - sbuf.append(s); - if (color.getAlpha() != 255) { - s = Integer.toHexString(color.getAlpha()); + public static String colorToString(Color color) { + ColorSpace cs = color.getColorSpace(); + if (cs != null && cs.getType() == ColorSpace.TYPE_CMYK) { + StringBuffer sbuf = new StringBuffer(24); + float[] cmyk = color.getColorComponents(null); + sbuf.append("cmyk(" + cmyk[0] + "," + cmyk[1] + "," + cmyk[2] + "," + cmyk[3] + ")"); + return sbuf.toString(); + } else if (color instanceof ColorExt) { + return ((ColorExt)color).toFunctionCall(); + } else { + StringBuffer sbuf = new StringBuffer(); + sbuf.append('#'); + String s = Integer.toHexString(color.getRed()); + if (s.length() == 1) { + sbuf.append('0'); + } + sbuf.append(s); + s = Integer.toHexString(color.getGreen()); if (s.length() == 1) { sbuf.append('0'); } sbuf.append(s); + s = Integer.toHexString(color.getBlue()); + if (s.length() == 1) { + sbuf.append('0'); + } + sbuf.append(s); + if (color.getAlpha() != 255) { + s = Integer.toHexString(color.getAlpha()); + if (s.length() == 1) { + sbuf.append('0'); + } + sbuf.append(s); + } + return sbuf.toString(); } - return sbuf.toString(); - } /** |