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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777
  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.URI;
  25. import java.net.URISyntaxException;
  26. import java.util.ArrayList;
  27. import java.util.Date;
  28. import java.util.EnumMap;
  29. import java.util.List;
  30. import java.util.Map;
  31. import java.util.TimeZone;
  32. import org.apache.commons.io.IOUtils;
  33. import org.apache.commons.logging.Log;
  34. import org.apache.commons.logging.LogFactory;
  35. import org.apache.xmlgraphics.java2d.color.profile.ColorProfileUtil;
  36. import org.apache.xmlgraphics.util.DateFormatUtil;
  37. import org.apache.xmlgraphics.xmp.Metadata;
  38. import org.apache.xmlgraphics.xmp.schemas.DublinCoreSchema;
  39. import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter;
  40. import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema;
  41. import org.apache.fop.accessibility.Accessibility;
  42. import org.apache.fop.apps.FOUserAgent;
  43. import org.apache.fop.apps.io.InternalResourceResolver;
  44. import org.apache.fop.fo.extensions.xmp.XMPMetadata;
  45. import org.apache.fop.pdf.PDFAMode;
  46. import org.apache.fop.pdf.PDFArray;
  47. import org.apache.fop.pdf.PDFConformanceException;
  48. import org.apache.fop.pdf.PDFDictionary;
  49. import org.apache.fop.pdf.PDFDocument;
  50. import org.apache.fop.pdf.PDFEmbeddedFile;
  51. import org.apache.fop.pdf.PDFEmbeddedFiles;
  52. import org.apache.fop.pdf.PDFEncryptionManager;
  53. import org.apache.fop.pdf.PDFEncryptionParams;
  54. import org.apache.fop.pdf.PDFFileSpec;
  55. import org.apache.fop.pdf.PDFICCBasedColorSpace;
  56. import org.apache.fop.pdf.PDFICCStream;
  57. import org.apache.fop.pdf.PDFInfo;
  58. import org.apache.fop.pdf.PDFLayer;
  59. import org.apache.fop.pdf.PDFMetadata;
  60. import org.apache.fop.pdf.PDFName;
  61. import org.apache.fop.pdf.PDFNames;
  62. import org.apache.fop.pdf.PDFNavigator;
  63. import org.apache.fop.pdf.PDFNull;
  64. import org.apache.fop.pdf.PDFNumber;
  65. import org.apache.fop.pdf.PDFOutputIntent;
  66. import org.apache.fop.pdf.PDFPage;
  67. import org.apache.fop.pdf.PDFPageLabels;
  68. import org.apache.fop.pdf.PDFReference;
  69. import org.apache.fop.pdf.PDFSetOCGStateAction;
  70. import org.apache.fop.pdf.PDFText;
  71. import org.apache.fop.pdf.PDFTransitionAction;
  72. import org.apache.fop.pdf.PDFXMode;
  73. import org.apache.fop.pdf.Version;
  74. import org.apache.fop.pdf.VersionController;
  75. import org.apache.fop.render.pdf.extensions.PDFActionExtension;
  76. import org.apache.fop.render.pdf.extensions.PDFArrayExtension;
  77. import org.apache.fop.render.pdf.extensions.PDFCollectionEntryExtension;
  78. import org.apache.fop.render.pdf.extensions.PDFDictionaryAttachment;
  79. import org.apache.fop.render.pdf.extensions.PDFDictionaryExtension;
  80. import org.apache.fop.render.pdf.extensions.PDFDictionaryType;
  81. import org.apache.fop.render.pdf.extensions.PDFEmbeddedFileAttachment;
  82. import org.apache.fop.render.pdf.extensions.PDFObjectType;
  83. import org.apache.fop.render.pdf.extensions.PDFPageExtension;
  84. import org.apache.fop.render.pdf.extensions.PDFReferenceExtension;
  85. import static org.apache.fop.render.pdf.PDFEncryptionOption.ENCRYPTION_PARAMS;
  86. import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_ACCESSCONTENT;
  87. import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_ANNOTATIONS;
  88. import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_ASSEMBLEDOC;
  89. import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_COPY_CONTENT;
  90. import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_EDIT_CONTENT;
  91. import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_FILLINFORMS;
  92. import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_PRINT;
  93. import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_PRINTHQ;
  94. import static org.apache.fop.render.pdf.PDFEncryptionOption.OWNER_PASSWORD;
  95. import static org.apache.fop.render.pdf.PDFEncryptionOption.USER_PASSWORD;
  96. /**
  97. * Utility class which enables all sorts of features that are not directly connected to the
  98. * normal rendering process.
  99. */
  100. class PDFRenderingUtil {
  101. /** logging instance */
  102. private static Log log = LogFactory.getLog(PDFRenderingUtil.class);
  103. private FOUserAgent userAgent;
  104. /** the PDF Document being created */
  105. private PDFDocument pdfDoc;
  106. private PDFRendererOptionsConfig rendererConfig;
  107. /** the ICC stream used as output profile by this document for PDF/A and PDF/X functionality. */
  108. private PDFICCStream outputProfile;
  109. /** the default sRGB color space. */
  110. private PDFICCBasedColorSpace sRGBColorSpace;
  111. PDFRenderingUtil(FOUserAgent userAgent) {
  112. this.userAgent = userAgent;
  113. initialize();
  114. }
  115. private void initialize() {
  116. rendererConfig = PDFRendererOptionsConfig.DEFAULT.merge(createFromUserAgent(userAgent));
  117. if (rendererConfig.getPDFAMode().isLevelA()) {
  118. // PDF/A Level A requires tagged PDF
  119. userAgent.getRendererOptions().put(Accessibility.ACCESSIBILITY, Boolean.TRUE);
  120. }
  121. }
  122. private static PDFRendererOptionsConfig createFromUserAgent(FOUserAgent userAgent) {
  123. Map<PDFRendererOption, Object> properties
  124. = new EnumMap<PDFRendererOption, Object>(PDFRendererOption.class);
  125. for (PDFRendererOption option : PDFRendererOption.values()) {
  126. Object value = userAgent.getRendererOption(option);
  127. properties.put(option, option.parse(value));
  128. }
  129. PDFEncryptionParams encryptionConfig = new EncryptionParamsBuilder().createParams(userAgent);
  130. return new PDFRendererOptionsConfig(properties, encryptionConfig);
  131. }
  132. void mergeRendererOptionsConfig(PDFRendererOptionsConfig config) {
  133. rendererConfig = rendererConfig.merge(config);
  134. }
  135. private void updateInfo() {
  136. PDFInfo info = pdfDoc.getInfo();
  137. info.setCreator(userAgent.getCreator());
  138. info.setCreationDate(userAgent.getCreationDate());
  139. info.setAuthor(userAgent.getAuthor());
  140. info.setTitle(userAgent.getTitle());
  141. info.setSubject(userAgent.getSubject());
  142. info.setKeywords(userAgent.getKeywords());
  143. }
  144. private void updatePDFProfiles() {
  145. pdfDoc.getProfile().setPDFAMode(rendererConfig.getPDFAMode());
  146. pdfDoc.getProfile().setPDFUAMode(rendererConfig.getPDFUAMode());
  147. userAgent.setPdfUAEnabled(pdfDoc.getProfile().getPDFUAMode().isEnabled());
  148. pdfDoc.getProfile().setPDFXMode(rendererConfig.getPDFXMode());
  149. pdfDoc.getProfile().setPDFVTMode(rendererConfig.getPDFVTMode());
  150. }
  151. private void addsRGBColorSpace() throws IOException {
  152. if (rendererConfig.getDisableSRGBColorSpace()) {
  153. if (rendererConfig.getPDFAMode() != PDFAMode.DISABLED
  154. || rendererConfig.getPDFXMode() != PDFXMode.DISABLED
  155. || rendererConfig.getOutputProfileURI() != null) {
  156. throw new IllegalStateException("It is not possible to disable the sRGB color"
  157. + " space if PDF/A or PDF/X functionality is enabled or an"
  158. + " output profile is set!");
  159. }
  160. } else {
  161. if (this.sRGBColorSpace != null) {
  162. return;
  163. }
  164. //Map sRGB as default RGB profile for DeviceRGB
  165. this.sRGBColorSpace = PDFICCBasedColorSpace.setupsRGBAsDefaultRGBColorSpace(pdfDoc);
  166. }
  167. }
  168. private void addDefaultOutputProfile() throws IOException {
  169. if (this.outputProfile != null) {
  170. return;
  171. }
  172. ICC_Profile profile;
  173. InputStream in = null;
  174. URI outputProfileUri = rendererConfig.getOutputProfileURI();
  175. if (outputProfileUri != null) {
  176. this.outputProfile = pdfDoc.getFactory().makePDFICCStream();
  177. in = userAgent.getResourceResolver().getResource(rendererConfig.getOutputProfileURI());
  178. try {
  179. profile = ColorProfileUtil.getICC_Profile(in);
  180. } finally {
  181. IOUtils.closeQuietly(in);
  182. }
  183. this.outputProfile.setColorSpace(profile, null);
  184. } else {
  185. //Fall back to sRGB profile
  186. outputProfile = sRGBColorSpace.getICCStream();
  187. }
  188. }
  189. /**
  190. * Adds an OutputIntent to the PDF as mandated by PDF/A-1 when uncalibrated color spaces
  191. * are used (which is true if we use DeviceRGB to represent sRGB colors).
  192. * @throws IOException in case of an I/O problem
  193. */
  194. private void addPDFA1OutputIntent() throws IOException {
  195. addDefaultOutputProfile();
  196. String desc = ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile());
  197. PDFOutputIntent outputIntent = pdfDoc.getFactory().makeOutputIntent();
  198. outputIntent.setSubtype(PDFOutputIntent.GTS_PDFA1);
  199. outputIntent.setDestOutputProfile(this.outputProfile);
  200. outputIntent.setOutputConditionIdentifier(desc);
  201. outputIntent.setInfo(outputIntent.getOutputConditionIdentifier());
  202. pdfDoc.getRoot().addOutputIntent(outputIntent);
  203. }
  204. /**
  205. * Adds an OutputIntent to the PDF as mandated by PDF/X when uncalibrated color spaces
  206. * are used (which is true if we use DeviceRGB to represent sRGB colors).
  207. * @throws IOException in case of an I/O problem
  208. */
  209. private void addPDFXOutputIntent() throws IOException {
  210. addDefaultOutputProfile();
  211. String desc = ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile());
  212. int deviceClass = this.outputProfile.getICCProfile().getProfileClass();
  213. if (deviceClass != ICC_Profile.CLASS_OUTPUT) {
  214. throw new PDFConformanceException(pdfDoc.getProfile().getPDFXMode() + " requires that"
  215. + " the DestOutputProfile be an Output Device Profile. "
  216. + desc + " does not match that requirement.");
  217. }
  218. PDFOutputIntent outputIntent = pdfDoc.getFactory().makeOutputIntent();
  219. outputIntent.setSubtype(PDFOutputIntent.GTS_PDFX);
  220. outputIntent.setDestOutputProfile(this.outputProfile);
  221. outputIntent.setOutputConditionIdentifier(desc);
  222. outputIntent.setInfo(outputIntent.getOutputConditionIdentifier());
  223. pdfDoc.getRoot().addOutputIntent(outputIntent);
  224. }
  225. public void renderXMPMetadata(XMPMetadata metadata) {
  226. Metadata docXMP = metadata.getMetadata();
  227. Metadata fopXMP = PDFMetadata.createXMPFromPDFDocument(pdfDoc);
  228. //Merge FOP's own metadata into the one from the XSL-FO document
  229. List<Class> exclude = new ArrayList<Class>();
  230. if (pdfDoc.getProfile().getPDFAMode().isPart1()) {
  231. exclude.add(DublinCoreSchema.class);
  232. }
  233. fopXMP.mergeInto(docXMP, exclude);
  234. XMPBasicAdapter xmpBasic = XMPBasicSchema.getAdapter(docXMP);
  235. //Metadata was changed so update metadata date
  236. xmpBasic.setMetadataDate(new java.util.Date());
  237. PDFMetadata.updateInfoFromMetadata(docXMP, pdfDoc.getInfo());
  238. PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata(
  239. docXMP, metadata.isReadOnly());
  240. pdfDoc.getRoot().setMetadata(pdfMetadata);
  241. }
  242. public void generateDefaultXMPMetadata() {
  243. if (pdfDoc.getRoot().getMetadata() == null) {
  244. //If at this time no XMP metadata for the overall document has been set, create it
  245. //from the PDFInfo object.
  246. Metadata xmp = PDFMetadata.createXMPFromPDFDocument(pdfDoc);
  247. PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata(
  248. xmp, true);
  249. pdfDoc.getRoot().setMetadata(pdfMetadata);
  250. }
  251. }
  252. public void renderDictionaryExtension(PDFDictionaryAttachment attachment, PDFPage currentPage) {
  253. PDFDictionaryExtension extension = attachment.getExtension();
  254. PDFDictionaryType type = extension.getDictionaryType();
  255. if (type == PDFDictionaryType.Action) {
  256. addNavigatorAction(extension);
  257. } else if (type == PDFDictionaryType.Layer) {
  258. addLayer(extension);
  259. } else if (type == PDFDictionaryType.Navigator) {
  260. addNavigator(extension);
  261. } else {
  262. renderDictionaryExtension(extension, currentPage);
  263. }
  264. }
  265. public void addLayer(PDFDictionaryExtension extension) {
  266. assert extension.getDictionaryType() == PDFDictionaryType.Layer;
  267. String id = extension.getProperty(PDFDictionaryExtension.PROPERTY_ID);
  268. if ((id != null) && (id.length() > 0)) {
  269. PDFLayer layer = pdfDoc.getFactory().makeLayer(id);
  270. layer.setResolver(new PDFLayer.Resolver(layer, extension) {
  271. public void performResolution() {
  272. PDFDictionaryExtension extension = (PDFDictionaryExtension) getExtension();
  273. Object name = extension.findEntryValue("Name");
  274. Object intent = extension.findEntryValue("Intent");
  275. Object usage = makeDictionary(extension.findEntryValue("Usage"));
  276. getLayer().populate(name, intent, usage);
  277. }
  278. });
  279. }
  280. }
  281. public void addNavigatorAction(PDFDictionaryExtension extension) {
  282. assert extension.getDictionaryType() == PDFDictionaryType.Action;
  283. String id = extension.getProperty(PDFDictionaryExtension.PROPERTY_ID);
  284. if ((id != null) && (id.length() > 0)) {
  285. String type = extension.getProperty(PDFActionExtension.PROPERTY_TYPE);
  286. if (type != null) {
  287. if (type.equals("SetOCGState")) {
  288. PDFSetOCGStateAction action = pdfDoc.getFactory().makeSetOCGStateAction(id);
  289. action.setResolver(new PDFSetOCGStateAction.Resolver(action, extension) {
  290. public void performResolution() {
  291. PDFDictionaryExtension extension = (PDFDictionaryExtension) getExtension();
  292. Object state = makeArray(extension.findEntryValue("State"));
  293. Object preserveRB = extension.findEntryValue("PreserveRB");
  294. Object nextAction = makeDictionaryOrArray(extension.findEntryValue("Next"));
  295. getAction().populate(state, preserveRB, nextAction);
  296. }
  297. });
  298. } else if (type.equals("Trans")) {
  299. PDFTransitionAction action = pdfDoc.getFactory().makeTransitionAction(id);
  300. action.setResolver(new PDFTransitionAction.Resolver(action, extension) {
  301. public void performResolution() {
  302. PDFDictionaryExtension extension = (PDFDictionaryExtension) getExtension();
  303. Object transition = makeDictionary(extension.findEntryValue("Trans"));
  304. Object nextAction = makeDictionaryOrArray(extension.findEntryValue("Next"));
  305. getAction().populate(transition, nextAction);
  306. }
  307. });
  308. } else {
  309. throw new UnsupportedOperationException();
  310. }
  311. }
  312. }
  313. }
  314. public void addNavigator(PDFDictionaryExtension extension) {
  315. assert extension.getDictionaryType() == PDFDictionaryType.Navigator;
  316. String id = extension.getProperty(PDFDictionaryExtension.PROPERTY_ID);
  317. if ((id != null) && (id.length() > 0)) {
  318. PDFNavigator navigator = pdfDoc.getFactory().makeNavigator(id);
  319. navigator.setResolver(new PDFNavigator.Resolver(navigator, extension) {
  320. public void performResolution() {
  321. PDFDictionaryExtension extension = (PDFDictionaryExtension) getExtension();
  322. Object nextAction = makeDictionary(extension.findEntryValue("NA"));
  323. Object next = makeDictionary(extension.findEntryValue("Next"));
  324. Object prevAction = makeDictionary(extension.findEntryValue("PA"));
  325. Object prev = makeDictionary(extension.findEntryValue("Prev"));
  326. Object duration = extension.findEntryValue("Dur");
  327. getNavigator().populate(nextAction, next, prevAction, prev, duration);
  328. }
  329. });
  330. }
  331. }
  332. private Object makeArray(Object value) {
  333. if (value == null) {
  334. return null;
  335. } else if (value instanceof PDFReferenceExtension) {
  336. return resolveReference((PDFReferenceExtension) value);
  337. } else if (value instanceof List<?>) {
  338. return populateArray(new PDFArray(), (List<?>) value);
  339. } else {
  340. throw new IllegalArgumentException();
  341. }
  342. }
  343. private Object populateArray(PDFArray array, List<?> entries) {
  344. for (PDFCollectionEntryExtension entry : (List<PDFCollectionEntryExtension>) entries) {
  345. PDFObjectType type = entry.getType();
  346. if (type == PDFObjectType.Array) {
  347. array.add(makeArray(entry.getValue()));
  348. } else if (type == PDFObjectType.Boolean) {
  349. array.add(entry.getValueAsBoolean());
  350. } else if (type == PDFObjectType.Dictionary) {
  351. array.add(makeDictionary(entry.getValue()));
  352. } else if (type == PDFObjectType.Name) {
  353. array.add(new PDFName(entry.getValueAsString()));
  354. } else if (type == PDFObjectType.Number) {
  355. array.add(new PDFNumber(entry.getValueAsNumber()));
  356. } else if (type == PDFObjectType.Reference) {
  357. assert (entry instanceof PDFReferenceExtension);
  358. array.add(resolveReference((PDFReferenceExtension) entry));
  359. } else if (type == PDFObjectType.String) {
  360. array.add(entry.getValue());
  361. }
  362. }
  363. return array;
  364. }
  365. private Object makeDictionary(Object value) {
  366. if (value == null) {
  367. return null;
  368. } else if (value instanceof PDFReferenceExtension) {
  369. return resolveReference((PDFReferenceExtension) value);
  370. } else if (value instanceof List<?>) {
  371. return populateDictionary(new PDFDictionary(), (List<?>) value);
  372. } else {
  373. throw new IllegalArgumentException();
  374. }
  375. }
  376. private Object populateDictionary(PDFDictionary dictionary, List<?> entries) {
  377. for (PDFCollectionEntryExtension entry : (List<PDFCollectionEntryExtension>) entries) {
  378. PDFObjectType type = entry.getType();
  379. String key = entry.getKey();
  380. if (type == PDFObjectType.Array) {
  381. dictionary.put(key, makeArray(entry.getValue()));
  382. } else if (type == PDFObjectType.Boolean) {
  383. dictionary.put(key, entry.getValueAsBoolean());
  384. } else if (type == PDFObjectType.Dictionary) {
  385. dictionary.put(key, makeDictionary(entry.getValue()));
  386. } else if (type == PDFObjectType.Name) {
  387. dictionary.put(key, new PDFName(entry.getValueAsString()));
  388. } else if (type == PDFObjectType.Number) {
  389. dictionary.put(key, new PDFNumber(entry.getValueAsNumber()));
  390. } else if (type == PDFObjectType.Reference) {
  391. assert (entry instanceof PDFReferenceExtension);
  392. dictionary.put(key, resolveReference((PDFReferenceExtension) entry));
  393. } else if (type == PDFObjectType.String) {
  394. dictionary.put(key, entry.getValue());
  395. }
  396. }
  397. return dictionary;
  398. }
  399. private Object makeDictionaryOrArray(Object value) {
  400. if (value == null) {
  401. return null;
  402. } else if (value instanceof PDFReferenceExtension) {
  403. return resolveReference((PDFReferenceExtension) value);
  404. } else if (value instanceof List<?>) {
  405. if (hasKeyedEntry((List<?>) value)) {
  406. return populateDictionary(new PDFDictionary(), (List<?>) value);
  407. } else {
  408. return populateArray(new PDFArray(), (List<?>) value);
  409. }
  410. } else {
  411. throw new IllegalArgumentException();
  412. }
  413. }
  414. private boolean hasKeyedEntry(List<?> entries) {
  415. for (PDFCollectionEntryExtension entry : (List<PDFCollectionEntryExtension>) entries) {
  416. if (entry.getKey() != null) {
  417. return true;
  418. }
  419. }
  420. return false;
  421. }
  422. public void renderDictionaryExtension(PDFDictionaryExtension extension, PDFPage currentPage) {
  423. PDFDictionaryType type = extension.getDictionaryType();
  424. if (type == PDFDictionaryType.Catalog) {
  425. augmentDictionary(pdfDoc.getRoot(), extension);
  426. } else if (type == PDFDictionaryType.Page) {
  427. assert extension instanceof PDFPageExtension;
  428. if (((PDFPageExtension) extension).matchesPageNumber(currentPage.getPageIndex() + 1)) {
  429. augmentDictionary(currentPage, extension);
  430. }
  431. } else if (type == PDFDictionaryType.Info) {
  432. PDFInfo info = pdfDoc.getInfo();
  433. for (PDFCollectionEntryExtension entry : extension.getEntries()) {
  434. info.put(entry.getKey(), entry.getValueAsString());
  435. }
  436. } else if (type == PDFDictionaryType.VT) {
  437. if (currentPage.get("DPart") != null) {
  438. augmentDictionary((PDFDictionary)currentPage.get("DPart"), extension);
  439. }
  440. } else if (type == PDFDictionaryType.PagePiece) {
  441. String date = DateFormatUtil.formatPDFDate(new Date(), TimeZone.getDefault());
  442. if (currentPage.get("PieceInfo") == null) {
  443. currentPage.put("PieceInfo", new PDFDictionary());
  444. currentPage.put("LastModified", date);
  445. }
  446. PDFDictionary d = augmentDictionary((PDFDictionary)currentPage.get("PieceInfo"), extension);
  447. d.put("LastModified", date);
  448. } else {
  449. throw new IllegalStateException();
  450. }
  451. }
  452. private PDFDictionary augmentDictionary(PDFDictionary dictionary, PDFDictionaryExtension extension) {
  453. for (PDFCollectionEntryExtension entry : extension.getEntries()) {
  454. if (entry instanceof PDFDictionaryExtension) {
  455. String[] keys = entry.getKey().split("/");
  456. for (int i = 0; i < keys.length; i++) {
  457. if (keys[i].isEmpty()) {
  458. throw new IllegalStateException("pdf:dictionary key: " + entry.getKey() + " not valid");
  459. }
  460. if (i == keys.length - 1) {
  461. dictionary.put(keys[i],
  462. augmentDictionary(new PDFDictionary(dictionary), (PDFDictionaryExtension) entry));
  463. } else {
  464. PDFDictionary d = new PDFDictionary();
  465. dictionary.put(keys[i], d);
  466. dictionary = d;
  467. }
  468. }
  469. } else if (entry instanceof PDFArrayExtension) {
  470. dictionary.put(entry.getKey(), augmentArray(new PDFArray(dictionary), (PDFArrayExtension) entry));
  471. } else {
  472. augmentDictionary(dictionary, entry);
  473. }
  474. }
  475. return dictionary;
  476. }
  477. private void augmentDictionary(PDFDictionary dictionary, PDFCollectionEntryExtension entry) {
  478. PDFObjectType type = entry.getType();
  479. String key = entry.getKey();
  480. if (type == PDFObjectType.Boolean) {
  481. dictionary.put(key, entry.getValueAsBoolean());
  482. } else if (type == PDFObjectType.Name) {
  483. dictionary.put(key, new PDFName(entry.getValueAsString()));
  484. } else if (type == PDFObjectType.Number) {
  485. dictionary.put(key, new PDFNumber(entry.getValueAsNumber()));
  486. } else if (type == PDFObjectType.Reference) {
  487. assert entry instanceof PDFReferenceExtension;
  488. dictionary.put(key, resolveReference((PDFReferenceExtension) entry));
  489. } else if (type == PDFObjectType.String) {
  490. dictionary.put(key, entry.getValueAsString());
  491. } else {
  492. throw new IllegalStateException();
  493. }
  494. }
  495. private Object resolveReference(PDFReferenceExtension entry) {
  496. PDFReference reference = (PDFReference) entry.getResolvedReference();
  497. if (reference == null) {
  498. reference = pdfDoc.resolveExtensionReference(entry.getReferenceId());
  499. if (reference != null) {
  500. entry.setResolvedReference(reference);
  501. }
  502. return reference;
  503. }
  504. return PDFNull.INSTANCE;
  505. }
  506. private PDFArray augmentArray(PDFArray array, PDFArrayExtension extension) {
  507. for (PDFCollectionEntryExtension entry : extension.getEntries()) {
  508. if (entry instanceof PDFDictionaryExtension) {
  509. array.add(augmentDictionary(new PDFDictionary(array), (PDFDictionaryExtension) entry));
  510. } else if (entry instanceof PDFArrayExtension) {
  511. array.add(augmentArray(new PDFArray(array), (PDFArrayExtension) entry));
  512. } else {
  513. augmentArray(array, entry);
  514. }
  515. }
  516. return array;
  517. }
  518. private void augmentArray(PDFArray array, PDFCollectionEntryExtension entry) {
  519. PDFObjectType type = entry.getType();
  520. if (type == PDFObjectType.Boolean) {
  521. array.add(entry.getValueAsBoolean());
  522. } else if (type == PDFObjectType.Name) {
  523. array.add(new PDFName(entry.getValueAsString()));
  524. } else if (type == PDFObjectType.Number) {
  525. array.add(new PDFNumber(entry.getValueAsNumber()));
  526. } else if (type == PDFObjectType.Reference) {
  527. assert entry instanceof PDFReferenceExtension;
  528. array.add(resolveReference((PDFReferenceExtension) entry));
  529. } else if (type == PDFObjectType.String) {
  530. array.add(entry.getValueAsString());
  531. } else {
  532. throw new IllegalStateException();
  533. }
  534. }
  535. public PDFDocument setupPDFDocument(OutputStream out) throws IOException {
  536. if (this.pdfDoc != null) {
  537. throw new IllegalStateException("PDFDocument already set up");
  538. }
  539. String producer = userAgent.getProducer() != null ? userAgent.getProducer() : "";
  540. final Version maxPDFVersion = rendererConfig.getPDFVersion();
  541. if (maxPDFVersion == null) {
  542. this.pdfDoc = new PDFDocument(producer);
  543. } else {
  544. VersionController controller
  545. = VersionController.getFixedVersionController(maxPDFVersion);
  546. this.pdfDoc = new PDFDocument(producer, controller);
  547. }
  548. updateInfo();
  549. updatePDFProfiles();
  550. pdfDoc.setFilterMap(rendererConfig.getFilterMap());
  551. pdfDoc.outputHeader(out);
  552. //Setup encryption if necessary
  553. PDFEncryptionManager.setupPDFEncryption(rendererConfig.getEncryptionParameters(), pdfDoc);
  554. addsRGBColorSpace();
  555. if (rendererConfig.getOutputProfileURI() != null) {
  556. addDefaultOutputProfile();
  557. }
  558. PDFXMode pdfXMode = rendererConfig.getPDFXMode();
  559. if (pdfXMode != PDFXMode.DISABLED) {
  560. log.debug(pdfXMode + " is active.");
  561. log.warn("Note: " + pdfXMode
  562. + " support is work-in-progress and not fully implemented, yet!");
  563. addPDFXOutputIntent();
  564. }
  565. PDFAMode pdfAMode = rendererConfig.getPDFAMode();
  566. if (pdfAMode.isEnabled()) {
  567. log.debug("PDF/A is active. Conformance Level: " + pdfAMode);
  568. addPDFA1OutputIntent();
  569. }
  570. this.pdfDoc.enableAccessibility(userAgent.isAccessibilityEnabled());
  571. pdfDoc.setMergeFontsEnabled(rendererConfig.getMergeFontsEnabled());
  572. pdfDoc.setLinearizationEnabled(rendererConfig.getLinearizationEnabled());
  573. return this.pdfDoc;
  574. }
  575. /**
  576. * Generates a page label in the PDF document.
  577. * @param pageIndex the index of the page
  578. * @param pageNumber the formatted page number
  579. */
  580. public void generatePageLabel(int pageIndex, String pageNumber) {
  581. //Produce page labels
  582. PDFPageLabels pageLabels = this.pdfDoc.getRoot().getPageLabels();
  583. if (pageLabels == null) {
  584. //Set up PageLabels
  585. pageLabels = this.pdfDoc.getFactory().makePageLabels();
  586. this.pdfDoc.getRoot().setPageLabels(pageLabels);
  587. }
  588. pageLabels.addPageLabel(pageIndex, pageNumber);
  589. }
  590. /**
  591. * Adds an embedded file to the PDF file.
  592. * @param embeddedFile the object representing the embedded file to be added
  593. * @throws IOException if an I/O error occurs
  594. */
  595. public void addEmbeddedFile(PDFEmbeddedFileAttachment embeddedFile)
  596. throws IOException {
  597. this.pdfDoc.getProfile().verifyEmbeddedFilesAllowed();
  598. PDFNames names = this.pdfDoc.getRoot().getNames();
  599. if (names == null) {
  600. //Add Names if not already present
  601. names = this.pdfDoc.getFactory().makeNames();
  602. this.pdfDoc.getRoot().setNames(names);
  603. }
  604. //Create embedded file
  605. PDFEmbeddedFile file = new PDFEmbeddedFile();
  606. this.pdfDoc.registerObject(file);
  607. URI srcURI;
  608. try {
  609. srcURI = InternalResourceResolver.cleanURI(embeddedFile.getSrc());
  610. } catch (URISyntaxException use) {
  611. throw new RuntimeException(use);
  612. }
  613. InputStream in = userAgent.getResourceResolver().getResource(srcURI);
  614. if (in == null) {
  615. throw new FileNotFoundException(embeddedFile.getSrc());
  616. }
  617. try {
  618. OutputStream out = file.getBufferOutputStream();
  619. IOUtils.copyLarge(in, out);
  620. } finally {
  621. IOUtils.closeQuietly(in);
  622. }
  623. PDFDictionary dict = new PDFDictionary();
  624. dict.put("F", file);
  625. String filename = PDFText.toPDFString(embeddedFile.getFilename(), '_');
  626. PDFFileSpec fileSpec = new PDFFileSpec(filename);
  627. pdfDoc.getRoot().addAF(fileSpec, filename);
  628. fileSpec.setEmbeddedFile(dict);
  629. if (embeddedFile.getDesc() != null) {
  630. fileSpec.setDescription(embeddedFile.getDesc());
  631. }
  632. this.pdfDoc.registerObject(fileSpec);
  633. //Make sure there is an EmbeddedFiles in the Names dictionary
  634. PDFEmbeddedFiles embeddedFiles = names.getEmbeddedFiles();
  635. if (embeddedFiles == null) {
  636. embeddedFiles = new PDFEmbeddedFiles();
  637. this.pdfDoc.assignObjectNumber(embeddedFiles);
  638. this.pdfDoc.addTrailerObject(embeddedFiles);
  639. names.setEmbeddedFiles(embeddedFiles);
  640. }
  641. //Add to EmbeddedFiles in the Names dictionary
  642. PDFArray nameArray = embeddedFiles.getNames();
  643. if (nameArray == null) {
  644. nameArray = new PDFArray();
  645. embeddedFiles.setNames(nameArray);
  646. }
  647. String name = PDFText.toPDFString(filename);
  648. nameArray.add(name);
  649. nameArray.add(new PDFReference(fileSpec));
  650. }
  651. private static final class EncryptionParamsBuilder {
  652. private PDFEncryptionParams params;
  653. private EncryptionParamsBuilder() {
  654. }
  655. private PDFEncryptionParams createParams(FOUserAgent userAgent) {
  656. params = (PDFEncryptionParams) userAgent.getRendererOptions().get(ENCRYPTION_PARAMS);
  657. String userPassword = (String) userAgent.getRendererOption(USER_PASSWORD);
  658. if (userPassword != null) {
  659. getEncryptionParams().setUserPassword(userPassword);
  660. }
  661. String ownerPassword = (String) userAgent.getRendererOption(OWNER_PASSWORD);
  662. if (ownerPassword != null) {
  663. getEncryptionParams().setOwnerPassword(ownerPassword);
  664. }
  665. Object noPrint = userAgent.getRendererOption(NO_PRINT);
  666. if (noPrint != null) {
  667. getEncryptionParams().setAllowPrint(!booleanValueOf(noPrint));
  668. }
  669. Object noCopyContent = userAgent.getRendererOption(NO_COPY_CONTENT);
  670. if (noCopyContent != null) {
  671. getEncryptionParams().setAllowCopyContent(!booleanValueOf(noCopyContent));
  672. }
  673. Object noEditContent = userAgent.getRendererOption(NO_EDIT_CONTENT);
  674. if (noEditContent != null) {
  675. getEncryptionParams().setAllowEditContent(!booleanValueOf(noEditContent));
  676. }
  677. Object noAnnotations = userAgent.getRendererOption(NO_ANNOTATIONS);
  678. if (noAnnotations != null) {
  679. getEncryptionParams().setAllowEditAnnotations(!booleanValueOf(noAnnotations));
  680. }
  681. Object noFillInForms = userAgent.getRendererOption(NO_FILLINFORMS);
  682. if (noFillInForms != null) {
  683. getEncryptionParams().setAllowFillInForms(!booleanValueOf(noFillInForms));
  684. }
  685. Object noAccessContent = userAgent.getRendererOption(NO_ACCESSCONTENT);
  686. if (noAccessContent != null) {
  687. getEncryptionParams().setAllowAccessContent(!booleanValueOf(noAccessContent));
  688. }
  689. Object noAssembleDoc = userAgent.getRendererOption(NO_ASSEMBLEDOC);
  690. if (noAssembleDoc != null) {
  691. getEncryptionParams().setAllowAssembleDocument(!booleanValueOf(noAssembleDoc));
  692. }
  693. Object noPrintHQ = userAgent.getRendererOption(NO_PRINTHQ);
  694. if (noPrintHQ != null) {
  695. getEncryptionParams().setAllowPrintHq(!booleanValueOf(noPrintHQ));
  696. }
  697. return params;
  698. }
  699. private PDFEncryptionParams getEncryptionParams() {
  700. if (params == null) {
  701. params = new PDFEncryptionParams();
  702. }
  703. return params;
  704. }
  705. private static boolean booleanValueOf(Object obj) {
  706. if (obj instanceof Boolean) {
  707. return (Boolean) obj;
  708. } else if (obj instanceof String) {
  709. return Boolean.valueOf((String) obj);
  710. } else {
  711. throw new IllegalArgumentException("Boolean or \"true\" or \"false\" expected.");
  712. }
  713. }
  714. }
  715. }