/*
 * 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: PDFColorHandler.java 1761020 2016-09-16 11:17:35Z ssteiner $ */

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.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 Color instance.
/** * 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 Map<String, PDFCIELabColorSpace> cieLabColorSpaces;
Create a new instance for the given PDFResources
Params:
  • resources – the PDF resources
/** * Create a new instance for the given {@link PDFResources} * @param resources the PDF resources */
public PDFColorHandler(PDFResources resources) { this.resources = resources; } 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.
Params:
  • codeBuffer – the target buffer to receive the color selection code
  • color – the color
  • fill – true for fill color, false for stroke color
/** * 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) { 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); } } 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) { 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 (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 establishDeviceCMYK(StringBuffer codeBuffer, Color color, boolean fill) { 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"); } }