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