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

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