| /* |
| * 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. |
| */ |
| package org.apache.pdfbox.pdmodel; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.apache.logging.log4j.Logger; |
| import org.apache.logging.log4j.LogManager; |
| |
| import org.apache.pdfbox.cos.COSArray; |
| import org.apache.pdfbox.cos.COSBase; |
| import org.apache.pdfbox.cos.COSDictionary; |
| import org.apache.pdfbox.cos.COSName; |
| import org.apache.pdfbox.cos.COSObject; |
| import org.apache.pdfbox.cos.COSStream; |
| import org.apache.pdfbox.pdmodel.common.COSArrayList; |
| import org.apache.pdfbox.pdmodel.common.COSObjectable; |
| import org.apache.pdfbox.pdmodel.common.PDDestinationOrAction; |
| import org.apache.pdfbox.pdmodel.common.PDMetadata; |
| import org.apache.pdfbox.pdmodel.common.PDPageLabels; |
| import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo; |
| import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot; |
| import org.apache.pdfbox.pdmodel.fixup.AcroFormDefaultFixup; |
| import org.apache.pdfbox.pdmodel.fixup.PDDocumentFixup; |
| import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent; |
| import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentProperties; |
| import org.apache.pdfbox.pdmodel.interactive.action.PDActionFactory; |
| import org.apache.pdfbox.pdmodel.interactive.action.PDDocumentCatalogAdditionalActions; |
| import org.apache.pdfbox.pdmodel.interactive.action.PDURIDictionary; |
| import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination; |
| import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDNamedDestination; |
| import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageDestination; |
| import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline; |
| import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; |
| import org.apache.pdfbox.pdmodel.interactive.pagenavigation.PDThread; |
| import org.apache.pdfbox.pdmodel.interactive.viewerpreferences.PDViewerPreferences; |
| |
| /** |
| * The Document Catalog of a PDF. |
| * |
| * @author Ben Litchfield |
| */ |
| public class PDDocumentCatalog implements COSObjectable |
| { |
| private static final Logger LOG = LogManager.getLogger(PDDocumentCatalog.class); |
| |
| private final COSDictionary root; |
| private final PDDocument document; |
| private PDDocumentFixup acroFormFixupApplied; |
| private PDAcroForm cachedAcroForm; |
| |
| /** |
| * Constructor. Internal PDFBox use only! If you need to get the document catalog, call |
| * {@link PDDocument#getDocumentCatalog()}. |
| * |
| * @param doc The document that this catalog is part of. |
| */ |
| protected PDDocumentCatalog(PDDocument doc) |
| { |
| document = doc; |
| root = new COSDictionary(); |
| root.setItem(COSName.TYPE, COSName.CATALOG); |
| document.getDocument().getTrailer().setItem(COSName.ROOT, root); |
| } |
| |
| /** |
| * Constructor. Internal PDFBox use only! If you need to get the document catalog, call |
| * {@link PDDocument#getDocumentCatalog()}. |
| * |
| * @param doc The document that this catalog is part of. |
| * @param rootDictionary The root dictionary that this object wraps. |
| */ |
| protected PDDocumentCatalog(PDDocument doc, COSDictionary rootDictionary) |
| { |
| document = doc; |
| root = rootDictionary; |
| } |
| |
| /** |
| * Convert this standard java object to a COS object. |
| * |
| * @return The cos object that matches this Java object. |
| */ |
| @Override |
| public COSDictionary getCOSObject() |
| { |
| return root; |
| } |
| |
| /** |
| * Get the documents AcroForm. This will return null if no AcroForm is part of the document. |
| * |
| * @return The document's AcroForm. |
| */ |
| public PDAcroForm getAcroForm() |
| { |
| return getAcroForm(new AcroFormDefaultFixup(document)); |
| } |
| |
| /** |
| * Get the documents AcroForm. This will return null if no AcroForm is part of the document. |
| * |
| * Dependent on setting <code>acroFormFixup</code> some fixing/changes will be done to the AcroForm. |
| * If you need to ensure that there are no fixes applied call <code>getAcroForm</code> with <code>null</code>. |
| * |
| * Using <code>getAcroForm(PDDocumentFixup acroFormFixup)</code> might change the original content and |
| * subsequent calls with <code>getAcroForm(null)</code> will return the changed content. |
| * |
| * @param acroFormFixup the fix up action or null |
| * @return The document's AcroForm. |
| */ |
| public PDAcroForm getAcroForm(PDDocumentFixup acroFormFixup) |
| { |
| if (acroFormFixup != null && acroFormFixup != acroFormFixupApplied) |
| { |
| acroFormFixup.apply(); |
| cachedAcroForm = null; |
| acroFormFixupApplied = acroFormFixup; |
| } |
| else if (acroFormFixupApplied != null) |
| { |
| LOG.debug("AcroForm content has already been retrieved with fixes applied - original content changed because of that"); |
| } |
| if (cachedAcroForm == null) |
| { |
| COSDictionary dict = root.getCOSDictionary(COSName.ACRO_FORM); |
| cachedAcroForm = dict == null ? null : new PDAcroForm(document, dict); |
| } |
| return cachedAcroForm; |
| } |
| |
| /** |
| * Sets the AcroForm for this catalog. |
| * |
| * @param acroForm The new AcroForm. |
| */ |
| public void setAcroForm(PDAcroForm acroForm) |
| { |
| root.setItem(COSName.ACRO_FORM, acroForm); |
| cachedAcroForm = null; |
| } |
| |
| /** |
| * Returns all pages in the document, as a page tree. |
| * |
| * @return PDPageTree providing all pages of the document |
| */ |
| public PDPageTree getPages() |
| { |
| // todo: cache me? |
| return new PDPageTree(root.getCOSDictionary(COSName.PAGES), document); |
| } |
| |
| /** |
| * Get the viewer preferences associated with this document or null if they do not exist. |
| * |
| * @return The document's viewer preferences. |
| */ |
| public PDViewerPreferences getViewerPreferences() |
| { |
| COSDictionary viewerPref = root.getCOSDictionary(COSName.VIEWER_PREFERENCES); |
| return viewerPref != null ? new PDViewerPreferences(viewerPref) : null; |
| } |
| |
| /** |
| * Sets the viewer preferences. |
| * |
| * @param prefs The new viewer preferences. |
| */ |
| public void setViewerPreferences(PDViewerPreferences prefs) |
| { |
| root.setItem(COSName.VIEWER_PREFERENCES, prefs); |
| } |
| |
| /** |
| * Get the outline associated with this document or null if it does not exist. |
| * |
| * @return The document's outline. |
| */ |
| public PDDocumentOutline getDocumentOutline() |
| { |
| COSDictionary outlineDict = root.getCOSDictionary(COSName.OUTLINES); |
| return outlineDict != null ? new PDDocumentOutline(outlineDict) : null; |
| } |
| |
| /** |
| * Sets the document outlines. |
| * |
| * @param outlines The new document outlines. |
| */ |
| public void setDocumentOutline(PDDocumentOutline outlines) |
| { |
| root.setItem(COSName.OUTLINES, outlines); |
| } |
| |
| /** |
| * Returns the document's article threads. |
| * |
| * @return a list of all threads of the document |
| */ |
| public List<PDThread> getThreads() |
| { |
| COSArray array = root.getCOSArray(COSName.THREADS); |
| if (array == null) |
| { |
| array = new COSArray(); |
| array.setDirect(false); |
| root.setItem(COSName.THREADS, array); |
| } |
| List<PDThread> pdObjects = new ArrayList<>(array.size()); |
| for (int i = 0; i < array.size(); i++) |
| { |
| pdObjects.add(new PDThread((COSDictionary)array.getObject(i))); |
| } |
| return new COSArrayList<>(pdObjects, array); |
| } |
| |
| /** |
| * Sets the list of threads for this pdf document. |
| * |
| * @param threads The list of threads, or null to clear it. |
| */ |
| public void setThreads(List<PDThread> threads) |
| { |
| COSArray threadsArray = new COSArray(threads); |
| threadsArray.setDirect(false); |
| root.setItem(COSName.THREADS, threadsArray); |
| } |
| |
| /** |
| * Get the metadata that is part of the document catalog. This will return null if there is no |
| * meta data for this object. |
| * |
| * @return The metadata for this object. |
| */ |
| public PDMetadata getMetadata() |
| { |
| COSStream metaObj = root.getCOSStream(COSName.METADATA); |
| return metaObj != null ? new PDMetadata(metaObj) : null; |
| } |
| |
| /** |
| * Sets the metadata for this object. This can be null. |
| * |
| * @param meta The meta data for this object. |
| */ |
| public void setMetadata(PDMetadata meta) |
| { |
| root.setItem(COSName.METADATA, meta); |
| } |
| |
| /** |
| * Sets the Document Open Action for this object. |
| * |
| * @param action The action you want to perform. |
| */ |
| public void setOpenAction(PDDestinationOrAction action) |
| { |
| root.setItem(COSName.OPEN_ACTION, action); |
| } |
| |
| /** |
| * Get the Document Open Action for this object. |
| * |
| * @return The action to perform when the document is opened. |
| * @throws IOException If there is an error creating the destination or action. |
| */ |
| public PDDestinationOrAction getOpenAction() throws IOException |
| { |
| COSBase openAction = root.getDictionaryObject(COSName.OPEN_ACTION); |
| if (openAction instanceof COSDictionary) |
| { |
| return PDActionFactory.createAction((COSDictionary)openAction); |
| } |
| else if (openAction instanceof COSArray) |
| { |
| return PDDestination.create(openAction); |
| } |
| else |
| { |
| return null; |
| } |
| } |
| /** |
| * @return The Additional Actions for this Document |
| */ |
| public PDDocumentCatalogAdditionalActions getActions() |
| { |
| COSDictionary addAction = root.getCOSDictionary(COSName.AA); |
| if (addAction == null) |
| { |
| addAction = new COSDictionary(); |
| root.setItem(COSName.AA, addAction); |
| } |
| return new PDDocumentCatalogAdditionalActions(addAction); |
| } |
| |
| /** |
| * Sets the additional actions for the document. |
| * |
| * @param actions The actions that are associated with this document. |
| */ |
| public void setActions(PDDocumentCatalogAdditionalActions actions) |
| { |
| root.setItem(COSName.AA, actions); |
| } |
| |
| /** |
| * @return The names dictionary for this document or null if none exist. |
| */ |
| public PDDocumentNameDictionary getNames() |
| { |
| COSDictionary names = root.getCOSDictionary(COSName.NAMES); |
| return names == null ? null : new PDDocumentNameDictionary(this, names); |
| } |
| |
| /** |
| * @return The named destinations dictionary for this document or null if none exists. |
| */ |
| public PDDocumentNameDestinationDictionary getDests() |
| { |
| COSDictionary dests = root.getCOSDictionary(COSName.DESTS); |
| return dests != null ? new PDDocumentNameDestinationDictionary(dests) : null; |
| } |
| |
| /** |
| * Find the page destination from a named destination. |
| * @param namedDest the named destination. |
| * @return a PDPageDestination object or null if not found. |
| * @throws IOException if there is an error creating the PDPageDestination object. |
| */ |
| public PDPageDestination findNamedDestinationPage(PDNamedDestination namedDest) |
| throws IOException |
| { |
| PDPageDestination pageDestination = null; |
| PDDocumentNameDictionary namesDict = getNames(); |
| if (namesDict != null) |
| { |
| PDDestinationNameTreeNode destsTree = namesDict.getDests(); |
| if (destsTree != null) |
| { |
| pageDestination = destsTree.getValue(namedDest.getNamedDestination()); |
| } |
| } |
| if (pageDestination == null) |
| { |
| // Look up /Dests dictionary from catalog |
| PDDocumentNameDestinationDictionary nameDestDict = getDests(); |
| if (nameDestDict != null) |
| { |
| String name = namedDest.getNamedDestination(); |
| pageDestination = (PDPageDestination) nameDestDict.getDestination(name); |
| } |
| } |
| return pageDestination; |
| } |
| |
| /** |
| * Sets the names dictionary for the document. |
| * |
| * @param names The names dictionary that is associated with this document. |
| */ |
| public void setNames(PDDocumentNameDictionary names) |
| { |
| root.setItem(COSName.NAMES, names); |
| } |
| |
| /** |
| * Get info about doc's usage of tagged features. This will return null if there is no |
| * information. |
| * |
| * @return The new mark info. |
| */ |
| public PDMarkInfo getMarkInfo() |
| { |
| COSDictionary dic = root.getCOSDictionary(COSName.MARK_INFO); |
| return dic == null ? null : new PDMarkInfo(dic); |
| } |
| |
| /** |
| * Set information about the doc's usage of tagged features. |
| * |
| * @param markInfo The new MarkInfo data. |
| */ |
| public void setMarkInfo(PDMarkInfo markInfo) |
| { |
| root.setItem(COSName.MARK_INFO, markInfo); |
| } |
| |
| /** |
| * Get the list of OutputIntents defined in the document. |
| * |
| * @return The list of PDOutputIntent, never null. |
| */ |
| public List<PDOutputIntent> getOutputIntents() |
| { |
| List<PDOutputIntent> retval = new ArrayList<>(); |
| COSArray array = root.getCOSArray(COSName.OUTPUT_INTENTS); |
| if (array != null) |
| { |
| for (COSBase cosBase : array) |
| { |
| if (cosBase instanceof COSObject) |
| { |
| cosBase = ((COSObject)cosBase).getObject(); |
| } |
| PDOutputIntent oi = new PDOutputIntent((COSDictionary) cosBase); |
| retval.add(oi); |
| } |
| } |
| return retval; |
| } |
| |
| /** |
| * Add an OutputIntent to the list. If there is not OutputIntent, the list is created and the |
| * first element added. |
| * |
| * @param outputIntent the OutputIntent to add. |
| */ |
| public void addOutputIntent(PDOutputIntent outputIntent) |
| { |
| COSArray array = root.getCOSArray(COSName.OUTPUT_INTENTS); |
| if (array == null) |
| { |
| array = new COSArray(); |
| root.setItem(COSName.OUTPUT_INTENTS, array); |
| } |
| array.add(outputIntent.getCOSObject()); |
| } |
| |
| /** |
| * Replace the list of OutputIntents of the document. |
| * |
| * @param outputIntents the list of OutputIntents, if the list is empty all OutputIntents are |
| * removed. |
| */ |
| public void setOutputIntents(List<PDOutputIntent> outputIntents) |
| { |
| COSArray array = new COSArray(); |
| for (PDOutputIntent intent : outputIntents) |
| { |
| array.add(intent.getCOSObject()); |
| } |
| root.setItem(COSName.OUTPUT_INTENTS, array); |
| } |
| |
| /** |
| * Returns the page display mode. |
| * |
| * @return the PageMode of the document, if not present PageMode.USE_NONE is returned |
| */ |
| public PageMode getPageMode() |
| { |
| String mode = root.getNameAsString(COSName.PAGE_MODE); |
| if (mode != null) |
| { |
| try |
| { |
| return PageMode.fromString(mode); |
| } |
| catch (IllegalArgumentException e) |
| { |
| LOG.debug("Invalid PageMode used '{}' - setting to PageMode.USE_NONE", mode, e); |
| return PageMode.USE_NONE; |
| } |
| } |
| else |
| { |
| return PageMode.USE_NONE; |
| } |
| } |
| |
| /** |
| * Sets the page mode. |
| * |
| * @param mode The new page mode. |
| */ |
| public void setPageMode(PageMode mode) |
| { |
| root.setName(COSName.PAGE_MODE, mode.stringValue()); |
| } |
| |
| /** |
| * Returns the page layout. |
| * |
| * @return the PageLayout of the document, if not present PageLayout.SINGLE_PAGE is returned |
| */ |
| public PageLayout getPageLayout() |
| { |
| String mode = root.getNameAsString(COSName.PAGE_LAYOUT); |
| if (mode != null && !mode.isEmpty()) |
| { |
| try |
| { |
| return PageLayout.fromString(mode); |
| } |
| catch (IllegalArgumentException e) |
| { |
| LOG.warn("Invalid PageLayout used '{}' - returning PageLayout.SINGLE_PAGE", mode, |
| e); |
| } |
| } |
| return PageLayout.SINGLE_PAGE; |
| } |
| |
| /** |
| * Sets the page layout. |
| * |
| * @param layout The new page layout. |
| */ |
| public void setPageLayout(PageLayout layout) |
| { |
| root.setName(COSName.PAGE_LAYOUT, layout.stringValue()); |
| } |
| |
| /** |
| * Returns the document-level URI. |
| * |
| * @return the document level URI if present, otherwise null |
| */ |
| public PDURIDictionary getURI() |
| { |
| COSDictionary uri = root.getCOSDictionary(COSName.URI); |
| return uri == null ? null : new PDURIDictionary(uri); |
| } |
| |
| /** |
| * Sets the document level URI. |
| * |
| * @param uri The new document level URI. |
| */ |
| public void setURI(PDURIDictionary uri) |
| { |
| root.setItem(COSName.URI, uri); |
| } |
| |
| /** |
| * Get the document's structure tree root, or null if none exists. |
| * |
| * @return the structure tree root if present, otherwise null |
| */ |
| public PDStructureTreeRoot getStructureTreeRoot() |
| { |
| COSDictionary dict = root.getCOSDictionary(COSName.STRUCT_TREE_ROOT); |
| return dict == null ? null : new PDStructureTreeRoot(dict); |
| } |
| |
| /** |
| * Sets the document's structure tree root. |
| * |
| * @param treeRoot The new structure tree. |
| */ |
| public void setStructureTreeRoot(PDStructureTreeRoot treeRoot) |
| { |
| root.setItem(COSName.STRUCT_TREE_ROOT, treeRoot); |
| } |
| |
| /** |
| * Returns the language for the document, or null. |
| * |
| * @return the language of the document if present, otherwise null |
| */ |
| public String getLanguage() |
| { |
| return root.getString(COSName.LANG); |
| } |
| |
| /** |
| * Sets the Language for the document. |
| * |
| * @param language The new document language. |
| */ |
| public void setLanguage(String language) |
| { |
| root.setString(COSName.LANG, language); |
| } |
| |
| /** |
| * Returns the PDF specification version this document conforms to. |
| * |
| * @return the PDF version (e.g. "1.4") |
| */ |
| public String getVersion() |
| { |
| return root.getNameAsString(COSName.VERSION); |
| } |
| |
| /** |
| * Sets the PDF specification version this document conforms to. |
| * |
| * @param version the PDF version (e.g. "1.4") |
| */ |
| public void setVersion(String version) |
| { |
| root.setName(COSName.VERSION, version); |
| } |
| |
| /** |
| * Returns the page labels descriptor of the document. |
| * |
| * @return the page labels descriptor of the document. |
| * @throws IOException If there is a problem retrieving the page labels. |
| */ |
| public PDPageLabels getPageLabels() throws IOException |
| { |
| COSDictionary dict = root.getCOSDictionary(COSName.PAGE_LABELS); |
| return dict == null ? null : new PDPageLabels(document, dict); |
| } |
| |
| /** |
| * Sets the page label descriptor for the document. |
| * |
| * @param labels the new page label descriptor to set. |
| */ |
| public void setPageLabels(PDPageLabels labels) |
| { |
| root.setItem(COSName.PAGE_LABELS, labels); |
| } |
| |
| /** |
| * Get the optional content properties dictionary associated with this document. |
| * |
| * @return the optional properties dictionary or null if it is not present |
| */ |
| public PDOptionalContentProperties getOCProperties() |
| { |
| COSDictionary dict = root.getCOSDictionary(COSName.OCPROPERTIES); |
| return dict == null ? null : new PDOptionalContentProperties(dict); |
| } |
| |
| /** |
| * Sets the optional content properties dictionary. The document version is incremented to 1.5 |
| * if lower. |
| * |
| * @param ocProperties the optional properties dictionary |
| */ |
| public void setOCProperties(PDOptionalContentProperties ocProperties) |
| { |
| root.setItem(COSName.OCPROPERTIES, ocProperties); |
| |
| // optional content groups require PDF 1.5 |
| if (ocProperties != null && document.getVersion() < 1.5) |
| { |
| document.setVersion(1.5f); |
| } |
| } |
| } |