]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
FOP-3091: Add transparency color support for PDF CMYK
authorSimon Steiner <ssteiner@apache.org>
Thu, 1 Sep 2022 10:00:43 +0000 (10:00 +0000)
committerSimon Steiner <ssteiner@apache.org>
Thu, 1 Sep 2022 10:00:43 +0000 (10:00 +0000)
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1903804 13f79535-47bb-0310-9956-ffa450edef68

fop-core/src/main/java/org/apache/fop/fo/expr/PropertyParser.java
fop-core/src/main/java/org/apache/fop/fo/expr/RGBICCColorFunction.java
fop-core/src/main/java/org/apache/fop/util/ColorUtil.java
fop-core/src/test/java/org/apache/fop/render/pdf/PDFPainterTestCase.java
fop-core/src/test/java/org/apache/fop/util/ColorUtilTestCase.java
fop/test/layoutengine/standard-testcases/color_1.xml

index d13c3cea8daf78b70d55230cc33b7e309eccaf5d..0ba6284ef554abbb10bcfe070b0116316bc76ddd 100644 (file)
@@ -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!!!
index 57cf6a9497c7f4efed294c2b94c842e280a0d0b1..0c11070f8f970d6176138c52c6d0a0f4ab0b2378 100644 (file)
@@ -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(")");
index 1117b6e38d11bfb78601a6f7fbd8118bd81e3ecc..43b1af656c032b4916425ab550558ec115171303 100644 (file)
@@ -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.
      * <p>
@@ -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<String> 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<String> 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<String> args) {
+        /* ICC profile arguments */
+        List<Float> 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) {
index 9a1872f5f06d1a9adfe4b8148389693a8efd694b..7b5d34bdcc2dcf95cbdc84597043f090cd76de35 100644 (file)
@@ -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"
                 + ">>");
     }
index 095d3a80698edf1970292fa458f3b4a69dc4c87e..4a530257eccda7a3de7671175215b900b38f6d71 100644 (file)
@@ -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);
+    }
 }
index e10f46d1922befe8b19bb1d9594d6aefd4067905..436b5c4e858b4c05ca22562100ee7a5307f88211 100644 (file)
@@ -42,6 +42,8 @@
           <fo:block color="rgb-icc(0%,100%,0%, unknown, 1, 0.5, 0)">color "rgb-icc(0%,100%,0%, unknown, 1, 0.5, 0)"</fo:block>
           <fo:block color="cmyk(0%,0%,20%,40%)">color "cmyk(0%,0%,20%,40%)" (Khaki)</fo:block>
           <fo:block color="rgb-icc(153, 153, 102, #CMYK, 0, 0, 0.2, 0.4)">color "rgb-icc(153, 153, 102, #CMYK, 0, 0, 0.2, 0.4)" (Khaki)</fo:block>
+          <fo:block color="rgb-icc(153,153,0.0,#Separation,,Postgelb)">color "rgb-icc(1.0,0.8,0.0,#Separation,,Postgelb)" (Khaki)</fo:block>
+          <fo:block color="fox-rgb-icc(153, 153, 102, 102, #CMYK, 0, 0, 0.2, 0.4)">color "fox-rgb-icc(153, 153, 102, 102, #CMYK, 0, 0, 0.2, 0.4)" (Khaki)</fo:block>
         </fo:flow>
       </fo:page-sequence>
     </fo:root>
@@ -58,5 +60,7 @@
     <eval expected="#00ff00" xpath="//block[5]//text/@color"/>
     <eval expected="fop-rgb-icc(0.6,0.6,0.48000002,#CMYK,,0.0,0.0,0.2,0.4)" xpath="//block[6]//text/@color"/>
     <eval expected="fop-rgb-icc(0.6,0.6,0.4,#CMYK,,0.0,0.0,0.2,0.4)" xpath="//block[7]//text/@color"/>
+    <eval expected="fop-rgb-icc(0.6,0.6,0.0,#Separation,,Postgelb)" xpath="//block[8]//text/@color"/>
+    <eval expected="fop-rgb-icc(0.6,0.6,0.4,#alpha,0.4,#CMYK,,0.0,0.0,0.2,0.4)" xpath="//block[9]//text/@color"/>
   </checks>
 </testcase>