You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PDFColorHandler.java 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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. /* $Id$ */
  18. package org.apache.fop.pdf;
  19. import java.awt.Color;
  20. import java.awt.color.ColorSpace;
  21. import java.awt.color.ICC_ColorSpace;
  22. import java.awt.color.ICC_Profile;
  23. import java.util.Map;
  24. import org.apache.commons.logging.Log;
  25. import org.apache.commons.logging.LogFactory;
  26. import org.apache.xmlgraphics.java2d.color.CIELabColorSpace;
  27. import org.apache.xmlgraphics.java2d.color.ColorUtil;
  28. import org.apache.xmlgraphics.java2d.color.ColorWithAlternatives;
  29. import org.apache.xmlgraphics.java2d.color.DeviceCMYKColorSpace;
  30. import org.apache.xmlgraphics.java2d.color.NamedColorSpace;
  31. import org.apache.xmlgraphics.java2d.color.profile.ColorProfileUtil;
  32. import org.apache.xmlgraphics.util.DoubleFormatUtil;
  33. /**
  34. * This class handles the registration of color spaces and the generation of PDF code to select
  35. * the right colors given a {@link Color} instance.
  36. */
  37. public class PDFColorHandler {
  38. private Log log = LogFactory.getLog(PDFColorHandler.class);
  39. private PDFResources resources;
  40. private Map<String, PDFCIELabColorSpace> cieLabColorSpaces;
  41. /**
  42. * Create a new instance for the given {@link PDFResources}
  43. * @param resources the PDF resources
  44. */
  45. public PDFColorHandler(PDFResources resources) {
  46. this.resources = resources;
  47. }
  48. private PDFDocument getDocument() {
  49. return this.resources.getDocumentSafely();
  50. }
  51. /**
  52. * Generates code to select the given color and handles the registration of color spaces in
  53. * PDF where necessary.
  54. * @param codeBuffer the target buffer to receive the color selection code
  55. * @param color the color
  56. * @param fill true for fill color, false for stroke color
  57. */
  58. public void establishColor(StringBuffer codeBuffer, Color color, boolean fill) {
  59. if (color instanceof ColorWithAlternatives) {
  60. ColorWithAlternatives colExt = (ColorWithAlternatives)color;
  61. //Alternate colors have priority
  62. Color[] alt = colExt.getAlternativeColors();
  63. for (Color col : alt) {
  64. boolean established = establishColorFromColor(codeBuffer, col, fill);
  65. if (established) {
  66. return;
  67. }
  68. }
  69. if (log.isDebugEnabled() && alt.length > 0) {
  70. log.debug("None of the alternative colors are supported. Using fallback: "
  71. + color);
  72. }
  73. }
  74. //Fallback
  75. boolean established = establishColorFromColor(codeBuffer, color, fill);
  76. if (!established) {
  77. establishDeviceRGB(codeBuffer, color, fill);
  78. }
  79. }
  80. private boolean establishColorFromColor(StringBuffer codeBuffer, Color color, boolean fill) {
  81. ColorSpace cs = color.getColorSpace();
  82. if (cs instanceof DeviceCMYKColorSpace) {
  83. establishDeviceCMYK(codeBuffer, color, fill);
  84. return true;
  85. } else if (!cs.isCS_sRGB()) {
  86. if (cs instanceof ICC_ColorSpace) {
  87. PDFICCBasedColorSpace pdfcs = getICCBasedColorSpace((ICC_ColorSpace)cs);
  88. establishColor(codeBuffer, pdfcs, color, fill);
  89. return true;
  90. } else if (cs instanceof NamedColorSpace) {
  91. PDFSeparationColorSpace sepcs = getSeparationColorSpace((NamedColorSpace)cs);
  92. establishColor(codeBuffer, sepcs, color, fill);
  93. return true;
  94. } else if (cs instanceof CIELabColorSpace) {
  95. CIELabColorSpace labcs = (CIELabColorSpace)cs;
  96. PDFCIELabColorSpace pdflab = getCIELabColorSpace(labcs);
  97. selectColorSpace(codeBuffer, pdflab, fill);
  98. float[] comps = color.getColorComponents(null);
  99. float[] nativeComps = labcs.toNativeComponents(comps);
  100. writeColor(codeBuffer, nativeComps, labcs.getNumComponents(), (fill ? "sc" : "SC"));
  101. return true;
  102. }
  103. }
  104. return false;
  105. }
  106. private PDFICCBasedColorSpace getICCBasedColorSpace(ICC_ColorSpace cs) {
  107. ICC_Profile profile = cs.getProfile();
  108. String desc = ColorProfileUtil.getICCProfileDescription(profile);
  109. if (log.isDebugEnabled()) {
  110. log.trace("ICC profile encountered: " + desc);
  111. }
  112. PDFICCBasedColorSpace pdfcs = this.resources.getICCColorSpaceByProfileName(desc);
  113. if (pdfcs == null) {
  114. //color space is not in the PDF, yet
  115. PDFFactory factory = getDocument().getFactory();
  116. PDFICCStream pdfICCStream = factory.makePDFICCStream();
  117. PDFDeviceColorSpace altSpace = PDFDeviceColorSpace.toPDFColorSpace(cs);
  118. pdfICCStream.setColorSpace(profile, altSpace);
  119. pdfcs = factory.makeICCBasedColorSpace(null, desc, pdfICCStream);
  120. }
  121. return pdfcs;
  122. }
  123. private PDFSeparationColorSpace getSeparationColorSpace(NamedColorSpace cs) {
  124. PDFName colorName = new PDFName(cs.getColorName());
  125. PDFSeparationColorSpace sepcs = (PDFSeparationColorSpace)this.resources.getColorSpace(
  126. colorName);
  127. if (sepcs == null) {
  128. //color space is not in the PDF, yet
  129. PDFFactory factory = getDocument().getFactory();
  130. sepcs = factory.makeSeparationColorSpace(null, cs);
  131. }
  132. return sepcs;
  133. }
  134. private PDFCIELabColorSpace getCIELabColorSpace(CIELabColorSpace labCS) {
  135. if (this.cieLabColorSpaces == null) {
  136. this.cieLabColorSpaces = new java.util.HashMap<String, PDFCIELabColorSpace>();
  137. }
  138. float[] wp = labCS.getWhitePoint();
  139. StringBuilder sb = new StringBuilder();
  140. for (int i = 0; i < 3; i++) {
  141. if (i > 0) {
  142. sb.append(',');
  143. }
  144. sb.append(wp[i]);
  145. }
  146. String key = sb.toString();
  147. PDFCIELabColorSpace cielab = this.cieLabColorSpaces.get(key);
  148. if (cielab == null) {
  149. //color space is not in the PDF, yet
  150. float[] wp1 = new float[] {wp[0] / 100f, wp[1] / 100f, wp[2] / 100f};
  151. cielab = new PDFCIELabColorSpace(wp1, null);
  152. getDocument().registerObject(cielab);
  153. this.resources.addColorSpace(cielab);
  154. this.cieLabColorSpaces.put(key, cielab);
  155. }
  156. return cielab;
  157. }
  158. private void establishColor(StringBuffer codeBuffer,
  159. PDFColorSpace pdfcs, Color color, boolean fill) {
  160. selectColorSpace(codeBuffer, pdfcs, fill);
  161. writeColor(codeBuffer, color, pdfcs.getNumComponents(), (fill ? "sc" : "SC"));
  162. }
  163. private void selectColorSpace(StringBuffer codeBuffer, PDFColorSpace pdfcs, boolean fill) {
  164. codeBuffer.append(new PDFName(pdfcs.getName()));
  165. if (fill) {
  166. codeBuffer.append(" cs ");
  167. } else {
  168. codeBuffer.append(" CS ");
  169. }
  170. }
  171. private void establishDeviceRGB(StringBuffer codeBuffer, Color color, boolean fill) {
  172. float[] comps;
  173. if (color.getColorSpace().isCS_sRGB()) {
  174. comps = color.getColorComponents(null);
  175. } else {
  176. if (log.isDebugEnabled()) {
  177. log.debug("Converting color to sRGB as a fallback: " + color);
  178. }
  179. ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
  180. comps = color.getColorComponents(sRGB, null);
  181. }
  182. if (ColorUtil.isGray(color)) {
  183. comps = new float[] {comps[0]}; //assuming that all components are the same
  184. writeColor(codeBuffer, comps, 1, (fill ? "g" : "G"));
  185. } else {
  186. writeColor(codeBuffer, comps, 3, (fill ? "rg" : "RG"));
  187. }
  188. }
  189. private void establishDeviceCMYK(StringBuffer codeBuffer, Color color, boolean fill) {
  190. writeColor(codeBuffer, color, 4, (fill ? "k" : "K"));
  191. }
  192. private void writeColor(StringBuffer codeBuffer, Color color, int componentCount,
  193. String command) {
  194. float[] comps = color.getColorComponents(null);
  195. writeColor(codeBuffer, comps, componentCount, command);
  196. }
  197. private void writeColor(StringBuffer codeBuffer, float[] comps, int componentCount,
  198. String command) {
  199. if (comps.length != componentCount) {
  200. throw new IllegalStateException("Color with unexpected component count encountered");
  201. }
  202. for (float comp : comps) {
  203. DoubleFormatUtil.formatDouble(comp, 4, 4, codeBuffer);
  204. codeBuffer.append(" ");
  205. }
  206. codeBuffer.append(command).append("\n");
  207. }
  208. }