public void addSignature()

in pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java [317:474]


    public void addSignature(PDSignature sigObject, SignatureInterface signatureInterface,
                             SignatureOptions options) throws IOException
    {
        if (signatureAdded)
        {
            throw new IllegalStateException("Only one signature may be added in a document");
        }
        signatureAdded = true;

        // 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.
        int preferredSignatureSize = options.getPreferredSignatureSize();
        if (preferredSignatureSize > 0)
        {
            sigObject.setContents(new byte[preferredSignatureSize]);
        }
        else
        {
            sigObject.setContents(new byte[SignatureOptions.DEFAULT_SIGNATURE_SIZE]);
        }

        // Reserve ByteRange, will be overwritten in COSWriter
        sigObject.setByteRange(RESERVE_BYTE_RANGE);

        signInterface = signatureInterface;

        // Create SignatureForm for signature and append it to the document

        // Get the first valid page
        PDPageTree pageTree = getPages();
        int pageCount = pageTree.getCount();
        if (pageCount == 0)
        {
            throw new IllegalStateException("Cannot sign an empty document");
        }

        // Get the AcroForm from the Root-Dictionary and append the annotation
        PDDocumentCatalog catalog = getDocumentCatalog();
        PDAcroForm acroForm = catalog.getAcroForm(null);
        catalog.getCOSObject().setNeedToBeUpdated(true);

        if (acroForm == null)
        {
            acroForm = new PDAcroForm(this);
            catalog.setAcroForm(acroForm);
        }
        else
        {
            acroForm.getCOSObject().setNeedToBeUpdated(true);
        }

        PDSignatureField signatureField = null;
        COSArray fieldArray = acroForm.getCOSObject().getCOSArray(COSName.FIELDS);
        if (fieldArray != null)
        {
            fieldArray.setNeedToBeUpdated(true);
            signatureField = findSignatureField(acroForm.getFieldIterator(), sigObject);
        }
        else
        {
            acroForm.getCOSObject().setItem(COSName.FIELDS, new COSArray());
        }
        PDAnnotationWidget firstWidget;
        PDPage page;
        if (signatureField == null)
        {
            signatureField = new PDSignatureField(acroForm);
            // append the signature object
            signatureField.setValue(sigObject);
            firstWidget = signatureField.getWidgets().get(0);
            int startIndex = Math.min(Math.max(options.getPage(), 0), pageCount - 1);
            page = pageTree.get(startIndex);
            // backward linking
            firstWidget.setPage(page);
        }
        else
        {
            firstWidget = signatureField.getWidgets().get(0);
            sigObject.getCOSObject().setNeedToBeUpdated(true);
            page = null;
        }

        // 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
        firstWidget.setPrinted(true);
        // 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
        List<PDField> acroFormFields = acroForm.getFields();
        acroForm.getCOSObject().setDirect(true);
        acroForm.setSignaturesExist(true);
        acroForm.setAppendOnly(true);

        boolean checkFields = checkSignatureField(acroForm.getFieldIterator(), signatureField);
        if (checkFields)
        {
            signatureField.getCOSObject().setNeedToBeUpdated(true);
        }
        else
        {
            acroFormFields.add(signatureField);
        }

        // Get the object from the visual signature
        COSDocument visualSignature = options.getVisualSignature();

        // Distinction of case for visual and non-visual signature
        if (visualSignature == null)
        {
            prepareNonVisibleSignature(firstWidget);
        }
        else
        {
            prepareVisibleSignature(firstWidget, acroForm, visualSignature);
        }

        if (page != null)
        {
            // Create Annotation / Field for signature
            List<PDAnnotation> annotations = page.getAnnotations();

            // 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)
            if (!(checkFields &&
                  annotations instanceof COSArrayList &&
                  acroFormFields instanceof COSArrayList &&
                  ((COSArrayList) annotations).toList().
                          equals(((COSArrayList) acroFormFields).toList())))
            {
                // use check to prevent the annotation widget from appearing twice
                if (checkSignatureAnnotation(annotations, firstWidget))
                {
                    firstWidget.getCOSObject().setNeedToBeUpdated(true);
                }
                else
                {
                    annotations.add(firstWidget);
                }   
            }

            // 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.
            page.setAnnotations(annotations);

            page.getCOSObject().setNeedToBeUpdated(true);
        }
    }