<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PDDocument.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Apache PDFBox</a> > <a href="index.source.html" class="el_package">org.apache.pdfbox.pdmodel</a> > <span class="el_source">PDDocument.java</span></div><h1>PDDocument.java</h1><pre class="source lang-java linenums">/* * 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.awt.Point; import java.awt.image.DataBuffer; import java.awt.image.Raster; import java.awt.image.WritableRaster; import java.io.BufferedOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fontbox.ttf.TrueTypeFont; import org.apache.pdfbox.cos.COSArray; import org.apache.pdfbox.cos.COSBase; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSDocument; import org.apache.pdfbox.cos.COSInteger; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSObject; import org.apache.pdfbox.cos.COSUpdateInfo; import org.apache.pdfbox.io.IOUtils; import org.apache.pdfbox.io.MemoryUsageSetting; import org.apache.pdfbox.io.RandomAccessRead; import org.apache.pdfbox.pdfwriter.COSWriter; import org.apache.pdfbox.pdfwriter.compress.CompressParameters; import org.apache.pdfbox.pdmodel.common.COSArrayList; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDStream; import org.apache.pdfbox.pdmodel.encryption.AccessPermission; import org.apache.pdfbox.pdmodel.encryption.PDEncryption; import org.apache.pdfbox.pdmodel.encryption.ProtectionPolicy; import org.apache.pdfbox.pdmodel.encryption.SecurityHandler; import org.apache.pdfbox.pdmodel.encryption.SecurityHandlerFactory; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.ExternalSigningSupport; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SigningSupport; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDField; import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; /** * This is the in-memory representation of the PDF document. * The #close() method must be called once the document is no longer needed. * * @author Ben Litchfield */ public class PDDocument implements Closeable { /** * For signing: large reserve byte range used as placeholder in the saved PDF until the actual * length of the PDF is known. You'll need to fetch (with * {@link PDSignature#getByteRange()} ) and reassign this yourself (with * {@link PDSignature#setByteRange(int[])} ) only if you call * {@link #saveIncrementalForExternalSigning(java.io.OutputStream) saveIncrementalForExternalSigning()} * twice. */ <span class="fc" id="L92"> private static final int[] RESERVE_BYTE_RANGE = new int[] { 0, 1000000000, 1000000000, 1000000000 };</span> <span class="fc" id="L94"> private static final Log LOG = LogFactory.getLog(PDDocument.class);</span> /** * avoid concurrency issues with PDDeviceRGB */ static { try { <span class="fc" id="L103"> WritableRaster raster = Raster.createBandedRaster(DataBuffer.TYPE_BYTE, 1, 1, 3, new Point(0, 0));</span> <span class="fc" id="L104"> PDDeviceRGB.INSTANCE.toRGBImage(raster);</span> } <span class="nc" id="L106"> catch (IOException ex)</span> { <span class="nc" id="L108"> LOG.debug("voodoo error", ex);</span> <span class="fc" id="L109"> }</span> <span class="fc" id="L110"> }</span> private final COSDocument document; // cached values private PDDocumentInformation documentInformation; private PDDocumentCatalog documentCatalog; // the encryption will be cached here. When the document is decrypted then // the COSDocument will not have an "Encrypt" dictionary anymore and this object must be used private PDEncryption encryption; // holds a flag which tells us if we should remove all security from this documents. private boolean allSecurityToBeRemoved; // keep tracking customized documentId for the trailer. If null, a new id will be generated // this ID doesn't represent the actual documentId from the trailer private Long documentId; // the pdf to be read private final RandomAccessRead pdfSource; // the access permissions of the document private AccessPermission accessPermission; // fonts to subset before saving <span class="fc" id="L136"> private final Set<PDFont> fontsToSubset = new HashSet<>();</span> // fonts to close when closing document <span class="fc" id="L139"> private final Set<TrueTypeFont> fontsToClose = new HashSet<>();</span> // Signature interface private SignatureInterface signInterface; // helper class used to create external signature private SigningSupport signingSupport; // document-wide cached resources <span class="fc" id="L148"> private ResourceCache resourceCache = new DefaultResourceCache();</span> // to make sure only one signature is added <span class="fc" id="L151"> private boolean signatureAdded = false;</span> /** * Creates an empty PDF document. * You need to add at least one page for the document to be valid. */ public PDDocument() { <span class="fc" id="L159"> this(MemoryUsageSetting.setupMainMemoryOnly());</span> <span class="fc" id="L160"> }</span> /** * Creates an empty PDF document. * You need to add at least one page for the document to be valid. * * @param memUsageSetting defines how memory is used for buffering PDF streams */ public PDDocument(MemoryUsageSetting memUsageSetting) <span class="fc" id="L169"> {</span> <span class="fc" id="L170"> document = new COSDocument(memUsageSetting);</span> <span class="fc" id="L171"> document.getDocumentState().setParsing(false);</span> <span class="fc" id="L172"> pdfSource = null;</span> // First we need a trailer <span class="fc" id="L175"> COSDictionary trailer = new COSDictionary();</span> <span class="fc" id="L176"> document.setTrailer(trailer);</span> // Next we need the root dictionary. <span class="fc" id="L179"> COSDictionary rootDictionary = new COSDictionary();</span> <span class="fc" id="L180"> trailer.setItem(COSName.ROOT, rootDictionary);</span> <span class="fc" id="L181"> rootDictionary.setItem(COSName.TYPE, COSName.CATALOG);</span> <span class="fc" id="L182"> rootDictionary.setItem(COSName.VERSION, COSName.getPDFName("1.4"));</span> // next we need the pages tree structure <span class="fc" id="L185"> COSDictionary pages = new COSDictionary();</span> <span class="fc" id="L186"> rootDictionary.setItem(COSName.PAGES, pages);</span> <span class="fc" id="L187"> pages.setItem(COSName.TYPE, COSName.PAGES);</span> <span class="fc" id="L188"> COSArray kidsArray = new COSArray();</span> <span class="fc" id="L189"> pages.setItem(COSName.KIDS, kidsArray);</span> <span class="fc" id="L190"> pages.setItem(COSName.COUNT, COSInteger.ZERO);</span> <span class="fc" id="L191"> }</span> /** * Constructor that uses an existing document. The COSDocument that is passed in must be valid. * * @param doc The COSDocument that this document wraps. */ public PDDocument(COSDocument doc) { <span class="nc" id="L200"> this(doc, null);</span> <span class="nc" id="L201"> }</span> /** * Constructor that uses an existing document. The COSDocument that is passed in must be valid. * * @param doc The COSDocument that this document wraps. * @param source input representing the pdf */ public PDDocument(COSDocument doc, RandomAccessRead source) { <span class="nc" id="L211"> this(doc, source, null);</span> <span class="nc" id="L212"> }</span> /** * Constructor that uses an existing document. The COSDocument that is passed in must be valid. * * @param doc The COSDocument that this document wraps. * @param source input representing the pdf * @param permission he access permissions of the pdf * */ public PDDocument(COSDocument doc, RandomAccessRead source, AccessPermission permission) <span class="fc" id="L223"> {</span> <span class="fc" id="L224"> document = doc;</span> <span class="fc" id="L225"> document.getDocumentState().setParsing(false);</span> <span class="fc" id="L226"> pdfSource = source;</span> <span class="fc" id="L227"> accessPermission = permission;</span> <span class="fc" id="L228"> }</span> /** * This will add a page to the document. This is a convenience method, that will add the page to the root of the * hierarchy and set the parent of the page to the root. * * @param page The page to add to the document. */ public void addPage(PDPage page) { <span class="fc" id="L238"> getPages().add(page);</span> <span class="fc" id="L239"> }</span> /** * Add parameters of signature to be created externally using default signature options. See * {@link #saveIncrementalForExternalSigning(OutputStream)} method description on external * signature creation scenario details. * <p> * Only one signature may be added in a document. To sign several times, * load document, add signature, save incremental and close again. * * @param sigObject is the PDSignatureField model * @throws IOException if there is an error creating required fields * @throws IllegalStateException if one attempts to add several signature * fields. */ public void addSignature(PDSignature sigObject) throws IOException { <span class="nc" id="L256"> addSignature(sigObject, new SignatureOptions());</span> <span class="nc" id="L257"> }</span> /** * Add parameters of signature to be created externally. See * {@link #saveIncrementalForExternalSigning(OutputStream)} method description on external * signature creation scenario details. * <p> * Only one signature may be added in a document. To sign several times, * load document, add signature, save incremental and close again. * * @param sigObject is the PDSignatureField model * @param options signature options * @throws IOException if there is an error creating required fields * @throws IllegalStateException if one attempts to add several signature * fields. */ public void addSignature(PDSignature sigObject, SignatureOptions options) throws IOException { <span class="nc" id="L275"> addSignature(sigObject, null, options);</span> <span class="nc" id="L276"> }</span> /** * Add a signature to be created using the instance of given interface. * <p> * Only one signature may be added in a document. To sign several times, * load document, add signature, save incremental and close again. * * @param sigObject is the PDSignatureField model * @param signatureInterface is an interface whose implementation provides * signing capabilities. Can be null if external signing if used. * @throws IOException if there is an error creating required fields * @throws IllegalStateException if one attempts to add several signature * fields. */ public void addSignature(PDSignature sigObject, SignatureInterface signatureInterface) throws IOException { <span class="nc" id="L293"> addSignature(sigObject, signatureInterface, new SignatureOptions());</span> <span class="nc" id="L294"> }</span> /** * This will add a signature to the document. If the 0-based page number in the options * parameter is smaller than 0 or larger than max, the nearest valid page number will be used * (i.e. 0 or max) and no exception will be thrown. * <p> * Only one signature may be added in a document. To sign several times, * load document, add signature, save incremental and close again. * * @param sigObject is the PDSignatureField model * @param signatureInterface is an interface whose implementation provides * signing capabilities. Can be null if external signing if used. * @param options signature options * @throws IOException if there is an error creating required fields * @throws IllegalStateException if one attempts to add several signature * fields. */ public void addSignature(PDSignature sigObject, SignatureInterface signatureInterface, SignatureOptions options) throws IOException { <span class="nc bnc" id="L315" title="All 2 branches missed."> if (signatureAdded)</span> { <span class="nc" id="L317"> throw new IllegalStateException("Only one signature may be added in a document");</span> } <span class="nc" id="L319"> signatureAdded = true;</span> // Reserve content // We need to reserve some space for the signature. Some signatures including // big certificate chain and we need enough space to store it. <span class="nc" id="L324"> int preferredSignatureSize = options.getPreferredSignatureSize();</span> <span class="nc bnc" id="L325" title="All 2 branches missed."> if (preferredSignatureSize > 0)</span> { <span class="nc" id="L327"> sigObject.setContents(new byte[preferredSignatureSize]);</span> } else { <span class="nc" id="L331"> sigObject.setContents(new byte[SignatureOptions.DEFAULT_SIGNATURE_SIZE]);</span> } // Reserve ByteRange, will be overwritten in COSWriter <span class="nc" id="L335"> sigObject.setByteRange(RESERVE_BYTE_RANGE);</span> <span class="nc" id="L337"> signInterface = signatureInterface;</span> // Create SignatureForm for signature and append it to the document // Get the first valid page <span class="nc" id="L342"> PDPageTree pageTree = getPages();</span> <span class="nc" id="L343"> int pageCount = pageTree.getCount();</span> <span class="nc bnc" id="L344" title="All 2 branches missed."> if (pageCount == 0)</span> { <span class="nc" id="L346"> throw new IllegalStateException("Cannot sign an empty document");</span> } <span class="nc" id="L349"> int startIndex = Math.min(Math.max(options.getPage(), 0), pageCount - 1);</span> <span class="nc" id="L350"> PDPage page = pageTree.get(startIndex);</span> // Get the AcroForm from the Root-Dictionary and append the annotation <span class="nc" id="L353"> PDDocumentCatalog catalog = getDocumentCatalog();</span> <span class="nc" id="L354"> PDAcroForm acroForm = catalog.getAcroForm(null);</span> <span class="nc" id="L355"> catalog.getCOSObject().setNeedToBeUpdated(true);</span> <span class="nc bnc" id="L357" title="All 2 branches missed."> if (acroForm == null)</span> { <span class="nc" id="L359"> acroForm = new PDAcroForm(this);</span> <span class="nc" id="L360"> catalog.setAcroForm(acroForm);</span> } else { <span class="nc" id="L364"> acroForm.getCOSObject().setNeedToBeUpdated(true);</span> } <span class="nc" id="L367"> PDSignatureField signatureField = null;</span> <span class="nc" id="L368"> COSBase cosFieldBase = acroForm.getCOSObject().getDictionaryObject(COSName.FIELDS);</span> <span class="nc bnc" id="L369" title="All 2 branches missed."> if (cosFieldBase instanceof COSArray)</span> { <span class="nc" id="L371"> COSArray fieldArray = (COSArray) cosFieldBase;</span> <span class="nc" id="L372"> fieldArray.setNeedToBeUpdated(true);</span> <span class="nc" id="L373"> signatureField = findSignatureField(acroForm.getFieldIterator(), sigObject);</span> <span class="nc" id="L374"> }</span> else { <span class="nc" id="L377"> acroForm.getCOSObject().setItem(COSName.FIELDS, new COSArray());</span> } PDAnnotationWidget firstWidget; <span class="nc bnc" id="L380" title="All 2 branches missed."> if (signatureField == null)</span> { <span class="nc" id="L382"> signatureField = new PDSignatureField(acroForm);</span> // append the signature object <span class="nc" id="L384"> signatureField.setValue(sigObject);</span> <span class="nc" id="L385"> firstWidget = signatureField.getWidgets().get(0);</span> // backward linking <span class="nc" id="L387"> firstWidget.setPage(page);</span> } else { <span class="nc" id="L391"> firstWidget = signatureField.getWidgets().get(0);</span> <span class="nc" id="L392"> sigObject.getCOSObject().setNeedToBeUpdated(true);</span> } // TODO This "overwrites" the settings of the original signature field which might not be intended by the user // better make it configurable (not all users need/want PDF/A but their own setting): // to conform PDF/A-1 requirement: // The /F key's Print flag bit shall be set to 1 and // its Hidden, Invisible and NoView flag bits shall be set to 0 <span class="nc" id="L401"> firstWidget.setPrinted(true);</span> // This may be troublesome if several form fields are signed, // see thread from PDFBox users mailing list 17.2.2021 - 19.2.2021 // https://mail-archives.apache.org/mod_mbox/pdfbox-users/202102.mbox/thread // better set the printed flag in advance // Set the AcroForm Fields <span class="nc" id="L408"> List<PDField> acroFormFields = acroForm.getFields();</span> <span class="nc" id="L409"> acroForm.getCOSObject().setDirect(true);</span> <span class="nc" id="L410"> acroForm.setSignaturesExist(true);</span> <span class="nc" id="L411"> acroForm.setAppendOnly(true);</span> <span class="nc" id="L413"> boolean checkFields = checkSignatureField(acroForm.getFieldIterator(), signatureField);</span> <span class="nc bnc" id="L414" title="All 2 branches missed."> if (checkFields)</span> { <span class="nc" id="L416"> signatureField.getCOSObject().setNeedToBeUpdated(true);</span> } else { <span class="nc" id="L420"> acroFormFields.add(signatureField);</span> } // Get the object from the visual signature <span class="nc" id="L424"> COSDocument visualSignature = options.getVisualSignature();</span> // Distinction of case for visual and non-visual signature <span class="nc bnc" id="L427" title="All 2 branches missed."> if (visualSignature == null)</span> { <span class="nc" id="L429"> prepareNonVisibleSignature(firstWidget);</span> <span class="nc" id="L430"> return;</span> } <span class="nc" id="L433"> prepareVisibleSignature(firstWidget, acroForm, visualSignature);</span> // Create Annotation / Field for signature <span class="nc" id="L436"> List<PDAnnotation> annotations = page.getAnnotations();</span> // Get the annotations of the page and append the signature-annotation to it // take care that page and acroforms do not share the same array (if so, we don't need to add it twice) <span class="nc bnc" id="L440" title="All 6 branches missed."> if (!(checkFields &&</span> annotations instanceof COSArrayList && acroFormFields instanceof COSArrayList && <span class="nc" id="L443"> ((COSArrayList<PDAnnotation>) annotations).toList().</span> <span class="nc bnc" id="L444" title="All 2 branches missed."> equals(((COSArrayList<PDField>) acroFormFields).toList())))</span> { // use check to prevent the annotation widget from appearing twice <span class="nc bnc" id="L447" title="All 2 branches missed."> if (checkSignatureAnnotation(annotations, firstWidget))</span> { <span class="nc" id="L449"> firstWidget.getCOSObject().setNeedToBeUpdated(true);</span> } else { <span class="nc" id="L453"> annotations.add(firstWidget);</span> } } // Make /Annots a direct object by reassigning it, // to avoid problem if it is an existing indirect object: // it would not be updated in incremental save, and if we'd set the /Annots array "to be updated" // while keeping it indirect, Adobe Reader would claim that the document had been modified. <span class="nc" id="L461"> page.setAnnotations(annotations);</span> <span class="nc" id="L463"> page.getCOSObject().setNeedToBeUpdated(true);</span> <span class="nc" id="L464"> }</span> /** * Search acroform fields for signature field with specific signature dictionary. * * @param fieldIterator iterator on all fields. * @param sigObject signature object (the /V part). * @return a signature field if found, or null if none was found. */ private PDSignatureField findSignatureField(Iterator<PDField> fieldIterator, PDSignature sigObject) { <span class="nc" id="L475"> PDSignatureField signatureField = null;</span> <span class="nc bnc" id="L476" title="All 2 branches missed."> while (fieldIterator.hasNext())</span> { <span class="nc" id="L478"> PDField pdField = fieldIterator.next();</span> <span class="nc bnc" id="L479" title="All 2 branches missed."> if (pdField instanceof PDSignatureField)</span> { <span class="nc" id="L481"> PDSignature signature = ((PDSignatureField) pdField).getSignature();</span> <span class="nc bnc" id="L482" title="All 4 branches missed."> if (signature != null && signature.getCOSObject().equals(sigObject.getCOSObject()))</span> { <span class="nc" id="L484"> signatureField = (PDSignatureField) pdField;</span> <span class="nc" id="L485"> break;</span> } } <span class="nc" id="L488"> }</span> <span class="nc" id="L489"> return signatureField;</span> } /** * Check if the field already exists in the field list. * * @param fieldIterator iterator on all fields. * @param signatureField the signature field. * @return true if the field already existed in the field list, false if not. */ private boolean checkSignatureField(Iterator<PDField> fieldIterator, PDSignatureField signatureField) { <span class="nc bnc" id="L501" title="All 2 branches missed."> while (fieldIterator.hasNext())</span> { <span class="nc" id="L503"> PDField field = fieldIterator.next();</span> <span class="nc bnc" id="L504" title="All 2 branches missed."> if (field instanceof PDSignatureField</span> <span class="nc bnc" id="L505" title="All 2 branches missed."> && field.getCOSObject().equals(signatureField.getCOSObject()))</span> { <span class="nc" id="L507"> return true;</span> } <span class="nc" id="L509"> }</span> <span class="nc" id="L510"> return false;</span> } /** * Check if the widget already exists in the annotation list. * * @param annotations the list of PDAnnotation fields. * @param widget the annotation widget. * @return true if the widget already existed in the annotation list, false if not. */ private boolean checkSignatureAnnotation(List<PDAnnotation> annotations, PDAnnotationWidget widget) { <span class="nc bnc" id="L522" title="All 2 branches missed."> for (PDAnnotation annotation : annotations)</span> { <span class="nc bnc" id="L524" title="All 2 branches missed."> if (annotation.getCOSObject().equals(widget.getCOSObject()))</span> { <span class="nc" id="L526"> return true;</span> } <span class="nc" id="L528"> }</span> <span class="nc" id="L529"> return false;</span> } private void prepareVisibleSignature(PDAnnotationWidget firstWidget, PDAcroForm acroForm, COSDocument visualSignature) { // Obtain visual signature object <span class="nc" id="L536"> boolean annotFound = false;</span> <span class="nc" id="L537"> boolean sigFieldFound = false;</span> // get all objects <span class="nc" id="L539"> List<COSObject> cosObjects = visualSignature.getXrefTable().keySet().stream() //</span> <span class="nc" id="L540"> .map(visualSignature::getObjectFromPool) //</span> <span class="nc" id="L541"> .collect(Collectors.toList());</span> <span class="nc bnc" id="L542" title="All 2 branches missed."> for (COSObject cosObject : cosObjects)</span> { <span class="nc" id="L544"> COSBase base = cosObject.getObject();</span> <span class="nc bnc" id="L545" title="All 2 branches missed."> if (base instanceof COSDictionary)</span> { <span class="nc" id="L547"> COSDictionary cosBaseDict = (COSDictionary) base;</span> // Search for signature annotation <span class="nc bnc" id="L549" title="All 4 branches missed."> if (!annotFound && COSName.ANNOT.equals(cosBaseDict.getCOSName(COSName.TYPE)))</span> { <span class="nc" id="L551"> assignSignatureRectangle(firstWidget, cosBaseDict);</span> <span class="nc" id="L552"> annotFound = true;</span> } // Search for signature field <span class="nc" id="L555"> COSDictionary apDict = cosBaseDict.getCOSDictionary(COSName.AP);</span> <span class="nc bnc" id="L556" title="All 4 branches missed."> if (apDict != null && !sigFieldFound</span> <span class="nc bnc" id="L557" title="All 2 branches missed."> && COSName.SIG.equals(cosBaseDict.getCOSName(COSName.FT)))</span> { <span class="nc" id="L559"> assignAppearanceDictionary(firstWidget, apDict);</span> <span class="nc" id="L560"> assignAcroFormDefaultResource(acroForm, cosBaseDict);</span> <span class="nc" id="L561"> sigFieldFound = true;</span> } <span class="nc bnc" id="L563" title="All 4 branches missed."> if (annotFound && sigFieldFound)</span> { <span class="nc" id="L565"> break;</span> } } <span class="nc" id="L568"> }</span> <span class="nc bnc" id="L569" title="All 4 branches missed."> if (!annotFound || !sigFieldFound)</span> { <span class="nc" id="L571"> throw new IllegalArgumentException("Template is missing required objects");</span> } <span class="nc" id="L573"> }</span> private void assignSignatureRectangle(PDAnnotationWidget firstWidget, COSDictionary annotDict) { // Read and set the rectangle for visual signature <span class="nc" id="L578"> PDRectangle existingRectangle = firstWidget.getRectangle();</span> //in case of an existing field keep the original rect <span class="nc bnc" id="L581" title="All 4 branches missed."> if (existingRectangle == null || existingRectangle.getCOSArray().size() != 4)</span> { <span class="nc" id="L583"> COSArray rectArray = annotDict.getCOSArray(COSName.RECT);</span> <span class="nc" id="L584"> PDRectangle rect = new PDRectangle(rectArray);</span> <span class="nc" id="L585"> firstWidget.setRectangle(rect);</span> } <span class="nc" id="L587"> }</span> private void assignAppearanceDictionary(PDAnnotationWidget firstWidget, COSDictionary apDict) { // read and set Appearance Dictionary <span class="nc" id="L592"> PDAppearanceDictionary ap = new PDAppearanceDictionary(apDict);</span> <span class="nc" id="L593"> apDict.setDirect(true);</span> <span class="nc" id="L594"> firstWidget.setAppearance(ap);</span> <span class="nc" id="L595"> }</span> private void assignAcroFormDefaultResource(PDAcroForm acroForm, COSDictionary newDict) { // read and set/update AcroForm default resource dictionary /DR if available <span class="nc" id="L600"> COSDictionary newDR = newDict.getCOSDictionary(COSName.DR);</span> <span class="nc bnc" id="L601" title="All 2 branches missed."> if (newDR != null)</span> { <span class="nc" id="L603"> PDResources defaultResources = acroForm.getDefaultResources();</span> <span class="nc bnc" id="L604" title="All 2 branches missed."> if (defaultResources == null)</span> { <span class="nc" id="L606"> acroForm.getCOSObject().setItem(COSName.DR, newDR);</span> <span class="nc" id="L607"> newDR.setDirect(true);</span> <span class="nc" id="L608"> newDR.setNeedToBeUpdated(true); </span> } else { <span class="nc" id="L612"> COSDictionary oldDR = defaultResources.getCOSObject();</span> <span class="nc" id="L613"> COSDictionary newXObject = newDR.getCOSDictionary(COSName.XOBJECT);</span> <span class="nc" id="L614"> COSDictionary oldXObject = oldDR.getCOSDictionary(COSName.XOBJECT);</span> <span class="nc bnc" id="L615" title="All 4 branches missed."> if (newXObject != null && oldXObject != null)</span> { <span class="nc" id="L617"> oldXObject.addAll(newXObject);</span> <span class="nc" id="L618"> oldDR.setNeedToBeUpdated(true);</span> } } } <span class="nc" id="L622"> }</span> private void prepareNonVisibleSignature(PDAnnotationWidget firstWidget) { // "Signature fields that are not intended to be visible shall // have an annotation rectangle that has zero height and width." // Set rectangle for non-visual signature to rectangle array [ 0 0 0 0 ] <span class="nc" id="L629"> firstWidget.setRectangle(new PDRectangle());</span> // The visual appearance must also exist for an invisible signature but may be empty. <span class="nc" id="L632"> PDAppearanceDictionary appearanceDictionary = new PDAppearanceDictionary();</span> <span class="nc" id="L633"> PDAppearanceStream appearanceStream = new PDAppearanceStream(this);</span> <span class="nc" id="L634"> appearanceStream.setBBox(new PDRectangle());</span> <span class="nc" id="L635"> appearanceDictionary.setNormalAppearance(appearanceStream);</span> <span class="nc" id="L636"> firstWidget.setAppearance(appearanceDictionary);</span> <span class="nc" id="L637"> }</span> /** * Remove the page from the document. * * @param page The page to remove from the document. */ public void removePage(PDPage page) { <span class="fc" id="L646"> getPages().remove(page);</span> <span class="fc" id="L647"> }</span> /** * Remove the page from the document. * * @param pageNumber 0 based index to page number. */ public void removePage(int pageNumber) { <span class="nc" id="L656"> getPages().remove(pageNumber);</span> <span class="nc" id="L657"> }</span> /** * This will import and copy the contents from another location. Currently the content stream is * stored in a scratch file. The scratch file is associated with the document. If you are adding * a page to this document from another document and want to copy the contents to this * document's scratch file then use this method otherwise just use the {@link #addPage addPage()} * method. * <p> * Unlike {@link #addPage addPage()}, this method creates a new PDPage object. If your page has * annotations, and if these link to pages not in the target document, then the target document * might become huge. What you need to do is to delete page references of such annotations. See * <a href="http://stackoverflow.com/a/35477351/535646">here</a> for how to do this. * <p> * Inherited (global) resources are ignored because these can contain resources not needed for * this page which could bloat your document, see * <a href="https://issues.apache.org/jira/browse/PDFBOX-28">PDFBOX-28</a> and related issues. * If you need them, call <code>importedPage.setResources(page.getResources());</code> * <p> * This method should only be used to import a page from a loaded document, not from a generated * document because these can contain unfinished parts, e.g. font subsetting information. * * @param page The page to import. * @return The page that was imported. * * @throws IOException If there is an error copying the page. */ public PDPage importPage(PDPage page) throws IOException { <span class="fc" id="L686"> PDPage importedPage = new PDPage(new COSDictionary(page.getCOSObject()), resourceCache);</span> <span class="fc" id="L687"> PDStream dest = new PDStream(this, page.getContents(), COSName.FLATE_DECODE);</span> <span class="fc" id="L688"> importedPage.setContents(dest);</span> <span class="fc" id="L689"> addPage(importedPage);</span> <span class="fc" id="L690"> importedPage.setCropBox(new PDRectangle(page.getCropBox().getCOSArray()));</span> <span class="fc" id="L691"> importedPage.setMediaBox(new PDRectangle(page.getMediaBox().getCOSArray()));</span> <span class="fc" id="L692"> importedPage.setRotation(page.getRotation());</span> <span class="pc bpc" id="L693" title="2 of 4 branches missed."> if (page.getResources() != null && !page.getCOSObject().containsKey(COSName.RESOURCES))</span> { <span class="nc" id="L695"> LOG.warn("inherited resources of source document are not imported to destination page");</span> <span class="nc" id="L696"> LOG.warn("call importedPage.setResources(page.getResources()) to do this");</span> } <span class="fc" id="L698"> return importedPage;</span> } /** * This will get the low level document. * * @return The document that this layer sits on top of. */ public COSDocument getDocument() { <span class="fc" id="L708"> return document;</span> } /** * This will get the document info dictionary. If it doesn't exist, an empty document info * dictionary is created in the document trailer. * <p> * In PDF 2.0 this is deprecated except for two entries, /CreationDate and /ModDate. For any other * document level metadata, a metadata stream should be used instead, see * {@link PDDocumentCatalog#getMetadata()}. * * @return The documents /Info dictionary, never null. */ public PDDocumentInformation getDocumentInformation() { <span class="fc bfc" id="L723" title="All 2 branches covered."> if (documentInformation == null)</span> { <span class="fc" id="L725"> COSDictionary trailer = document.getTrailer();</span> <span class="fc" id="L726"> COSDictionary infoDic = trailer.getCOSDictionary(COSName.INFO);</span> <span class="fc bfc" id="L727" title="All 2 branches covered."> if (infoDic == null)</span> { <span class="fc" id="L729"> infoDic = new COSDictionary();</span> <span class="fc" id="L730"> trailer.setItem(COSName.INFO, infoDic);</span> } <span class="fc" id="L732"> documentInformation = new PDDocumentInformation(infoDic);</span> } <span class="fc" id="L734"> return documentInformation;</span> } /** * This will set the document information for this document. * <p> * In PDF 2.0 this is deprecated except for two entries, /CreationDate and /ModDate. For any other * document level metadata, a metadata stream should be used instead, see * {@link PDDocumentCatalog#setMetadata(org.apache.pdfbox.pdmodel.common.PDMetadata) PDDocumentCatalog#setMetadata(PDMetadata)}. * * @param info The updated document information. */ public void setDocumentInformation(PDDocumentInformation info) { <span class="fc" id="L748"> documentInformation = info;</span> <span class="fc" id="L749"> document.getTrailer().setItem(COSName.INFO, info.getCOSObject());</span> <span class="fc" id="L750"> }</span> /** * This will get the document CATALOG. This is guaranteed to not return null. * * @return The documents /Root dictionary */ public PDDocumentCatalog getDocumentCatalog() { <span class="fc bfc" id="L759" title="All 2 branches covered."> if (documentCatalog == null)</span> { <span class="fc" id="L761"> COSDictionary trailer = document.getTrailer();</span> <span class="fc" id="L762"> COSDictionary dictionary = trailer.getCOSDictionary(COSName.ROOT);</span> <span class="pc bpc" id="L763" title="1 of 2 branches missed."> if (dictionary != null)</span> { <span class="fc" id="L765"> documentCatalog = new PDDocumentCatalog(this, dictionary);</span> } else { <span class="nc" id="L769"> documentCatalog = new PDDocumentCatalog(this);</span> } } <span class="fc" id="L772"> return documentCatalog;</span> } /** * This will tell if this document is encrypted or not. * * @return true If this document is encrypted. */ public boolean isEncrypted() { <span class="fc" id="L782"> return document.isEncrypted();</span> } /** * This will get the encryption dictionary for this document. This will still return the parameters if the document * was decrypted. As the encryption architecture in PDF documents is pluggable this returns an abstract class, * but the only supported subclass at this time is a * PDStandardEncryption object. * * @return The encryption dictionary(most likely a PDStandardEncryption object) */ public PDEncryption getEncryption() { <span class="pc bpc" id="L795" title="1 of 4 branches missed."> if (encryption == null && isEncrypted())</span> { <span class="nc" id="L797"> encryption = new PDEncryption(document.getEncryptionDictionary());</span> } <span class="fc" id="L799"> return encryption;</span> } /** * This will set the encryption dictionary for this document. * * @param encryption The encryption dictionary(most likely a PDStandardEncryption object) */ public void setEncryptionDictionary(PDEncryption encryption) { <span class="fc" id="L809"> this.encryption = encryption;</span> <span class="fc" id="L810"> }</span> /** * This will return the last signature from the field tree. Note that this may not be the * last in time when empty signature fields are created first but signed after other fields. * * @return the last signature as <code>PDSignatureField</code>. */ public PDSignature getLastSignatureDictionary() { <span class="nc" id="L820"> List<PDSignature> signatureDictionaries = getSignatureDictionaries();</span> <span class="nc" id="L821"> int size = signatureDictionaries.size();</span> <span class="nc bnc" id="L822" title="All 2 branches missed."> if (size > 0)</span> { <span class="nc" id="L824"> return signatureDictionaries.get(size - 1);</span> } <span class="nc" id="L826"> return null;</span> } /** * Retrieve all signature fields from the document. * * @return a <code>List</code> of <code>PDSignatureField</code>s */ public List<PDSignatureField> getSignatureFields() { <span class="fc" id="L836"> List<PDSignatureField> fields = new ArrayList<>();</span> <span class="fc" id="L837"> PDAcroForm acroForm = getDocumentCatalog().getAcroForm(null);</span> <span class="pc bpc" id="L838" title="1 of 2 branches missed."> if (acroForm != null)</span> { <span class="fc bfc" id="L840" title="All 2 branches covered."> for (PDField field : acroForm.getFieldTree())</span> { <span class="pc bpc" id="L842" title="1 of 2 branches missed."> if (field instanceof PDSignatureField)</span> { <span class="nc" id="L844"> fields.add((PDSignatureField)field);</span> } <span class="fc" id="L846"> }</span> } <span class="fc" id="L848"> return fields;</span> } /** * Retrieve all signature dictionaries from the document. * * @return a <code>List</code> of <code>PDSignatureField</code>s */ public List<PDSignature> getSignatureDictionaries() { <span class="fc" id="L858"> List<PDSignature> signatures = new ArrayList<>();</span> <span class="pc bpc" id="L859" title="1 of 2 branches missed."> for (PDSignatureField field : getSignatureFields())</span> { <span class="nc" id="L861"> COSDictionary value = field.getCOSObject().getCOSDictionary(COSName.V);</span> <span class="nc bnc" id="L862" title="All 2 branches missed."> if (value != null)</span> { <span class="nc" id="L864"> signatures.add(new PDSignature(value));</span> } <span class="nc" id="L866"> }</span> <span class="fc" id="L867"> return signatures;</span> } /** * For internal PDFBox use when creating PDF documents: register a TrueTypeFont to make sure it * is closed when the PDDocument is closed to avoid memory leaks. Users don't have to call this * method, it is done by the appropriate PDFont classes. * * @param ttf */ public void registerTrueTypeFontForClosing(TrueTypeFont ttf) { <span class="fc" id="L879"> fontsToClose.add(ttf);</span> <span class="fc" id="L880"> }</span> /** * Returns the list of fonts which will be subset before the document is saved. */ Set<PDFont> getFontsToSubset() { <span class="fc" id="L887"> return fontsToSubset;</span> } /** * Save the document to a file using default compression. * <p> * Don't use the input file as target as this will produce a corrupted file. * <p> * If encryption has been activated (with {@link #protect(org.apache.pdfbox.pdmodel.encryption.ProtectionPolicy) * protect(ProtectionPolicy)}), do not use the document after saving because the contents are now encrypted. * * @param fileName The file to save as. * * @throws IOException if the output could not be written */ public void save(String fileName) throws IOException { <span class="fc" id="L904"> save(new File(fileName));</span> <span class="fc" id="L905"> }</span> /** * Save the document to a file using default compression. * <p> * Don't use the input file as target as this will produce a corrupted file. * <p> * If encryption has been activated (with {@link #protect(org.apache.pdfbox.pdmodel.encryption.ProtectionPolicy) * protect(ProtectionPolicy)}), do not use the document after saving because the contents are now encrypted. * * @param file The file to save as. * * @throws IOException if the output could not be written */ public void save(File file) throws IOException { <span class="fc" id="L921"> save(file, CompressParameters.DEFAULT_COMPRESSION);</span> <span class="fc" id="L922"> }</span> /** * This will save the document to an output stream. * <p> * Don't use the input file as target as this will produce a corrupted file. * <p> * If encryption has been activated (with {@link #protect(org.apache.pdfbox.pdmodel.encryption.ProtectionPolicy) * protect(ProtectionPolicy)}), do not use the document after saving because the contents are now encrypted. * * @param output The stream to write to. It is recommended to wrap it in a {@link java.io.BufferedOutputStream}, * unless it is already buffered. * * @throws IOException if the output could not be written */ public void save(OutputStream output) throws IOException { <span class="fc" id="L939"> save(output, CompressParameters.DEFAULT_COMPRESSION);</span> <span class="fc" id="L940"> }</span> /** * Save the document using the given compression. * <p> * Don't use the input file as target as this will produce a corrupted file. * <p> * If encryption has been activated (with {@link #protect(org.apache.pdfbox.pdmodel.encryption.ProtectionPolicy) * protect(ProtectionPolicy)}), do not use the document after saving because the contents are now encrypted. * * @param file The file to save as. * @param compressParameters The parameters for the document's compression. * @throws IOException if the output could not be written */ public void save(File file, CompressParameters compressParameters) throws IOException { <span class="fc bfc" id="L956" title="All 2 branches covered."> if (file.exists())</span> { <span class="fc" id="L958"> LOG.warn(</span> <span class="fc" id="L959"> "You are overwriting the existing file " + file.getName()</span> + ", this will produce a corrupted file if you're also reading from it"); } <span class="fc" id="L962"> try (BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(</span> new FileOutputStream(file))) { <span class="fc" id="L965"> save(bufferedOutputStream, compressParameters);</span> } <span class="fc" id="L967"> }</span> /** * Save the document to a file using the given compression. * <p> * Don't use the input file as target as this will produce a corrupted file. * <p> * If encryption has been activated (with {@link #protect(org.apache.pdfbox.pdmodel.encryption.ProtectionPolicy) * protect(ProtectionPolicy)}), do not use the document after saving because the contents are now encrypted. * * @param fileName The file to save as. * @param compressParameters The parameters for the document's compression. * * @throws IOException if the output could not be written */ public void save(String fileName, CompressParameters compressParameters) throws IOException { <span class="fc" id="L984"> save(new File(fileName), compressParameters);</span> <span class="fc" id="L985"> }</span> /** * Save the document using the given compression. * <p> * Don't use the input file as target as this will produce a corrupted file. * <p> * If encryption has been activated (with {@link #protect(org.apache.pdfbox.pdmodel.encryption.ProtectionPolicy) * protect(ProtectionPolicy)}), do not use the document after saving because the contents are now encrypted. * * @param output The stream to write to. It is recommended to wrap it in a {@link java.io.BufferedOutputStream}, * unless it is already buffered. * @param compressParameters The parameters for the document's compression. * @throws IOException if the output could not be written */ public void save(OutputStream output, CompressParameters compressParameters) throws IOException { <span class="pc bpc" id="L1003" title="1 of 2 branches missed."> if (document.isClosed())</span> { <span class="nc" id="L1005"> throw new IOException("Cannot save a document which has been closed");</span> } // object stream compression requires a cross reference stream. <span class="pc bpc" id="L1009" title="1 of 4 branches missed."> document.setIsXRefStream(compressParameters != null //</span> && CompressParameters.NO_COMPRESSION != compressParameters); // subset designated fonts <span class="fc bfc" id="L1012" title="All 2 branches covered."> for (PDFont font : fontsToSubset)</span> { <span class="fc" id="L1014"> font.subset();</span> <span class="fc" id="L1015"> }</span> <span class="fc" id="L1016"> fontsToSubset.clear();</span> // save PDF <span class="fc" id="L1019"> COSWriter writer = new COSWriter(output, compressParameters);</span> <span class="fc" id="L1020"> writer.write(this);</span> <span class="fc" id="L1021"> }</span> /** * Save the PDF as an incremental update. This is only possible if the PDF was loaded from a file or a stream, not * if the document was created in PDFBox itself. There must be a path of objects that have * {@link COSUpdateInfo#isNeedToBeUpdated()} set, starting from the document catalog. For signatures this is taken * care by PDFBox itself. * <p> * Other usages of this method are for experienced users only. You will usually never need it. It is useful only if * you are required to keep the current revision and append the changes. A typical use case is changing a signed * file without invalidating the signature. * <p> * Don't use the input file as target as this will produce a corrupted file. * * @param output stream to write to. It will be closed when done. It <i><b>must never</b></i> point to the source * file or that one will be harmed! * @throws IOException if the output could not be written * @throws IllegalStateException if the document was not loaded from a file or a stream. */ public void saveIncremental(OutputStream output) throws IOException { <span class="pc bpc" id="L1042" title="1 of 2 branches missed."> if (pdfSource == null)</span> { <span class="nc" id="L1044"> throw new IllegalStateException("document was not loaded from a file or a stream");</span> } <span class="fc" id="L1046"> COSWriter writer = new COSWriter(output, pdfSource);</span> <span class="fc" id="L1047"> writer.write(this, signInterface);</span> <span class="fc" id="L1048"> }</span> /** * Save the PDF as an incremental update. This is only possible if the PDF was loaded from a file or a stream, not * if the document was created in PDFBox itself. This allows to include objects even if there is no path of objects * that have {@link COSUpdateInfo#isNeedToBeUpdated()} set so the incremental update gets smaller. Only dictionaries * are supported; if you need to update other objects classes, then add their parent dictionary. * <p> * This method is for experienced users only. You will usually never need it. It is useful only if you are required * to keep the current revision and append the changes. A typical use case is changing a signed file without * invalidating the signature. To know which objects are getting changed, you need to have some understanding of the * PDF specification, and look at the saved file with an editor to verify that you are updating the correct objects. * You should also inspect the page and document structures of the file with PDFDebugger. * <p> * Don't use the input file as target as this will produce a corrupted file. * * @param output stream to write to. It will be closed when done. It <i><b>must never</b></i> point to the source * file or that one will be harmed! * @param objectsToWrite objects that <b>must</b> be part of the incremental saving. * @throws IOException if the output could not be written * @throws IllegalStateException if the document was not loaded from a file or a stream. */ public void saveIncremental(OutputStream output, Set<COSDictionary> objectsToWrite) throws IOException { <span class="nc bnc" id="L1072" title="All 2 branches missed."> if (pdfSource == null)</span> { <span class="nc" id="L1074"> throw new IllegalStateException("document was not loaded from a file or a stream");</span> } <span class="nc" id="L1076"> COSWriter writer = new COSWriter(output, pdfSource, objectsToWrite);</span> <span class="nc" id="L1077"> writer.write(this, signInterface);</span> <span class="nc" id="L1078"> }</span> /** * Save PDF incrementally without closing for external signature creation scenario. The general sequence is: * * <pre> * PDDocument pdDocument = ...; * OutputStream outputStream = ...; * SignatureOptions signatureOptions = ...; // options to specify fine tuned signature options or null for defaults * PDSignature pdSignature = ...; * * // add signature parameters to be used when creating signature dictionary * pdDocument.addSignature(pdSignature, signatureOptions); * // prepare PDF for signing and obtain helper class to be used * ExternalSigningSupport externalSigningSupport = pdDocument.saveIncrementalForExternalSigning(outputStream); * // get data to be signed * InputStream dataToBeSigned = externalSigningSupport.getContent(); * // invoke signature service * byte[] signature = sign(dataToBeSigned); * // set resulted CMS signature * externalSigningSupport.setSignature(signature); * * // last step is to close the document * pdDocument.close(); * </pre> * <p> * Note that after calling this method, only {@code close()} method may invoked for {@code PDDocument} instance and * only AFTER {@link ExternalSigningSupport} instance is used. * </p> * <p> * Don't use the input file as target as this will produce a corrupted file. * * @param output stream to write the final PDF. It will be closed when the document is closed. It <i><b>must * never</b></i> point to the source file or that one will be harmed! * @return instance to be used for external signing and setting CMS signature * @throws IOException if the output could not be written * @throws IllegalStateException if the document was not loaded from a file or a stream or signature options were * not set. */ public ExternalSigningSupport saveIncrementalForExternalSigning(OutputStream output) throws IOException { <span class="nc bnc" id="L1119" title="All 2 branches missed."> if (pdfSource == null)</span> { <span class="nc" id="L1121"> throw new IllegalStateException("document was not loaded from a file or a stream");</span> } // PDFBOX-3978: getLastSignatureDictionary() not helpful if signing into a template // that is not the last signature. So give higher priority to signature with update flag. <span class="nc" id="L1125"> PDSignature foundSignature = null;</span> <span class="nc bnc" id="L1126" title="All 2 branches missed."> for (PDSignature sig : getSignatureDictionaries())</span> { <span class="nc" id="L1128"> foundSignature = sig;</span> <span class="nc bnc" id="L1129" title="All 2 branches missed."> if (sig.getCOSObject().isNeedToBeUpdated())</span> { <span class="nc" id="L1131"> break;</span> } <span class="nc" id="L1133"> }</span> <span class="nc bnc" id="L1135" title="All 2 branches missed."> if (foundSignature == null)</span> { <span class="nc" id="L1137"> throw new IllegalStateException("document does not contain signature fields");</span> } <span class="nc" id="L1140"> int[] byteRange = foundSignature.getByteRange();</span> <span class="nc bnc" id="L1141" title="All 2 branches missed."> if (!Arrays.equals(byteRange, RESERVE_BYTE_RANGE))</span> { <span class="nc" id="L1143"> throw new IllegalStateException("signature reserve byte range has been changed "</span> + "after addSignature(), please set the byte range that existed after addSignature()"); } <span class="nc" id="L1146"> COSWriter writer = new COSWriter(output, pdfSource);</span> <span class="nc" id="L1147"> writer.write(this);</span> <span class="nc" id="L1148"> signingSupport = new SigningSupport(writer);</span> <span class="nc" id="L1149"> return signingSupport;</span> } /** * Returns the page at the given 0-based index. * <p> * This method is too slow to get all the pages from a large PDF document * (1000 pages or more). For such documents, use the iterator of * {@link PDDocument#getPages()} instead. * * @param pageIndex the 0-based page index * @return the page at the given index. */ public PDPage getPage(int pageIndex) // todo: REPLACE most calls to this method with BELOW method { <span class="fc" id="L1164"> return getDocumentCatalog().getPages().get(pageIndex);</span> } /** * Returns the page tree. * * @return the page tree */ public PDPageTree getPages() { <span class="fc" id="L1174"> return getDocumentCatalog().getPages();</span> } /** * This will return the total page count of the PDF document. * * @return The total number of pages in the PDF document. */ public int getNumberOfPages() { <span class="fc" id="L1184"> return getDocumentCatalog().getPages().getCount();</span> } /** * This will close the underlying COSDocument object. * * @throws IOException If there is an error releasing resources. */ @Override public void close() throws IOException { <span class="fc bfc" id="L1195" title="All 2 branches covered."> if (!document.isClosed())</span> { // Make sure that: // - first Exception is kept // - all IO resources are closed // - there's a way to see which errors occurred <span class="fc" id="L1202"> IOException firstException = null;</span> // close resources and COSWriter <span class="pc bpc" id="L1205" title="1 of 2 branches missed."> if (signingSupport != null)</span> { <span class="nc" id="L1207"> firstException = IOUtils.closeAndLogException(signingSupport, LOG, "SigningSupport", firstException);</span> } // close all intermediate I/O streams <span class="fc" id="L1211"> firstException = IOUtils.closeAndLogException(document, LOG, "COSDocument", firstException);</span> // close the source PDF stream, if we read from one <span class="fc bfc" id="L1214" title="All 2 branches covered."> if (pdfSource != null)</span> { <span class="fc" id="L1216"> firstException = IOUtils.closeAndLogException(pdfSource, LOG, "RandomAccessRead pdfSource", firstException);</span> } // close fonts <span class="fc bfc" id="L1220" title="All 2 branches covered."> for (TrueTypeFont ttf : fontsToClose)</span> { <span class="fc" id="L1222"> firstException = IOUtils.closeAndLogException(ttf, LOG, "TrueTypeFont", firstException);</span> <span class="fc" id="L1223"> }</span> // rethrow first exception to keep method contract <span class="pc bpc" id="L1226" title="1 of 2 branches missed."> if (firstException != null)</span> { <span class="nc" id="L1228"> throw firstException;</span> } } <span class="fc" id="L1231"> }</span> /** * Protects the document with a protection policy. The document content will be really * encrypted when it will be saved. This method only marks the document for encryption. It also * calls {@link #setAllSecurityToBeRemoved(boolean)} with a false argument if it was set to true * previously and logs a warning. * <p> * Do not use the document after saving, because the structures are encrypted. * * @see org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy * @see org.apache.pdfbox.pdmodel.encryption.PublicKeyProtectionPolicy * * @param policy The protection policy. * @throws IOException if there isn't any suitable security handler. */ public void protect(ProtectionPolicy policy) throws IOException { <span class="fc bfc" id="L1249" title="All 2 branches covered."> if (isAllSecurityToBeRemoved())</span> { <span class="fc" id="L1251"> LOG.warn("do not call setAllSecurityToBeRemoved(true) before calling protect(), "</span> + "as protect() implies setAllSecurityToBeRemoved(false)"); <span class="fc" id="L1253"> setAllSecurityToBeRemoved(false);</span> } <span class="pc bpc" id="L1256" title="1 of 2 branches missed."> if (!isEncrypted())</span> { <span class="fc" id="L1258"> encryption = new PDEncryption();</span> } <span class="fc" id="L1261"> SecurityHandler<ProtectionPolicy> securityHandler =</span> <span class="fc" id="L1262"> SecurityHandlerFactory.INSTANCE.newSecurityHandlerForPolicy(policy);</span> <span class="pc bpc" id="L1263" title="1 of 2 branches missed."> if (securityHandler == null)</span> { <span class="nc" id="L1265"> throw new IOException("No security handler for policy " + policy);</span> } <span class="fc" id="L1268"> getEncryption().setSecurityHandler(securityHandler);</span> <span class="fc" id="L1269"> }</span> /** * Returns the access permissions granted when the document was decrypted. If the document was not decrypted this * method returns the access permission for a document owner (ie can do everything). The returned object is in read * only mode so that permissions cannot be changed. Methods providing access to content should rely on this object * to verify if the current user is allowed to proceed. * * @return the access permissions for the current user on the document. */ public AccessPermission getCurrentAccessPermission() { <span class="pc bpc" id="L1281" title="1 of 2 branches missed."> if (accessPermission == null)</span> { <span class="nc" id="L1283"> accessPermission = AccessPermission.getOwnerAccessPermission();</span> } <span class="fc" id="L1285"> return accessPermission;</span> } /** * Indicates if all security is removed or not when writing the pdf. * * @return returns true if all security shall be removed otherwise false */ public boolean isAllSecurityToBeRemoved() { <span class="fc" id="L1295"> return allSecurityToBeRemoved;</span> } /** * Activates/Deactivates the removal of all security when writing the pdf. * * @param removeAllSecurity remove all security if set to true */ public void setAllSecurityToBeRemoved(boolean removeAllSecurity) { <span class="fc" id="L1305"> allSecurityToBeRemoved = removeAllSecurity;</span> <span class="fc" id="L1306"> }</span> /** * Provides the document ID. * * @return the document ID */ public Long getDocumentId() { <span class="fc" id="L1315"> return documentId;</span> } /** * Sets the document ID to the given value. * * @param docId the new document ID */ public void setDocumentId(Long docId) { <span class="nc" id="L1325"> documentId = docId;</span> <span class="nc" id="L1326"> }</span> /** * Returns the PDF specification version this document conforms to. * * @return the PDF version (e.g. 1.4f) */ public float getVersion() { <span class="fc" id="L1335"> float headerVersionFloat = getDocument().getVersion();</span> // there may be a second version information in the document catalog starting with 1.4 <span class="fc bfc" id="L1337" title="All 2 branches covered."> if (headerVersionFloat >= 1.4f)</span> { <span class="fc" id="L1339"> String catalogVersion = getDocumentCatalog().getVersion();</span> <span class="fc" id="L1340"> float catalogVersionFloat = -1;</span> <span class="fc bfc" id="L1341" title="All 2 branches covered."> if (catalogVersion != null)</span> { try { <span class="fc" id="L1345"> catalogVersionFloat = Float.parseFloat(catalogVersion);</span> } <span class="nc" id="L1347"> catch(NumberFormatException exception)</span> { <span class="nc" id="L1349"> LOG.error("Can't extract the version number of the document catalog.", exception);</span> <span class="fc" id="L1350"> }</span> } // the most recent version is the correct one <span class="fc" id="L1353"> return Math.max(catalogVersionFloat, headerVersionFloat);</span> } else { <span class="fc" id="L1357"> return headerVersionFloat;</span> } } /** * Sets the PDF specification version for this document. * * @param newVersion the new PDF version (e.g. 1.4f) * */ public void setVersion(float newVersion) { <span class="fc" id="L1369"> float currentVersion = getVersion();</span> // nothing to do? <span class="fc bfc" id="L1371" title="All 2 branches covered."> if (Float.compare(newVersion,currentVersion) == 0)</span> { <span class="fc" id="L1373"> return;</span> } // the version can't be downgraded <span class="fc bfc" id="L1376" title="All 2 branches covered."> if (newVersion < currentVersion)</span> { <span class="fc" id="L1378"> LOG.error("It's not allowed to downgrade the version of a pdf.");</span> <span class="fc" id="L1379"> return;</span> } // update the catalog version if the document version is >= 1.4 <span class="fc bfc" id="L1382" title="All 2 branches covered."> if (getDocument().getVersion() >= 1.4f)</span> { <span class="fc" id="L1384"> getDocumentCatalog().setVersion(Float.toString(newVersion));</span> } else { // versions < 1.4f have a version header only <span class="fc" id="L1389"> getDocument().setVersion(newVersion);</span> } <span class="fc" id="L1391"> }</span> /** * Returns the resource cache associated with this document, or null if there is none. */ public ResourceCache getResourceCache() { <span class="fc" id="L1398"> return resourceCache;</span> } /** * Sets the resource cache associated with this document. * * @param resourceCache A resource cache, or null. */ public void setResourceCache(ResourceCache resourceCache) { <span class="nc" id="L1408"> this.resourceCache = resourceCache;</span> <span class="nc" id="L1409"> }</span> } </pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.8.202204050719</span></div></body></html>