blob: 62dfc69e7d7a19767b7255db8502170d5e1e4be9 [file] [log] [blame]
/*
* 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);
}
}
}