1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 /* $Id: PDFColorHandler.java 1761020 2016-09-16 11:17:35Z ssteiner $ */
19 
20 package org.apache.fop.pdf;
21 
22 import java.awt.Color;
23 import java.awt.color.ColorSpace;
24 import java.awt.color.ICC_ColorSpace;
25 import java.awt.color.ICC_Profile;
26 import java.util.Map;
27 
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 
31 import org.apache.xmlgraphics.java2d.color.CIELabColorSpace;
32 import org.apache.xmlgraphics.java2d.color.ColorUtil;
33 import org.apache.xmlgraphics.java2d.color.ColorWithAlternatives;
34 import org.apache.xmlgraphics.java2d.color.DeviceCMYKColorSpace;
35 import org.apache.xmlgraphics.java2d.color.NamedColorSpace;
36 import org.apache.xmlgraphics.java2d.color.profile.ColorProfileUtil;
37 import org.apache.xmlgraphics.util.DoubleFormatUtil;
38 
39 /**
40  * This class handles the registration of color spaces and the generation of PDF code to select
41  * the right colors given a {@link Color} instance.
42  */
43 public class PDFColorHandler {
44 
45     private Log log = LogFactory.getLog(PDFColorHandler.class);
46 
47     private PDFResources resources;
48 
49     private Map<String, PDFCIELabColorSpace> cieLabColorSpaces;
50 
51     /**
52      * Create a new instance for the given {@link PDFResources}
53      * @param resources the PDF resources
54      */
PDFColorHandler(PDFResources resources)55     public PDFColorHandler(PDFResources resources) {
56         this.resources = resources;
57     }
58 
getDocument()59     private PDFDocument getDocument() {
60         return this.resources.getDocumentSafely();
61     }
62 
63     /**
64      * Generates code to select the given color and handles the registration of color spaces in
65      * PDF where necessary.
66      * @param codeBuffer the target buffer to receive the color selection code
67      * @param color the color
68      * @param fill true for fill color, false for stroke color
69      */
establishColor(StringBuffer codeBuffer, Color color, boolean fill)70     public void establishColor(StringBuffer codeBuffer, Color color, boolean fill) {
71         if (color instanceof ColorWithAlternatives) {
72             ColorWithAlternatives colExt = (ColorWithAlternatives)color;
73             //Alternate colors have priority
74             Color[] alt = colExt.getAlternativeColors();
75             for (Color col : alt) {
76                 boolean established = establishColorFromColor(codeBuffer, col, fill);
77                 if (established) {
78                     return;
79                 }
80             }
81             if (log.isDebugEnabled() && alt.length > 0) {
82                 log.debug("None of the alternative colors are supported. Using fallback: "
83                         + color);
84             }
85         }
86 
87         //Fallback
88         boolean established = establishColorFromColor(codeBuffer, color, fill);
89         if (!established) {
90             establishDeviceRGB(codeBuffer, color, fill);
91         }
92     }
93 
establishColorFromColor(StringBuffer codeBuffer, Color color, boolean fill)94     private boolean establishColorFromColor(StringBuffer codeBuffer, Color color, boolean fill) {
95         ColorSpace cs = color.getColorSpace();
96         if (cs instanceof DeviceCMYKColorSpace) {
97             establishDeviceCMYK(codeBuffer, color, fill);
98             return true;
99         } else if (!cs.isCS_sRGB()) {
100             if (cs instanceof ICC_ColorSpace) {
101                 PDFICCBasedColorSpace pdfcs = getICCBasedColorSpace((ICC_ColorSpace)cs);
102                 establishColor(codeBuffer, pdfcs, color, fill);
103                 return true;
104             } else if (cs instanceof NamedColorSpace) {
105                 PDFSeparationColorSpace sepcs = getSeparationColorSpace((NamedColorSpace)cs);
106                 establishColor(codeBuffer, sepcs, color, fill);
107                 return true;
108             } else if (cs instanceof CIELabColorSpace) {
109                 CIELabColorSpace labcs = (CIELabColorSpace)cs;
110                 PDFCIELabColorSpace pdflab = getCIELabColorSpace(labcs);
111                 selectColorSpace(codeBuffer, pdflab, fill);
112                 float[] comps = color.getColorComponents(null);
113                 float[] nativeComps = labcs.toNativeComponents(comps);
114                 writeColor(codeBuffer, nativeComps, labcs.getNumComponents(), (fill ? "sc" : "SC"));
115                 return true;
116             }
117         }
118         return false;
119     }
120 
getICCBasedColorSpace(ICC_ColorSpace cs)121     private PDFICCBasedColorSpace getICCBasedColorSpace(ICC_ColorSpace cs) {
122         ICC_Profile profile = cs.getProfile();
123         String desc = ColorProfileUtil.getICCProfileDescription(profile);
124         if (log.isDebugEnabled()) {
125             log.trace("ICC profile encountered: " + desc);
126         }
127         PDFICCBasedColorSpace pdfcs = this.resources.getICCColorSpaceByProfileName(desc);
128         if (pdfcs == null) {
129             //color space is not in the PDF, yet
130             PDFFactory factory = getDocument().getFactory();
131             PDFICCStream pdfICCStream = factory.makePDFICCStream();
132             PDFDeviceColorSpace altSpace = PDFDeviceColorSpace.toPDFColorSpace(cs);
133             pdfICCStream.setColorSpace(profile, altSpace);
134             pdfcs = factory.makeICCBasedColorSpace(null, desc, pdfICCStream);
135         }
136         return pdfcs;
137     }
138 
getSeparationColorSpace(NamedColorSpace cs)139     private PDFSeparationColorSpace getSeparationColorSpace(NamedColorSpace cs) {
140         PDFName colorName = new PDFName(cs.getColorName());
141         PDFSeparationColorSpace sepcs = (PDFSeparationColorSpace)this.resources.getColorSpace(
142                 colorName);
143         if (sepcs == null) {
144             //color space is not in the PDF, yet
145             PDFFactory factory = getDocument().getFactory();
146             sepcs = factory.makeSeparationColorSpace(null, cs);
147         }
148         return sepcs;
149     }
150 
getCIELabColorSpace(CIELabColorSpace labCS)151     private PDFCIELabColorSpace getCIELabColorSpace(CIELabColorSpace labCS) {
152         if (this.cieLabColorSpaces == null) {
153             this.cieLabColorSpaces = new java.util.HashMap<String, PDFCIELabColorSpace>();
154         }
155         float[] wp = labCS.getWhitePoint();
156         StringBuilder sb = new StringBuilder();
157         for (int i = 0; i < 3; i++) {
158             if (i > 0) {
159                 sb.append(',');
160             }
161             sb.append(wp[i]);
162         }
163         String key = sb.toString();
164         PDFCIELabColorSpace cielab = this.cieLabColorSpaces.get(key);
165         if (cielab == null) {
166             //color space is not in the PDF, yet
167             float[] wp1 = new float[] {wp[0] / 100f, wp[1] / 100f, wp[2] / 100f};
168             cielab = new PDFCIELabColorSpace(wp1, null);
169             getDocument().registerObject(cielab);
170             this.resources.addColorSpace(cielab);
171             this.cieLabColorSpaces.put(key, cielab);
172         }
173         return cielab;
174     }
175 
establishColor(StringBuffer codeBuffer, PDFColorSpace pdfcs, Color color, boolean fill)176     private void establishColor(StringBuffer codeBuffer,
177             PDFColorSpace pdfcs, Color color, boolean fill) {
178         selectColorSpace(codeBuffer, pdfcs, fill);
179         writeColor(codeBuffer, color, pdfcs.getNumComponents(), (fill ? "sc" : "SC"));
180     }
181 
selectColorSpace(StringBuffer codeBuffer, PDFColorSpace pdfcs, boolean fill)182     private void selectColorSpace(StringBuffer codeBuffer, PDFColorSpace pdfcs, boolean fill) {
183         codeBuffer.append(new PDFName(pdfcs.getName()));
184         if (fill)  {
185             codeBuffer.append(" cs ");
186         } else {
187             codeBuffer.append(" CS ");
188         }
189     }
190 
establishDeviceRGB(StringBuffer codeBuffer, Color color, boolean fill)191     private void establishDeviceRGB(StringBuffer codeBuffer, Color color, boolean fill) {
192         float[] comps;
193         if (color.getColorSpace().isCS_sRGB()) {
194             comps = color.getColorComponents(null);
195         } else {
196             if (log.isDebugEnabled()) {
197                 log.debug("Converting color to sRGB as a fallback: " + color);
198             }
199             ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
200             comps = color.getColorComponents(sRGB, null);
201         }
202         if (ColorUtil.isGray(color)) {
203             comps = new float[] {comps[0]}; //assuming that all components are the same
204             writeColor(codeBuffer, comps, 1, (fill ? "g" : "G"));
205         } else {
206             writeColor(codeBuffer, comps, 3, (fill ? "rg" : "RG"));
207         }
208     }
209 
establishDeviceCMYK(StringBuffer codeBuffer, Color color, boolean fill)210     private void establishDeviceCMYK(StringBuffer codeBuffer, Color color, boolean fill) {
211         writeColor(codeBuffer, color, 4, (fill ? "k" : "K"));
212     }
213 
writeColor(StringBuffer codeBuffer, Color color, int componentCount, String command)214     private void writeColor(StringBuffer codeBuffer, Color color, int componentCount,
215             String command) {
216         float[] comps = color.getColorComponents(null);
217         writeColor(codeBuffer, comps, componentCount, command);
218     }
219 
writeColor(StringBuffer codeBuffer, float[] comps, int componentCount, String command)220     private void writeColor(StringBuffer codeBuffer, float[] comps, int componentCount,
221             String command) {
222         if (comps.length != componentCount) {
223             throw new IllegalStateException("Color with unexpected component count encountered");
224         }
225         for (float comp : comps) {
226             DoubleFormatUtil.formatDouble(comp, 4, 4, codeBuffer);
227             codeBuffer.append(" ");
228         }
229         codeBuffer.append(command).append("\n");
230     }
231 
232 }
233