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);
}
}