blob: 193bd6dfdd6a33152e3e4c377ad7b59c2e9744ce [file] [log] [blame]
/*
* 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.pdf;
import java.awt.Color;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlgraphics.java2d.color.CIELabColorSpace;
import org.apache.xmlgraphics.java2d.color.ColorUtil;
import org.apache.xmlgraphics.java2d.color.ColorWithAlternatives;
import org.apache.xmlgraphics.java2d.color.DeviceCMYKColorSpace;
import org.apache.xmlgraphics.java2d.color.NamedColorSpace;
import org.apache.xmlgraphics.java2d.color.profile.ColorProfileUtil;
import org.apache.xmlgraphics.util.DoubleFormatUtil;
/**
* This class handles the registration of color spaces and the generation of PDF code to select
* the right colors given a {@link Color} instance.
*/
public class PDFColorHandler {
private Log log = LogFactory.getLog(PDFColorHandler.class);
private PDFResources resources;
private PDFResourceContext resourceContext;
private Map<String, PDFCIELabColorSpace> cieLabColorSpaces;
/**
* Create a new instance for the given {@link PDFResources}
* @param resources the PDF resources
*/
public PDFColorHandler(PDFResources resources, PDFResourceContext resourceContext) {
this.resources = resources;
this.resourceContext = resourceContext;
}
private PDFDocument getDocument() {
return this.resources.getDocumentSafely();
}
/**
* Generates code to select the given color and handles the registration of color spaces in
* PDF where necessary.
* @param codeBuffer the target buffer to receive the color selection code
* @param color the color
* @param fill true for fill color, false for stroke color
*/
public void establishColor(StringBuffer codeBuffer, Color color, boolean fill, boolean alpha) {
if (color instanceof ColorWithAlternatives) {
ColorWithAlternatives colExt = (ColorWithAlternatives)color;
//Alternate colors have priority
Color[] alt = colExt.getAlternativeColors();
for (Color col : alt) {
boolean established = establishColorFromColor(codeBuffer, col, fill);
if (established) {
return;
}
}
if (log.isDebugEnabled() && alt.length > 0) {
log.debug("None of the alternative colors are supported. Using fallback: "
+ color);
}
}
//Fallback
boolean established = establishColorFromColor(codeBuffer, color, fill);
if (!established) {
establishDeviceRGB(codeBuffer, color, fill, alpha);
}
}
private boolean establishColorFromColor(StringBuffer codeBuffer, Color color, boolean fill) {
ColorSpace cs = color.getColorSpace();
if (cs instanceof DeviceCMYKColorSpace) {
establishDeviceCMYK(codeBuffer, color, fill);
return true;
} else if (!cs.isCS_sRGB()) {
if (cs instanceof ICC_ColorSpace) {
PDFICCBasedColorSpace pdfcs = getICCBasedColorSpace((ICC_ColorSpace)cs);
establishColor(codeBuffer, pdfcs, color, fill);
return true;
} else if (cs instanceof NamedColorSpace) {
PDFSeparationColorSpace sepcs = getSeparationColorSpace((NamedColorSpace)cs);
establishColor(codeBuffer, sepcs, color, fill);
return true;
} else if (cs instanceof CIELabColorSpace) {
CIELabColorSpace labcs = (CIELabColorSpace)cs;
PDFCIELabColorSpace pdflab = getCIELabColorSpace(labcs);
selectColorSpace(codeBuffer, pdflab, fill);
float[] comps = color.getColorComponents(null);
float[] nativeComps = labcs.toNativeComponents(comps);
writeColor(codeBuffer, nativeComps, labcs.getNumComponents(), (fill ? "sc" : "SC"));
return true;
}
}
return false;
}
private PDFICCBasedColorSpace getICCBasedColorSpace(ICC_ColorSpace cs) {
ICC_Profile profile = cs.getProfile();
String desc = ColorProfileUtil.getICCProfileDescription(profile);
if (log.isDebugEnabled()) {
log.trace("ICC profile encountered: " + desc);
}
PDFICCBasedColorSpace pdfcs = this.resources.getICCColorSpaceByProfileName(desc);
if (pdfcs == null) {
//color space is not in the PDF, yet
PDFFactory factory = getDocument().getFactory();
PDFICCStream pdfICCStream = factory.makePDFICCStream();
PDFDeviceColorSpace altSpace = PDFDeviceColorSpace.toPDFColorSpace(cs);
pdfICCStream.setColorSpace(profile, altSpace);
pdfcs = factory.makeICCBasedColorSpace(null, desc, pdfICCStream);
}
return pdfcs;
}
private PDFSeparationColorSpace getSeparationColorSpace(NamedColorSpace cs) {
PDFName colorName = new PDFName(cs.getColorName());
PDFSeparationColorSpace sepcs = (PDFSeparationColorSpace)this.resources.getColorSpace(
colorName);
if (sepcs == null) {
//color space is not in the PDF, yet
PDFFactory factory = getDocument().getFactory();
sepcs = factory.makeSeparationColorSpace(null, cs);
}
return sepcs;
}
private PDFCIELabColorSpace getCIELabColorSpace(CIELabColorSpace labCS) {
if (this.cieLabColorSpaces == null) {
this.cieLabColorSpaces = new java.util.HashMap<String, PDFCIELabColorSpace>();
}
float[] wp = labCS.getWhitePoint();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 3; i++) {
if (i > 0) {
sb.append(',');
}
sb.append(wp[i]);
}
String key = sb.toString();
PDFCIELabColorSpace cielab = this.cieLabColorSpaces.get(key);
if (cielab == null) {
//color space is not in the PDF, yet
float[] wp1 = new float[] {wp[0] / 100f, wp[1] / 100f, wp[2] / 100f};
cielab = new PDFCIELabColorSpace(wp1, null);
getDocument().registerObject(cielab);
this.resources.addColorSpace(cielab);
this.cieLabColorSpaces.put(key, cielab);
}
return cielab;
}
private void establishColor(StringBuffer codeBuffer,
PDFColorSpace pdfcs, Color color, boolean fill) {
selectColorSpace(codeBuffer, pdfcs, fill);
writeColor(codeBuffer, color, pdfcs.getNumComponents(), (fill ? "sc" : "SC"));
}
private void selectColorSpace(StringBuffer codeBuffer, PDFColorSpace pdfcs, boolean fill) {
codeBuffer.append(new PDFName(pdfcs.getName()));
if (fill) {
codeBuffer.append(" cs ");
} else {
codeBuffer.append(" CS ");
}
}
private void establishDeviceRGB(StringBuffer codeBuffer, Color color, boolean fill, boolean alpha) {
float[] comps;
if (color.getColorSpace().isCS_sRGB()) {
comps = color.getColorComponents(null);
} else {
if (log.isDebugEnabled()) {
log.debug("Converting color to sRGB as a fallback: " + color);
}
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
comps = color.getColorComponents(sRGB, null);
}
if (alpha) {
writeAlpha(color, codeBuffer);
}
if (ColorUtil.isGray(color)) {
comps = new float[] {comps[0]}; //assuming that all components are the same
writeColor(codeBuffer, comps, 1, (fill ? "g" : "G"));
} else {
writeColor(codeBuffer, comps, 3, (fill ? "rg" : "RG"));
}
}
private void writeAlpha(Color color, StringBuffer codeBuffer) {
int alpha = color.getAlpha();
if (alpha != 255 && alpha != 0 && resourceContext != null) {
Map<String, Float> vals = new HashMap<>();
vals.put(PDFGState.GSTATE_ALPHA_NONSTROKE, alpha / 255f);
vals.put(PDFGState.GSTATE_ALPHA_STROKE, 1.0f);
PDFGState gState = getDocument().getFactory().makeGState(vals, PDFGState.DEFAULT);
resourceContext.addGState(gState);
codeBuffer.append("/").append(gState.getName()).append(" gs\n");
}
}
private void establishDeviceCMYK(StringBuffer codeBuffer, Color color, boolean fill) {
writeAlpha(color, codeBuffer);
writeColor(codeBuffer, color, 4, (fill ? "k" : "K"));
}
private void writeColor(StringBuffer codeBuffer, Color color, int componentCount,
String command) {
float[] comps = color.getColorComponents(null);
writeColor(codeBuffer, comps, componentCount, command);
}
private void writeColor(StringBuffer codeBuffer, float[] comps, int componentCount,
String command) {
if (comps.length != componentCount) {
throw new IllegalStateException("Color with unexpected component count encountered");
}
for (float comp : comps) {
DoubleFormatUtil.formatDouble(comp, 4, 4, codeBuffer);
codeBuffer.append(" ");
}
codeBuffer.append(command).append("\n");
}
}