Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

PDFRenderingUtil.java 15KB

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