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.3KB

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