/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* $Id$ */ package org.apache.fop.render.pdf; import java.awt.color.ICC_Profile; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Date; import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.TimeZone; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xmlgraphics.java2d.color.profile.ColorProfileUtil; import org.apache.xmlgraphics.util.DateFormatUtil; import org.apache.xmlgraphics.xmp.Metadata; import org.apache.xmlgraphics.xmp.schemas.DublinCoreSchema; import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter; import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema; import org.apache.fop.accessibility.Accessibility; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.io.InternalResourceResolver; import org.apache.fop.fo.extensions.xmp.XMPMetadata; import org.apache.fop.pdf.PDFAMode; import org.apache.fop.pdf.PDFArray; import org.apache.fop.pdf.PDFConformanceException; import org.apache.fop.pdf.PDFDictionary; import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFEmbeddedFile; import org.apache.fop.pdf.PDFEmbeddedFiles; import org.apache.fop.pdf.PDFEncryptionManager; import org.apache.fop.pdf.PDFEncryptionParams; import org.apache.fop.pdf.PDFFileSpec; import org.apache.fop.pdf.PDFICCBasedColorSpace; import org.apache.fop.pdf.PDFICCStream; import org.apache.fop.pdf.PDFInfo; import org.apache.fop.pdf.PDFLayer; import org.apache.fop.pdf.PDFMetadata; import org.apache.fop.pdf.PDFName; import org.apache.fop.pdf.PDFNames; import org.apache.fop.pdf.PDFNavigator; import org.apache.fop.pdf.PDFNull; import org.apache.fop.pdf.PDFNumber; import org.apache.fop.pdf.PDFOutputIntent; import org.apache.fop.pdf.PDFPage; import org.apache.fop.pdf.PDFPageLabels; import org.apache.fop.pdf.PDFReference; import org.apache.fop.pdf.PDFSetOCGStateAction; import org.apache.fop.pdf.PDFText; import org.apache.fop.pdf.PDFTransitionAction; import org.apache.fop.pdf.PDFXMode; import org.apache.fop.pdf.Version; import org.apache.fop.pdf.VersionController; import org.apache.fop.render.pdf.extensions.PDFActionExtension; import org.apache.fop.render.pdf.extensions.PDFArrayExtension; import org.apache.fop.render.pdf.extensions.PDFCollectionEntryExtension; import org.apache.fop.render.pdf.extensions.PDFDictionaryAttachment; import org.apache.fop.render.pdf.extensions.PDFDictionaryExtension; import org.apache.fop.render.pdf.extensions.PDFDictionaryType; import org.apache.fop.render.pdf.extensions.PDFEmbeddedFileAttachment; import org.apache.fop.render.pdf.extensions.PDFObjectType; import org.apache.fop.render.pdf.extensions.PDFPageExtension; import org.apache.fop.render.pdf.extensions.PDFReferenceExtension; import static org.apache.fop.render.pdf.PDFEncryptionOption.ENCRYPTION_PARAMS; import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_ACCESSCONTENT; import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_ANNOTATIONS; import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_ASSEMBLEDOC; import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_COPY_CONTENT; import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_EDIT_CONTENT; import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_FILLINFORMS; import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_PRINT; import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_PRINTHQ; import static org.apache.fop.render.pdf.PDFEncryptionOption.OWNER_PASSWORD; import static org.apache.fop.render.pdf.PDFEncryptionOption.USER_PASSWORD; /** * Utility class which enables all sorts of features that are not directly connected to the * normal rendering process. */ class PDFRenderingUtil { /** logging instance */ private static Log log = LogFactory.getLog(PDFRenderingUtil.class); private FOUserAgent userAgent; /** the PDF Document being created */ private PDFDocument pdfDoc; private PDFRendererOptionsConfig rendererConfig; /** the ICC stream used as output profile by this document for PDF/A and PDF/X functionality. */ private PDFICCStream outputProfile; /** the default sRGB color space. */ private PDFICCBasedColorSpace sRGBColorSpace; PDFRenderingUtil(FOUserAgent userAgent) { this.userAgent = userAgent; initialize(); } private void initialize() { rendererConfig = PDFRendererOptionsConfig.DEFAULT.merge(createFromUserAgent(userAgent)); if (rendererConfig.getPDFAMode().isLevelA()) { // PDF/A Level A requires tagged PDF userAgent.getRendererOptions().put(Accessibility.ACCESSIBILITY, Boolean.TRUE); } } private static PDFRendererOptionsConfig createFromUserAgent(FOUserAgent userAgent) { Map<PDFRendererOption, Object> properties = new EnumMap<PDFRendererOption, Object>(PDFRendererOption.class); for (PDFRendererOption option : PDFRendererOption.values()) { Object value = userAgent.getRendererOption(option); properties.put(option, option.parse(value)); } PDFEncryptionParams encryptionConfig = new EncryptionParamsBuilder().createParams(userAgent); return new PDFRendererOptionsConfig(properties, encryptionConfig); } void mergeRendererOptionsConfig(PDFRendererOptionsConfig config) { rendererConfig = rendererConfig.merge(config); } private void updateInfo() { PDFInfo info = pdfDoc.getInfo(); info.setCreator(userAgent.getCreator()); info.setCreationDate(userAgent.getCreationDate()); info.setAuthor(userAgent.getAuthor()); info.setTitle(userAgent.getTitle()); info.setSubject(userAgent.getSubject()); info.setKeywords(userAgent.getKeywords()); } private void updatePDFProfiles() { pdfDoc.getProfile().setPDFAMode(rendererConfig.getPDFAMode()); pdfDoc.getProfile().setPDFUAMode(rendererConfig.getPDFUAMode()); userAgent.setPdfUAEnabled(pdfDoc.getProfile().getPDFUAMode().isEnabled()); pdfDoc.getProfile().setPDFXMode(rendererConfig.getPDFXMode()); pdfDoc.getProfile().setPDFVTMode(rendererConfig.getPDFVTMode()); } private void addsRGBColorSpace() throws IOException { if (rendererConfig.getDisableSRGBColorSpace()) { if (rendererConfig.getPDFAMode() != PDFAMode.DISABLED || rendererConfig.getPDFXMode() != PDFXMode.DISABLED || rendererConfig.getOutputProfileURI() != null) { throw new IllegalStateException("It is not possible to disable the sRGB color" + " space if PDF/A or PDF/X functionality is enabled or an" + " output profile is set!"); } } else { if (this.sRGBColorSpace != null) { return; } //Map sRGB as default RGB profile for DeviceRGB this.sRGBColorSpace = PDFICCBasedColorSpace.setupsRGBAsDefaultRGBColorSpace(pdfDoc); } } private void addDefaultOutputProfile() throws IOException { if (this.outputProfile != null) { return; } ICC_Profile profile; InputStream in = null; URI outputProfileUri = rendererConfig.getOutputProfileURI(); if (outputProfileUri != null) { this.outputProfile = pdfDoc.getFactory().makePDFICCStream(); in = userAgent.getResourceResolver().getResource(rendererConfig.getOutputProfileURI()); try { profile = ColorProfileUtil.getICC_Profile(in); } finally { IOUtils.closeQuietly(in); } this.outputProfile.setColorSpace(profile, null); } else { //Fall back to sRGB profile outputProfile = sRGBColorSpace.getICCStream(); } } /** * Adds an OutputIntent to the PDF as mandated by PDF/A-1 when uncalibrated color spaces * are used (which is true if we use DeviceRGB to represent sRGB colors). * @throws IOException in case of an I/O problem */ private void addPDFA1OutputIntent() throws IOException { addDefaultOutputProfile(); String desc = ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile()); PDFOutputIntent outputIntent = pdfDoc.getFactory().makeOutputIntent(); outputIntent.setSubtype(PDFOutputIntent.GTS_PDFA1); outputIntent.setDestOutputProfile(this.outputProfile); outputIntent.setOutputConditionIdentifier(desc); outputIntent.setInfo(outputIntent.getOutputConditionIdentifier()); pdfDoc.getRoot().addOutputIntent(outputIntent); } /** * Adds an OutputIntent to the PDF as mandated by PDF/X when uncalibrated color spaces * are used (which is true if we use DeviceRGB to represent sRGB colors). * @throws IOException in case of an I/O problem */ private void addPDFXOutputIntent() throws IOException { addDefaultOutputProfile(); String desc = ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile()); int deviceClass = this.outputProfile.getICCProfile().getProfileClass(); if (deviceClass != ICC_Profile.CLASS_OUTPUT) { throw new PDFConformanceException(pdfDoc.getProfile().getPDFXMode() + " requires that" + " the DestOutputProfile be an Output Device Profile. " + desc + " does not match that requirement."); } PDFOutputIntent outputIntent = pdfDoc.getFactory().makeOutputIntent(); outputIntent.setSubtype(PDFOutputIntent.GTS_PDFX); outputIntent.setDestOutputProfile(this.outputProfile); outputIntent.setOutputConditionIdentifier(desc); outputIntent.setInfo(outputIntent.getOutputConditionIdentifier()); pdfDoc.getRoot().addOutputIntent(outputIntent); } public void renderXMPMetadata(XMPMetadata metadata) { Metadata docXMP = metadata.getMetadata(); Metadata fopXMP = PDFMetadata.createXMPFromPDFDocument(pdfDoc); //Merge FOP's own metadata into the one from the XSL-FO document List<Class> exclude = new ArrayList<Class>(); if (pdfDoc.getProfile().getPDFAMode().isPart1()) { exclude.add(DublinCoreSchema.class); } fopXMP.mergeInto(docXMP, exclude); XMPBasicAdapter xmpBasic = XMPBasicSchema.getAdapter(docXMP); //Metadata was changed so update metadata date xmpBasic.setMetadataDate(new java.util.Date()); PDFMetadata.updateInfoFromMetadata(docXMP, pdfDoc.getInfo()); PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata( docXMP, metadata.isReadOnly()); pdfDoc.getRoot().setMetadata(pdfMetadata); } public void generateDefaultXMPMetadata() { if (pdfDoc.getRoot().getMetadata() == null) { //If at this time no XMP metadata for the overall document has been set, create it //from the PDFInfo object. Metadata xmp = PDFMetadata.createXMPFromPDFDocument(pdfDoc); PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata( xmp, true); pdfDoc.getRoot().setMetadata(pdfMetadata); } } public void renderDictionaryExtension(PDFDictionaryAttachment attachment, PDFPage currentPage) { PDFDictionaryExtension extension = attachment.getExtension(); PDFDictionaryType type = extension.getDictionaryType(); if (type == PDFDictionaryType.Action) { addNavigatorAction(extension); } else if (type == PDFDictionaryType.Layer) { addLayer(extension); } else if (type == PDFDictionaryType.Navigator) { addNavigator(extension); } else { renderDictionaryExtension(extension, currentPage); } } public void addLayer(PDFDictionaryExtension extension) { assert extension.getDictionaryType() == PDFDictionaryType.Layer; String id = extension.getProperty(PDFDictionaryExtension.PROPERTY_ID); if ((id != null) && (id.length() > 0)) { PDFLayer layer = pdfDoc.getFactory().makeLayer(id); layer.setResolver(new PDFLayer.Resolver(layer, extension) { public void performResolution() { PDFDictionaryExtension extension = (PDFDictionaryExtension) getExtension(); Object name = extension.findEntryValue("Name"); Object intent = extension.findEntryValue("Intent"); Object usage = makeDictionary(extension.findEntryValue("Usage")); getLayer().populate(name, intent, usage); } }); } } public void addNavigatorAction(PDFDictionaryExtension extension) { assert extension.getDictionaryType() == PDFDictionaryType.Action; String id = extension.getProperty(PDFDictionaryExtension.PROPERTY_ID); if ((id != null) && (id.length() > 0)) { String type = extension.getProperty(PDFActionExtension.PROPERTY_TYPE); if (type != null) { if (type.equals("SetOCGState")) { PDFSetOCGStateAction action = pdfDoc.getFactory().makeSetOCGStateAction(id); action.setResolver(new PDFSetOCGStateAction.Resolver(action, extension) { public void performResolution() { PDFDictionaryExtension extension = (PDFDictionaryExtension) getExtension(); Object state = makeArray(extension.findEntryValue("State")); Object preserveRB = extension.findEntryValue("PreserveRB"); Object nextAction = makeDictionaryOrArray(extension.findEntryValue("Next")); getAction().populate(state, preserveRB, nextAction); } }); } else if (type.equals("Trans")) { PDFTransitionAction action = pdfDoc.getFactory().makeTransitionAction(id); action.setResolver(new PDFTransitionAction.Resolver(action, extension) { public void performResolution() { PDFDictionaryExtension extension = (PDFDictionaryExtension) getExtension(); Object transition = makeDictionary(extension.findEntryValue("Trans")); Object nextAction = makeDictionaryOrArray(extension.findEntryValue("Next")); getAction().populate(transition, nextAction); } }); } else { throw new UnsupportedOperationException(); } } } } public void addNavigator(PDFDictionaryExtension extension) { assert extension.getDictionaryType() == PDFDictionaryType.Navigator; String id = extension.getProperty(PDFDictionaryExtension.PROPERTY_ID); if ((id != null) && (id.length() > 0)) { PDFNavigator navigator = pdfDoc.getFactory().makeNavigator(id); navigator.setResolver(new PDFNavigator.Resolver(navigator, extension) { public void performResolution() { PDFDictionaryExtension extension = (PDFDictionaryExtension) getExtension(); Object nextAction = makeDictionary(extension.findEntryValue("NA")); Object next = makeDictionary(extension.findEntryValue("Next")); Object prevAction = makeDictionary(extension.findEntryValue("PA")); Object prev = makeDictionary(extension.findEntryValue("Prev")); Object duration = extension.findEntryValue("Dur"); getNavigator().populate(nextAction, next, prevAction, prev, duration); } }); } } private Object makeArray(Object value) { if (value == null) { return null; } else if (value instanceof PDFReferenceExtension) { return resolveReference((PDFReferenceExtension) value); } else if (value instanceof List<?>) { return populateArray(new PDFArray(), (List<?>) value); } else { throw new IllegalArgumentException(); } } private Object populateArray(PDFArray array, List<?> entries) { for (PDFCollectionEntryExtension entry : (List<PDFCollectionEntryExtension>) entries) { PDFObjectType type = entry.getType(); if (type == PDFObjectType.Array) { array.add(makeArray(entry.getValue())); } else if (type == PDFObjectType.Boolean) { array.add(entry.getValueAsBoolean()); } else if (type == PDFObjectType.Dictionary) { array.add(makeDictionary(entry.getValue())); } else if (type == PDFObjectType.Name) { array.add(new PDFName(entry.getValueAsString())); } else if (type == PDFObjectType.Number) { array.add(new PDFNumber(entry.getValueAsNumber())); } else if (type == PDFObjectType.Reference) { assert (entry instanceof PDFReferenceExtension); array.add(resolveReference((PDFReferenceExtension) entry)); } else if (type == PDFObjectType.String) { array.add(entry.getValue()); } } return array; } private Object makeDictionary(Object value) { if (value == null) { return null; } else if (value instanceof PDFReferenceExtension) { return resolveReference((PDFReferenceExtension) value); } else if (value instanceof List<?>) { return populateDictionary(new PDFDictionary(), (List<?>) value); } else { throw new IllegalArgumentException(); } } private Object populateDictionary(PDFDictionary dictionary, List<?> entries) { for (PDFCollectionEntryExtension entry : (List<PDFCollectionEntryExtension>) entries) { PDFObjectType type = entry.getType(); String key = entry.getKey(); if (type == PDFObjectType.Array) { dictionary.put(key, makeArray(entry.getValue())); } else if (type == PDFObjectType.Boolean) { dictionary.put(key, entry.getValueAsBoolean()); } else if (type == PDFObjectType.Dictionary) { dictionary.put(key, makeDictionary(entry.getValue())); } else if (type == PDFObjectType.Name) { dictionary.put(key, new PDFName(entry.getValueAsString())); } else if (type == PDFObjectType.Number) { dictionary.put(key, new PDFNumber(entry.getValueAsNumber())); } else if (type == PDFObjectType.Reference) { assert (entry instanceof PDFReferenceExtension); dictionary.put(key, resolveReference((PDFReferenceExtension) entry)); } else if (type == PDFObjectType.String) { dictionary.put(key, entry.getValue()); } } return dictionary; } private Object makeDictionaryOrArray(Object value) { if (value == null) { return null; } else if (value instanceof PDFReferenceExtension) { return resolveReference((PDFReferenceExtension) value); } else if (value instanceof List<?>) { if (hasKeyedEntry((List<?>) value)) { return populateDictionary(new PDFDictionary(), (List<?>) value); } else { return populateArray(new PDFArray(), (List<?>) value); } } else { throw new IllegalArgumentException(); } } private boolean hasKeyedEntry(List<?> entries) { for (PDFCollectionEntryExtension entry : (List<PDFCollectionEntryExtension>) entries) { if (entry.getKey() != null) { return true; } } return false; } public void renderDictionaryExtension(PDFDictionaryExtension extension, PDFPage currentPage) { PDFDictionaryType type = extension.getDictionaryType(); if (type == PDFDictionaryType.Catalog) { augmentDictionary(pdfDoc.getRoot(), extension); } else if (type == PDFDictionaryType.Page) { assert extension instanceof PDFPageExtension; if (((PDFPageExtension) extension).matchesPageNumber(currentPage.getPageIndex() + 1)) { augmentDictionary(currentPage, extension); } } else if (type == PDFDictionaryType.Info) { PDFInfo info = pdfDoc.getInfo(); for (PDFCollectionEntryExtension entry : extension.getEntries()) { info.put(entry.getKey(), entry.getValueAsString()); } } else if (type == PDFDictionaryType.VT) { if (currentPage.get("DPart") != null) { augmentDictionary((PDFDictionary)currentPage.get("DPart"), extension); } } else if (type == PDFDictionaryType.PagePiece) { String date = DateFormatUtil.formatPDFDate(new Date(), TimeZone.getDefault()); if (currentPage.get("PieceInfo") == null) { currentPage.put("PieceInfo", new PDFDictionary()); currentPage.put("LastModified", date); } PDFDictionary d = augmentDictionary((PDFDictionary)currentPage.get("PieceInfo"), extension); d.put("LastModified", date); } else { throw new IllegalStateException(); } } private PDFDictionary augmentDictionary(PDFDictionary dictionary, PDFDictionaryExtension extension) { for (PDFCollectionEntryExtension entry : extension.getEntries()) { if (entry instanceof PDFDictionaryExtension) { String[] keys = entry.getKey().split("/"); for (int i = 0; i < keys.length; i++) { if (keys[i].isEmpty()) { throw new IllegalStateException("pdf:dictionary key: " + entry.getKey() + " not valid"); } if (i == keys.length - 1) { dictionary.put(keys[i], augmentDictionary(new PDFDictionary(dictionary), (PDFDictionaryExtension) entry)); } else { PDFDictionary d = new PDFDictionary(); dictionary.put(keys[i], d); dictionary = d; } } } else if (entry instanceof PDFArrayExtension) { dictionary.put(entry.getKey(), augmentArray(new PDFArray(dictionary), (PDFArrayExtension) entry)); } else { augmentDictionary(dictionary, entry); } } return dictionary; } private void augmentDictionary(PDFDictionary dictionary, PDFCollectionEntryExtension entry) { PDFObjectType type = entry.getType(); String key = entry.getKey(); if (type == PDFObjectType.Boolean) { dictionary.put(key, entry.getValueAsBoolean()); } else if (type == PDFObjectType.Name) { dictionary.put(key, new PDFName(entry.getValueAsString())); } else if (type == PDFObjectType.Number) { dictionary.put(key, new PDFNumber(entry.getValueAsNumber())); } else if (type == PDFObjectType.Reference) { assert entry instanceof PDFReferenceExtension; dictionary.put(key, resolveReference((PDFReferenceExtension) entry)); } else if (type == PDFObjectType.String) { dictionary.put(key, entry.getValueAsString()); } else { throw new IllegalStateException(); } } private Object resolveReference(PDFReferenceExtension entry) { PDFReference reference = (PDFReference) entry.getResolvedReference(); if (reference == null) { reference = pdfDoc.resolveExtensionReference(entry.getReferenceId()); if (reference != null) { entry.setResolvedReference(reference); } return reference; } return PDFNull.INSTANCE; } private PDFArray augmentArray(PDFArray array, PDFArrayExtension extension) { for (PDFCollectionEntryExtension entry : extension.getEntries()) { if (entry instanceof PDFDictionaryExtension) { array.add(augmentDictionary(new PDFDictionary(array), (PDFDictionaryExtension) entry)); } else if (entry instanceof PDFArrayExtension) { array.add(augmentArray(new PDFArray(array), (PDFArrayExtension) entry)); } else { augmentArray(array, entry); } } return array; } private void augmentArray(PDFArray array, PDFCollectionEntryExtension entry) { PDFObjectType type = entry.getType(); if (type == PDFObjectType.Boolean) { array.add(entry.getValueAsBoolean()); } else if (type == PDFObjectType.Name) { array.add(new PDFName(entry.getValueAsString())); } else if (type == PDFObjectType.Number) { array.add(new PDFNumber(entry.getValueAsNumber())); } else if (type == PDFObjectType.Reference) { assert entry instanceof PDFReferenceExtension; array.add(resolveReference((PDFReferenceExtension) entry)); } else if (type == PDFObjectType.String) { array.add(entry.getValueAsString()); } else { throw new IllegalStateException(); } } public PDFDocument setupPDFDocument(OutputStream out) throws IOException { if (this.pdfDoc != null) { throw new IllegalStateException("PDFDocument already set up"); } String producer = userAgent.getProducer() != null ? userAgent.getProducer() : ""; final Version maxPDFVersion = rendererConfig.getPDFVersion(); if (maxPDFVersion == null) { this.pdfDoc = new PDFDocument(producer); } else { VersionController controller = VersionController.getFixedVersionController(maxPDFVersion); this.pdfDoc = new PDFDocument(producer, controller); } updateInfo(); updatePDFProfiles(); pdfDoc.setFilterMap(rendererConfig.getFilterMap()); pdfDoc.outputHeader(out); //Setup encryption if necessary PDFEncryptionManager.setupPDFEncryption(rendererConfig.getEncryptionParameters(), pdfDoc); addsRGBColorSpace(); if (rendererConfig.getOutputProfileURI() != null) { addDefaultOutputProfile(); } PDFXMode pdfXMode = rendererConfig.getPDFXMode(); if (pdfXMode != PDFXMode.DISABLED) { log.debug(pdfXMode + " is active."); log.warn("Note: " + pdfXMode + " support is work-in-progress and not fully implemented, yet!"); addPDFXOutputIntent(); } PDFAMode pdfAMode = rendererConfig.getPDFAMode(); if (pdfAMode.isEnabled()) { log.debug("PDF/A is active. Conformance Level: " + pdfAMode); addPDFA1OutputIntent(); } this.pdfDoc.enableAccessibility(userAgent.isAccessibilityEnabled()); pdfDoc.setMergeFontsEnabled(rendererConfig.getMergeFontsEnabled()); pdfDoc.setLinearizationEnabled(rendererConfig.getLinearizationEnabled()); return this.pdfDoc; } /** * Generates a page label in the PDF document. * @param pageIndex the index of the page * @param pageNumber the formatted page number */ public void generatePageLabel(int pageIndex, String pageNumber) { //Produce page labels PDFPageLabels pageLabels = this.pdfDoc.getRoot().getPageLabels(); if (pageLabels == null) { //Set up PageLabels pageLabels = this.pdfDoc.getFactory().makePageLabels(); this.pdfDoc.getRoot().setPageLabels(pageLabels); } pageLabels.addPageLabel(pageIndex, pageNumber); } /** * Adds an embedded file to the PDF file. * @param embeddedFile the object representing the embedded file to be added * @throws IOException if an I/O error occurs */ public void addEmbeddedFile(PDFEmbeddedFileAttachment embeddedFile) throws IOException { this.pdfDoc.getProfile().verifyEmbeddedFilesAllowed(); PDFNames names = this.pdfDoc.getRoot().getNames(); if (names == null) { //Add Names if not already present names = this.pdfDoc.getFactory().makeNames(); this.pdfDoc.getRoot().setNames(names); } //Create embedded file PDFEmbeddedFile file = new PDFEmbeddedFile(); this.pdfDoc.registerObject(file); URI srcURI; try { srcURI = InternalResourceResolver.cleanURI(embeddedFile.getSrc()); } catch (URISyntaxException use) { throw new RuntimeException(use); } InputStream in = userAgent.getResourceResolver().getResource(srcURI); if (in == null) { throw new FileNotFoundException(embeddedFile.getSrc()); } try { OutputStream out = file.getBufferOutputStream(); IOUtils.copyLarge(in, out); } finally { IOUtils.closeQuietly(in); } PDFDictionary dict = new PDFDictionary(); dict.put("F", file); String filename = PDFText.toPDFString(embeddedFile.getFilename(), '_'); PDFFileSpec fileSpec = new PDFFileSpec(filename); pdfDoc.getRoot().addAF(fileSpec, filename); fileSpec.setEmbeddedFile(dict); if (embeddedFile.getDesc() != null) { fileSpec.setDescription(embeddedFile.getDesc()); } this.pdfDoc.registerObject(fileSpec); //Make sure there is an EmbeddedFiles in the Names dictionary PDFEmbeddedFiles embeddedFiles = names.getEmbeddedFiles(); if (embeddedFiles == null) { embeddedFiles = new PDFEmbeddedFiles(); this.pdfDoc.assignObjectNumber(embeddedFiles); this.pdfDoc.addTrailerObject(embeddedFiles); names.setEmbeddedFiles(embeddedFiles); } //Add to EmbeddedFiles in the Names dictionary PDFArray nameArray = embeddedFiles.getNames(); if (nameArray == null) { nameArray = new PDFArray(); embeddedFiles.setNames(nameArray); } String name = PDFText.toPDFString(filename); nameArray.add(name); nameArray.add(new PDFReference(fileSpec)); } private static final class EncryptionParamsBuilder { private PDFEncryptionParams params; private EncryptionParamsBuilder() { } private PDFEncryptionParams createParams(FOUserAgent userAgent) { params = (PDFEncryptionParams) userAgent.getRendererOptions().get(ENCRYPTION_PARAMS); String userPassword = (String) userAgent.getRendererOption(USER_PASSWORD); if (userPassword != null) { getEncryptionParams().setUserPassword(userPassword); } String ownerPassword = (String) userAgent.getRendererOption(OWNER_PASSWORD); if (ownerPassword != null) { getEncryptionParams().setOwnerPassword(ownerPassword); } Object noPrint = userAgent.getRendererOption(NO_PRINT); if (noPrint != null) { getEncryptionParams().setAllowPrint(!booleanValueOf(noPrint)); } Object noCopyContent = userAgent.getRendererOption(NO_COPY_CONTENT); if (noCopyContent != null) { getEncryptionParams().setAllowCopyContent(!booleanValueOf(noCopyContent)); } Object noEditContent = userAgent.getRendererOption(NO_EDIT_CONTENT); if (noEditContent != null) { getEncryptionParams().setAllowEditContent(!booleanValueOf(noEditContent)); } Object noAnnotations = userAgent.getRendererOption(NO_ANNOTATIONS); if (noAnnotations != null) { getEncryptionParams().setAllowEditAnnotations(!booleanValueOf(noAnnotations)); } Object noFillInForms = userAgent.getRendererOption(NO_FILLINFORMS); if (noFillInForms != null) { getEncryptionParams().setAllowFillInForms(!booleanValueOf(noFillInForms)); } Object noAccessContent = userAgent.getRendererOption(NO_ACCESSCONTENT); if (noAccessContent != null) { getEncryptionParams().setAllowAccessContent(!booleanValueOf(noAccessContent)); } Object noAssembleDoc = userAgent.getRendererOption(NO_ASSEMBLEDOC); if (noAssembleDoc != null) { getEncryptionParams().setAllowAssembleDocument(!booleanValueOf(noAssembleDoc)); } Object noPrintHQ = userAgent.getRendererOption(NO_PRINTHQ); if (noPrintHQ != null) { getEncryptionParams().setAllowPrintHq(!booleanValueOf(noPrintHQ)); } return params; } private PDFEncryptionParams getEncryptionParams() { if (params == null) { params = new PDFEncryptionParams(); } return params; } private static boolean booleanValueOf(Object obj) { if (obj instanceof Boolean) { return (Boolean) obj; } else if (obj instanceof String) { return Boolean.valueOf((String) obj); } else { throw new IllegalArgumentException("Boolean or \"true\" or \"false\" expected."); } } } }