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.

PDFRenderingUtil.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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.render.pdf;
  19. import java.awt.color.ICC_Profile;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.OutputStream;
  23. import java.net.URL;
  24. import java.util.Map;
  25. import javax.xml.transform.Source;
  26. import javax.xml.transform.stream.StreamSource;
  27. import org.apache.commons.io.IOUtils;
  28. import org.apache.commons.logging.Log;
  29. import org.apache.commons.logging.LogFactory;
  30. import org.apache.xmlgraphics.xmp.Metadata;
  31. import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter;
  32. import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema;
  33. import org.apache.fop.accessibility.Accessibility;
  34. import org.apache.fop.apps.FOUserAgent;
  35. import org.apache.fop.fo.extensions.xmp.XMPMetadata;
  36. import org.apache.fop.pdf.PDFAMode;
  37. import org.apache.fop.pdf.PDFConformanceException;
  38. import org.apache.fop.pdf.PDFDictionary;
  39. import org.apache.fop.pdf.PDFDocument;
  40. import org.apache.fop.pdf.PDFEncryptionManager;
  41. import org.apache.fop.pdf.PDFEncryptionParams;
  42. import org.apache.fop.pdf.PDFICCBasedColorSpace;
  43. import org.apache.fop.pdf.PDFICCStream;
  44. import org.apache.fop.pdf.PDFInfo;
  45. import org.apache.fop.pdf.PDFMetadata;
  46. import org.apache.fop.pdf.PDFNumsArray;
  47. import org.apache.fop.pdf.PDFOutputIntent;
  48. import org.apache.fop.pdf.PDFPageLabels;
  49. import org.apache.fop.pdf.PDFXMode;
  50. import org.apache.fop.util.ColorProfileUtil;
  51. /**
  52. * Utility class which enables all sorts of features that are not directly connected to the
  53. * normal rendering process.
  54. */
  55. class PDFRenderingUtil implements PDFConfigurationConstants {
  56. /** logging instance */
  57. private static Log log = LogFactory.getLog(PDFRenderingUtil.class);
  58. private FOUserAgent userAgent;
  59. /** the PDF Document being created */
  60. protected PDFDocument pdfDoc;
  61. /** the PDF/A mode (Default: disabled) */
  62. protected PDFAMode pdfAMode = PDFAMode.DISABLED;
  63. /** the PDF/X mode (Default: disabled) */
  64. protected PDFXMode pdfXMode = PDFXMode.DISABLED;
  65. /** the (optional) encryption parameters */
  66. protected PDFEncryptionParams encryptionParams;
  67. /** Registry of PDF filters */
  68. protected Map filterMap;
  69. /** the ICC stream used as output profile by this document for PDF/A and PDF/X functionality. */
  70. protected PDFICCStream outputProfile;
  71. /** the default sRGB color space. */
  72. protected PDFICCBasedColorSpace sRGBColorSpace;
  73. /** controls whether the sRGB color space should be installed */
  74. protected boolean disableSRGBColorSpace = false;
  75. /** Optional URI to an output profile to be used. */
  76. protected String outputProfileURI;
  77. PDFRenderingUtil(FOUserAgent userAgent) {
  78. this.userAgent = userAgent;
  79. initialize();
  80. }
  81. private static boolean booleanValueOf(Object obj) {
  82. if (obj instanceof Boolean) {
  83. return ((Boolean)obj).booleanValue();
  84. } else if (obj instanceof String) {
  85. return Boolean.valueOf((String)obj).booleanValue();
  86. } else {
  87. throw new IllegalArgumentException("Boolean or \"true\" or \"false\" expected.");
  88. }
  89. }
  90. private void initialize() {
  91. PDFEncryptionParams params
  92. = (PDFEncryptionParams)userAgent.getRendererOptions().get(ENCRYPTION_PARAMS);
  93. if (params != null) {
  94. this.encryptionParams = params; //overwrite if available
  95. }
  96. String pwd;
  97. pwd = (String)userAgent.getRendererOptions().get(USER_PASSWORD);
  98. if (pwd != null) {
  99. if (encryptionParams == null) {
  100. this.encryptionParams = new PDFEncryptionParams();
  101. }
  102. this.encryptionParams.setUserPassword(pwd);
  103. }
  104. pwd = (String)userAgent.getRendererOptions().get(OWNER_PASSWORD);
  105. if (pwd != null) {
  106. if (encryptionParams == null) {
  107. this.encryptionParams = new PDFEncryptionParams();
  108. }
  109. this.encryptionParams.setOwnerPassword(pwd);
  110. }
  111. Object setting;
  112. setting = userAgent.getRendererOptions().get(NO_PRINT);
  113. if (setting != null) {
  114. if (encryptionParams == null) {
  115. this.encryptionParams = new PDFEncryptionParams();
  116. }
  117. this.encryptionParams.setAllowPrint(!booleanValueOf(setting));
  118. }
  119. setting = userAgent.getRendererOptions().get(NO_COPY_CONTENT);
  120. if (setting != null) {
  121. if (encryptionParams == null) {
  122. this.encryptionParams = new PDFEncryptionParams();
  123. }
  124. this.encryptionParams.setAllowCopyContent(!booleanValueOf(setting));
  125. }
  126. setting = userAgent.getRendererOptions().get(NO_EDIT_CONTENT);
  127. if (setting != null) {
  128. if (encryptionParams == null) {
  129. this.encryptionParams = new PDFEncryptionParams();
  130. }
  131. this.encryptionParams.setAllowEditContent(!booleanValueOf(setting));
  132. }
  133. setting = userAgent.getRendererOptions().get(NO_ANNOTATIONS);
  134. if (setting != null) {
  135. if (encryptionParams == null) {
  136. this.encryptionParams = new PDFEncryptionParams();
  137. }
  138. this.encryptionParams.setAllowEditAnnotations(!booleanValueOf(setting));
  139. }
  140. String s = (String)userAgent.getRendererOptions().get(PDF_A_MODE);
  141. if (s != null) {
  142. this.pdfAMode = PDFAMode.valueOf(s);
  143. }
  144. if (this.pdfAMode.isPDFA1LevelA()) {
  145. //Enable accessibility if PDF/A-1a is enabled because it requires tagged PDF.
  146. userAgent.getRendererOptions().put(Accessibility.ACCESSIBILITY, Boolean.TRUE);
  147. }
  148. s = (String)userAgent.getRendererOptions().get(PDF_X_MODE);
  149. if (s != null) {
  150. this.pdfXMode = PDFXMode.valueOf(s);
  151. }
  152. s = (String)userAgent.getRendererOptions().get(KEY_OUTPUT_PROFILE);
  153. if (s != null) {
  154. this.outputProfileURI = s;
  155. }
  156. setting = userAgent.getRendererOptions().get(KEY_DISABLE_SRGB_COLORSPACE);
  157. if (setting != null) {
  158. this.disableSRGBColorSpace = booleanValueOf(setting);
  159. }
  160. }
  161. public FOUserAgent getUserAgent() {
  162. return this.userAgent;
  163. }
  164. /**
  165. * Sets the PDF/A mode for the PDF renderer.
  166. * @param mode the PDF/A mode
  167. */
  168. public void setAMode(PDFAMode mode) {
  169. this.pdfAMode = mode;
  170. }
  171. /**
  172. * Sets the PDF/X mode for the PDF renderer.
  173. * @param mode the PDF/X mode
  174. */
  175. public void setXMode(PDFXMode mode) {
  176. this.pdfXMode = mode;
  177. }
  178. /**
  179. * Sets the output color profile for the PDF renderer.
  180. * @param outputProfileURI the URI to the output color profile
  181. */
  182. public void setOutputProfileURI(String outputProfileURI) {
  183. this.outputProfileURI = outputProfileURI;
  184. }
  185. /**
  186. * Enables or disables the default sRGB color space needed for the PDF document to preserve
  187. * the sRGB colors used in XSL-FO.
  188. * @param disable true to disable, false to enable
  189. */
  190. public void setDisableSRGBColorSpace(boolean disable) {
  191. this.disableSRGBColorSpace = disable;
  192. }
  193. /**
  194. * Sets the filter map to be used by the PDF renderer.
  195. * @param filterMap the filter map
  196. */
  197. public void setFilterMap(Map filterMap) {
  198. this.filterMap = filterMap;
  199. }
  200. /**
  201. * Sets the encryption parameters used by the PDF renderer.
  202. * @param encryptionParams the encryption parameters
  203. */
  204. public void setEncryptionParams(PDFEncryptionParams encryptionParams) {
  205. this.encryptionParams = encryptionParams;
  206. }
  207. private void updateInfo() {
  208. PDFInfo info = pdfDoc.getInfo();
  209. info.setCreator(userAgent.getCreator());
  210. info.setCreationDate(userAgent.getCreationDate());
  211. info.setAuthor(userAgent.getAuthor());
  212. info.setTitle(userAgent.getTitle());
  213. info.setSubject(userAgent.getSubject());
  214. info.setKeywords(userAgent.getKeywords());
  215. }
  216. private void updatePDFProfiles() {
  217. pdfDoc.getProfile().setPDFAMode(this.pdfAMode);
  218. pdfDoc.getProfile().setPDFXMode(this.pdfXMode);
  219. }
  220. private void addsRGBColorSpace() throws IOException {
  221. if (disableSRGBColorSpace) {
  222. if (this.pdfAMode != PDFAMode.DISABLED
  223. || this.pdfXMode != PDFXMode.DISABLED
  224. || this.outputProfileURI != null) {
  225. throw new IllegalStateException("It is not possible to disable the sRGB color"
  226. + " space if PDF/A or PDF/X functionality is enabled or an"
  227. + " output profile is set!");
  228. }
  229. } else {
  230. if (this.sRGBColorSpace != null) {
  231. return;
  232. }
  233. //Map sRGB as default RGB profile for DeviceRGB
  234. this.sRGBColorSpace = PDFICCBasedColorSpace.setupsRGBAsDefaultRGBColorSpace(pdfDoc);
  235. }
  236. }
  237. private void addDefaultOutputProfile() throws IOException {
  238. if (this.outputProfile != null) {
  239. return;
  240. }
  241. ICC_Profile profile;
  242. InputStream in = null;
  243. if (this.outputProfileURI != null) {
  244. this.outputProfile = pdfDoc.getFactory().makePDFICCStream();
  245. Source src = getUserAgent().resolveURI(this.outputProfileURI);
  246. if (src == null) {
  247. throw new IOException("Output profile not found: " + this.outputProfileURI);
  248. }
  249. if (src instanceof StreamSource) {
  250. in = ((StreamSource)src).getInputStream();
  251. } else {
  252. in = new URL(src.getSystemId()).openStream();
  253. }
  254. try {
  255. profile = ICC_Profile.getInstance(in);
  256. } finally {
  257. IOUtils.closeQuietly(in);
  258. }
  259. this.outputProfile.setColorSpace(profile, null);
  260. } else {
  261. //Fall back to sRGB profile
  262. outputProfile = sRGBColorSpace.getICCStream();
  263. }
  264. }
  265. /**
  266. * Adds an OutputIntent to the PDF as mandated by PDF/A-1 when uncalibrated color spaces
  267. * are used (which is true if we use DeviceRGB to represent sRGB colors).
  268. * @throws IOException in case of an I/O problem
  269. */
  270. private void addPDFA1OutputIntent() throws IOException {
  271. addDefaultOutputProfile();
  272. String desc = ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile());
  273. PDFOutputIntent outputIntent = pdfDoc.getFactory().makeOutputIntent();
  274. outputIntent.setSubtype(PDFOutputIntent.GTS_PDFA1);
  275. outputIntent.setDestOutputProfile(this.outputProfile);
  276. outputIntent.setOutputConditionIdentifier(desc);
  277. outputIntent.setInfo(outputIntent.getOutputConditionIdentifier());
  278. pdfDoc.getRoot().addOutputIntent(outputIntent);
  279. }
  280. /**
  281. * Adds an OutputIntent to the PDF as mandated by PDF/X when uncalibrated color spaces
  282. * are used (which is true if we use DeviceRGB to represent sRGB colors).
  283. * @throws IOException in case of an I/O problem
  284. */
  285. private void addPDFXOutputIntent() throws IOException {
  286. addDefaultOutputProfile();
  287. String desc = ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile());
  288. int deviceClass = this.outputProfile.getICCProfile().getProfileClass();
  289. if (deviceClass != ICC_Profile.CLASS_OUTPUT) {
  290. throw new PDFConformanceException(pdfDoc.getProfile().getPDFXMode() + " requires that"
  291. + " the DestOutputProfile be an Output Device Profile. "
  292. + desc + " does not match that requirement.");
  293. }
  294. PDFOutputIntent outputIntent = pdfDoc.getFactory().makeOutputIntent();
  295. outputIntent.setSubtype(PDFOutputIntent.GTS_PDFX);
  296. outputIntent.setDestOutputProfile(this.outputProfile);
  297. outputIntent.setOutputConditionIdentifier(desc);
  298. outputIntent.setInfo(outputIntent.getOutputConditionIdentifier());
  299. pdfDoc.getRoot().addOutputIntent(outputIntent);
  300. }
  301. public void renderXMPMetadata(XMPMetadata metadata) {
  302. Metadata docXMP = metadata.getMetadata();
  303. Metadata fopXMP = PDFMetadata.createXMPFromPDFDocument(pdfDoc);
  304. //Merge FOP's own metadata into the one from the XSL-FO document
  305. fopXMP.mergeInto(docXMP);
  306. XMPBasicAdapter xmpBasic = XMPBasicSchema.getAdapter(docXMP);
  307. //Metadata was changed so update metadata date
  308. xmpBasic.setMetadataDate(new java.util.Date());
  309. PDFMetadata.updateInfoFromMetadata(docXMP, pdfDoc.getInfo());
  310. PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata(
  311. docXMP, metadata.isReadOnly());
  312. pdfDoc.getRoot().setMetadata(pdfMetadata);
  313. }
  314. public void generateDefaultXMPMetadata() {
  315. if (pdfDoc.getRoot().getMetadata() == null) {
  316. //If at this time no XMP metadata for the overall document has been set, create it
  317. //from the PDFInfo object.
  318. Metadata xmp = PDFMetadata.createXMPFromPDFDocument(pdfDoc);
  319. PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata(
  320. xmp, true);
  321. pdfDoc.getRoot().setMetadata(pdfMetadata);
  322. }
  323. }
  324. public PDFDocument setupPDFDocument(OutputStream out) throws IOException {
  325. if (this.pdfDoc != null) {
  326. throw new IllegalStateException("PDFDocument already set up");
  327. }
  328. this.pdfDoc = new PDFDocument(
  329. userAgent.getProducer() != null ? userAgent.getProducer() : "");
  330. updateInfo();
  331. updatePDFProfiles();
  332. pdfDoc.setFilterMap(filterMap);
  333. pdfDoc.outputHeader(out);
  334. //Setup encryption if necessary
  335. PDFEncryptionManager.setupPDFEncryption(encryptionParams, pdfDoc);
  336. addsRGBColorSpace();
  337. if (this.outputProfileURI != null) {
  338. addDefaultOutputProfile();
  339. }
  340. if (pdfXMode != PDFXMode.DISABLED) {
  341. log.debug(pdfXMode + " is active.");
  342. log.warn("Note: " + pdfXMode
  343. + " support is work-in-progress and not fully implemented, yet!");
  344. addPDFXOutputIntent();
  345. }
  346. if (pdfAMode.isPDFA1LevelB()) {
  347. log.debug("PDF/A is active. Conformance Level: " + pdfAMode);
  348. addPDFA1OutputIntent();
  349. }
  350. return this.pdfDoc;
  351. }
  352. /**
  353. * Generates a page label in the PDF document.
  354. * @param pageIndex the index of the page
  355. * @param pageNumber the formatted page number
  356. */
  357. public void generatePageLabel(int pageIndex, String pageNumber) {
  358. //Produce page labels
  359. PDFPageLabels pageLabels = this.pdfDoc.getRoot().getPageLabels();
  360. if (pageLabels == null) {
  361. //Set up PageLabels
  362. pageLabels = this.pdfDoc.getFactory().makePageLabels();
  363. this.pdfDoc.getRoot().setPageLabels(pageLabels);
  364. }
  365. PDFNumsArray nums = pageLabels.getNums();
  366. PDFDictionary dict = new PDFDictionary(nums);
  367. dict.put("P", pageNumber);
  368. //TODO If the sequence of generated page numbers were inspected, this could be
  369. //expressed in a more space-efficient way
  370. nums.put(pageIndex, dict);
  371. }
  372. }