Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

PDFRenderingUtil.java 34KB

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