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 19KB

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