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

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