From 02656ec73867455a217091cb60f6c8c1f25caa59 Mon Sep 17 00:00:00 2001 From: Simon Steiner Date: Thu, 1 Sep 2022 10:00:43 +0000 Subject: [PATCH] FOP-3091: Add transparency color support for PDF CMYK git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1903804 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/fop/fo/expr/PropertyParser.java | 3 +- .../fop/fo/expr/RGBICCColorFunction.java | 48 +++++++---- .../java/org/apache/fop/util/ColorUtil.java | 82 +++++++++++++------ .../fop/render/pdf/PDFPainterTestCase.java | 14 +++- .../apache/fop/util/ColorUtilTestCase.java | 8 ++ .../standard-testcases/color_1.xml | 4 + 6 files changed, 110 insertions(+), 49 deletions(-) diff --git a/fop-core/src/main/java/org/apache/fop/fo/expr/PropertyParser.java b/fop-core/src/main/java/org/apache/fop/fo/expr/PropertyParser.java index d13c3cea8..0ba6284ef 100644 --- a/fop-core/src/main/java/org/apache/fop/fo/expr/PropertyParser.java +++ b/fop-core/src/main/java/org/apache/fop/fo/expr/PropertyParser.java @@ -64,7 +64,8 @@ public final class PropertyParser extends PropertyTokenizer { FUNCTION_TABLE.put("proportional-column-width", new ProportionalColumnWidthFunction()); FUNCTION_TABLE.put("label-end", new LabelEndFunction()); FUNCTION_TABLE.put("body-start", new BodyStartFunction()); - FUNCTION_TABLE.put("rgb-icc", new RGBICCColorFunction()); + FUNCTION_TABLE.put("rgb-icc", new RGBICCColorFunction(false)); + FUNCTION_TABLE.put("fox-rgb-icc", new RGBICCColorFunction(true)); FUNCTION_TABLE.put("rgb-named-color", new RGBNamedColorFunction()); //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/fop-core/src/main/java/org/apache/fop/fo/expr/RGBICCColorFunction.java b/fop-core/src/main/java/org/apache/fop/fo/expr/RGBICCColorFunction.java index 57cf6a949..0c11070f8 100644 --- a/fop-core/src/main/java/org/apache/fop/fo/expr/RGBICCColorFunction.java +++ b/fop-core/src/main/java/org/apache/fop/fo/expr/RGBICCColorFunction.java @@ -30,6 +30,12 @@ import org.apache.fop.util.ColorUtil; * Implements the rgb-icc() function. */ class RGBICCColorFunction extends FunctionBase { + private int colors = 3; + public RGBICCColorFunction(boolean fox) { + if (fox) { + colors = 4; + } + } /** {@inheritDoc} */ public int getRequiredArgsCount() { @@ -49,12 +55,14 @@ class RGBICCColorFunction extends FunctionBase { } /** {@inheritDoc} */ - public Property eval(Property[] args, PropertyInfo pInfo) throws PropertyException { + public Property eval(Property[] args, + PropertyInfo pInfo) throws PropertyException { // Map color profile NCNAME to src from declarations/color-profile element - String colorProfileName = args[3].getString(); - Declarations decls = (pInfo.getFO() != null - ? pInfo.getFO().getRoot().getDeclarations() - : null); + String colorProfileName = args[colors].getString(); + if (colors == 4 && colorProfileName == null) { + throw new PropertyException("Alpha channel value missing"); + } + Declarations decls = pInfo.getFO().getRoot().getDeclarations(); ColorProfile cp = null; if (decls == null) { //function used in a color-specification @@ -79,32 +87,36 @@ class RGBICCColorFunction extends FunctionBase { } } String src = (cp != null ? cp.getSrc() : ""); - - float red = 0; - float green = 0; - float blue = 0; - red = args[0].getNumber().floatValue(); - green = args[1].getNumber().floatValue(); - blue = args[2].getNumber().floatValue(); + float red = args[0].getNumber().floatValue(); + float green = args[1].getNumber().floatValue(); + float blue = args[2].getNumber().floatValue(); + float alpha = 255; + if (colors == 4) { + alpha = args[3].getNumber().floatValue(); + } /* Verify rgb replacement arguments */ - if ((red < 0 || red > 255) || (green < 0 || green > 255) || (blue < 0 || blue > 255)) { + if ((red < 0 || red > 255) + || (green < 0 || green > 255) + || (blue < 0 || blue > 255)) { throw new PropertyException("Color values out of range. " + "Arguments to rgb-icc() must be [0..255] or [0%..100%]"); } // 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. - StringBuffer sb = new StringBuffer(); - sb.append("fop-rgb-icc("); + StringBuilder sb = new StringBuilder("fop-rgb-icc("); sb.append(red / 255f); sb.append(',').append(green / 255f); sb.append(',').append(blue / 255f); - for (int ix = 3; ix < args.length; ix++) { - if (ix == 3) { + if (colors == 4) { + sb.append("," + ColorUtil.ALPHA_PSEUDO_PROFILE + ",").append(alpha / 255f); + } + for (int i = colors; i < args.length; i++) { + if (i == colors) { sb.append(',').append(colorProfileName); sb.append(',').append(src); } else { - sb.append(',').append(args[ix]); + sb.append(',').append(args[i]); } } sb.append(")"); diff --git a/fop-core/src/main/java/org/apache/fop/util/ColorUtil.java b/fop-core/src/main/java/org/apache/fop/util/ColorUtil.java index 1117b6e38..43b1af656 100644 --- a/fop-core/src/main/java/org/apache/fop/util/ColorUtil.java +++ b/fop-core/src/main/java/org/apache/fop/util/ColorUtil.java @@ -24,7 +24,11 @@ import java.awt.color.ColorSpace; import java.awt.color.ICC_ColorSpace; import java.awt.color.ICC_Profile; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.ListIterator; import java.util.Map; import org.apache.commons.logging.Log; @@ -61,6 +65,8 @@ public final class ColorUtil { /** The name for the Separation pseudo-profile used for spot colors */ public static final String SEPARATION_PSEUDO_PROFILE = "#Separation"; + public static final String ALPHA_PSEUDO_PROFILE = "#alpha"; + /** * Keeps all the predefined and parsed colors. *

@@ -290,13 +296,19 @@ public final class ColorUtil { return component; } - private static Color parseFallback(String[] args, String value) throws PropertyException { - float red = parseComponent1(args[0], value); - float green = parseComponent1(args[1], value); - float blue = parseComponent1(args[2], value); + private static Color parseFallback(ListIterator args, String value) throws PropertyException { + float red = parseComponent1(args.next(), value); + float green = parseComponent1(args.next(), value); + float blue = parseComponent1(args.next(), value); + float alpha = 1; + if (ALPHA_PSEUDO_PROFILE.equals(args.next())) { + alpha = parseComponent1(args.next(), value); + } else { + args.previous(); + } //Sun's classlib rounds differently with this constructor than when converting to sRGB //via CIE XYZ. - Color sRGB = new Color(red, green, blue); + Color sRGB = new Color(red, green, blue, alpha); return sRGB; } @@ -356,49 +368,48 @@ public final class ColorUtil { int poss = value.indexOf("("); int pose = value.indexOf(")"); if (poss != -1 && pose != -1) { - String[] args = value.substring(poss + 1, pose).split(","); + String[] argsStr = value.substring(poss + 1, pose).split(","); try { - if (args.length < 5) { + if (argsStr.length < 5) { throw new PropertyException("Too few arguments for rgb-icc() function"); } + ListIterator args = Arrays.asList(argsStr).listIterator(); //Set up fallback sRGB value Color sRGB = parseFallback(args, value); /* Get and verify ICC profile name */ - String iccProfileName = args[3].trim(); - if (iccProfileName == null || "".equals(iccProfileName)) { + String iccProfileName = args.next().trim(); + if (iccProfileName.equals("")) { throw new PropertyException("ICC profile name missing"); } ColorSpace colorSpace = null; String iccProfileSrc = null; + String iccProfile = args.next(); + String namedColorSpace = args.next(); if (isPseudoProfile(iccProfileName)) { if (CMYK_PSEUDO_PROFILE.equalsIgnoreCase(iccProfileName)) { colorSpace = ColorSpaces.getDeviceCMYKColorSpace(); } else if (SEPARATION_PSEUDO_PROFILE.equalsIgnoreCase(iccProfileName)) { - colorSpace = new NamedColorSpace(args[5], sRGB, + colorSpace = new NamedColorSpace(namedColorSpace, sRGB, SEPARATION_PSEUDO_PROFILE, null); } else { assert false : "Incomplete implementation"; } } else { /* Get and verify ICC profile source */ - iccProfileSrc = args[4].trim(); - if (iccProfileSrc == null || "".equals(iccProfileSrc)) { + iccProfileSrc = iccProfile.trim(); + if (iccProfileSrc.equals("")) { throw new PropertyException("ICC profile source missing"); } iccProfileSrc = unescapeString(iccProfileSrc); } - /* ICC profile arguments */ - int componentStart = 4; - if (colorSpace instanceof NamedColorSpace) { - componentStart++; - } - float[] iccComponents = new float[args.length - componentStart - 1]; - for (int ix = componentStart; ++ix < args.length;) { - iccComponents[ix - componentStart - 1] = Float.parseFloat(args[ix].trim()); + + if (!(colorSpace instanceof NamedColorSpace)) { + args.previous(); } + float[] iccComponents = getICCComponents(args); if (colorSpace instanceof NamedColorSpace && iccComponents.length == 0) { iccComponents = new float[] {1.0f}; //full tint if not specified } @@ -415,10 +426,10 @@ public final class ColorUtil { if (ColorSpaces.isDeviceColorSpace(colorSpace)) { //Device-specific colors are handled differently: //sRGB is the primary color with the CMYK as the alternative - Color deviceColor = new Color(colorSpace, iccComponents, 1.0f); - float[] rgbComps = sRGB.getRGBColorComponents(null); + float[] rgbComps = sRGB.getRGBComponents(null); + Color deviceColor = new Color(colorSpace, iccComponents, rgbComps[3]); parsedColor = new ColorWithAlternatives( - rgbComps[0], rgbComps[1], rgbComps[2], + rgbComps[0], rgbComps[1], rgbComps[2], rgbComps[3], new Color[] {deviceColor}); } else { parsedColor = new ColorWithFallback( @@ -440,6 +451,21 @@ public final class ColorUtil { return parsedColor; } + private static float[] getICCComponents(ListIterator args) { + /* ICC profile arguments */ + List iccComponentsList = new ArrayList<>(); + while (args.hasNext()) { + iccComponentsList.add(Float.parseFloat(args.next().trim())); + } + float[] iccComponents = new float[iccComponentsList.size()]; + int i = 0; + for (float component : iccComponentsList) { + iccComponents[i] = component; + i++; + } + return iccComponents; + } + /** * Parse a color specified using the fop-rgb-named-color() function. * @@ -461,7 +487,7 @@ public final class ColorUtil { } //Set up fallback sRGB value - Color sRGB = parseFallback(args, value); + Color sRGB = parseFallback(Arrays.asList(args).listIterator(), value); /* Get and verify ICC profile name */ String iccProfileName = args[3].trim(); @@ -768,13 +794,15 @@ public final class ColorUtil { String functionName; Color fallbackColor = getsRGBFallback(color); - float[] rgb = fallbackColor.getColorComponents(null); - assert rgb.length == 3; - StringBuffer sb = new StringBuffer(40); + float[] rgb = fallbackColor.getComponents(null); + StringBuilder sb = new StringBuilder(40); sb.append("("); sb.append(rgb[0]).append(","); sb.append(rgb[1]).append(","); sb.append(rgb[2]).append(","); + if (rgb[3] != 1f) { + sb.append(ALPHA_PSEUDO_PROFILE + ",").append(rgb[3]).append(","); + } String profileName = origin.getProfileName(); sb.append(profileName).append(","); if (origin.getProfileURI() != null) { diff --git a/fop-core/src/test/java/org/apache/fop/render/pdf/PDFPainterTestCase.java b/fop-core/src/test/java/org/apache/fop/render/pdf/PDFPainterTestCase.java index 9a1872f5f..7b5d34bdc 100644 --- a/fop-core/src/test/java/org/apache/fop/render/pdf/PDFPainterTestCase.java +++ b/fop-core/src/test/java/org/apache/fop/render/pdf/PDFPainterTestCase.java @@ -48,6 +48,8 @@ import static org.mockito.Mockito.when; import org.apache.xmlgraphics.image.loader.Image; import org.apache.xmlgraphics.image.loader.ImageException; import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.java2d.color.ColorSpaces; +import org.apache.xmlgraphics.java2d.color.ColorWithAlternatives; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.FopFactory; @@ -474,6 +476,12 @@ public class PDFPainterTestCase { @Test public void testAlphaColor() throws Exception { + fillColor(new Color(0, 0, 0, 102), "0 g\n"); + Color deviceColor = new Color(ColorSpaces.getDeviceCMYKColorSpace(), new float[4], 0.4f); + fillColor(new ColorWithAlternatives(0, 0, 0, 102, new Color[]{deviceColor}), "0 0 0 0 k\n"); + } + + private void fillColor(Color color, String cmd) throws Exception { FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI()); foUserAgent = fopFactory.newFOUserAgent(); PDFDocumentHandler pdfDocumentHandler = new PDFDocumentHandler(new IFContext(foUserAgent)); @@ -481,7 +489,7 @@ public class PDFPainterTestCase { pdfDocumentHandler.startDocument(); pdfDocumentHandler.startPage(0, "", "", new Dimension()); PDFPainter pdfPainter = new PDFPainter(pdfDocumentHandler, null); - pdfPainter.fillRect(new Rectangle(10, 10), new Color(0, 0, 0, 128)); + pdfPainter.fillRect(new Rectangle(10, 10), color); PDFFilterList filters = pdfPainter.generator.getStream().getFilterList(); filters.setDisableAllFilters(true); ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -491,7 +499,7 @@ public class PDFPainterTestCase { + "q\n" + "1 0 0 -1 0 0 cm\n" + "/GS1 gs\n" - + "0 g\n" + + cmd + "0 0 0.01 0.01 re f\n" + "\n" + "endstream", bos.toString()); @@ -499,7 +507,7 @@ public class PDFPainterTestCase { pdfDocumentHandler.getCurrentPage().getGStates().iterator().next().output(bos); Assert.assertEquals(bos.toString(), "<<\n" + "/Type /ExtGState\n" - + "/ca 0.5019608\n" + + "/ca 0.4\n" + "/CA 1.0\n" + ">>"); } diff --git a/fop-core/src/test/java/org/apache/fop/util/ColorUtilTestCase.java b/fop-core/src/test/java/org/apache/fop/util/ColorUtilTestCase.java index 095d3a806..4a530257e 100644 --- a/fop-core/src/test/java/org/apache/fop/util/ColorUtilTestCase.java +++ b/fop-core/src/test/java/org/apache/fop/util/ColorUtilTestCase.java @@ -336,4 +336,12 @@ public class ColorUtilTestCase { assertEquals(colSpec, ColorUtil.colorToString(colActual)); } + + @Test + public void testAlphaColor() throws Exception { + String colSpec = "fop-rgb-icc(0.6,0.6,0.4,#alpha,0.4,#CMYK,,0.0,0.0,0.2,0.4)"; + ColorWithAlternatives colActual2 = (ColorWithAlternatives)ColorUtil.parseColorString(null, colSpec); + assertEquals(colActual2.getAlternativeColors()[0].getAlpha(), 102); + assertEquals(ColorUtil.colorToString(colActual2), colSpec); + } } diff --git a/fop/test/layoutengine/standard-testcases/color_1.xml b/fop/test/layoutengine/standard-testcases/color_1.xml index e10f46d19..436b5c4e8 100644 --- a/fop/test/layoutengine/standard-testcases/color_1.xml +++ b/fop/test/layoutengine/standard-testcases/color_1.xml @@ -42,6 +42,8 @@ color "rgb-icc(0%,100%,0%, unknown, 1, 0.5, 0)" color "cmyk(0%,0%,20%,40%)" (Khaki) color "rgb-icc(153, 153, 102, #CMYK, 0, 0, 0.2, 0.4)" (Khaki) + color "rgb-icc(1.0,0.8,0.0,#Separation,,Postgelb)" (Khaki) + color "fox-rgb-icc(153, 153, 102, 102, #CMYK, 0, 0, 0.2, 0.4)" (Khaki) @@ -58,5 +60,7 @@ + + -- 2.39.5