How to Populate All Signature Form Fields When Creating a Signature Annotation
Last year, we rolled out a powerful new component on our Web PDF SDK: Electronic Signatures. This component allows you to offer your users a variety of input methods to add visual electronic signatures to documents. More specifically, users can add a signature by drawing, picking a local JPG or PNG image, or typing text. You can read more about it in our announcement blog post.
In this blog post, we want to take this a step further and show how to use our PSPDFKit APIs to populate all signature form fields present in a document when a user signs a document with multiple signature form fields.
Overview
Here’s a quick overview of what we need to do to achieve this functionality:
-
Obtain a
List
of the widget annotations associated with signature form fields in the document. This step is needed to know where on the document the duplicated signatures should be placed. -
Duplicate the signatures logic.
If you want to go straight to the code, click here.
Obtaining the List
To obtain a List
of the widget annotations associated with signature form fields in the document, you first need to get all the form fields using our getFormFields()
API. Then, filter them by type:
const formFields = await instance.getFormFields(); const signatureForms = formFields.filter( (formField) => formField instanceof PSPDFKit.FormFields.SignatureFormField, );
Next, gather all the widget annotations associated with the above form fields. To do this, you need to iterate over all the document pages using the instance.totalPageCount
member:
const widgetAnnotations = ( await Promise.all( Array.from({ length: instance.totalPageCount, }).map(async (_, pageIndex) => (await instance.getAnnotations(pageIndex)).filter( (it) => it instanceof PSPDFKit.PSPDFKit.Annotations.WidgetAnnotation, ), ), ) ).flatMap((pageAnnotations) => pageAnnotations.toArray());
Now, filter the above widget annotations found on the page to get the widget annotations associated with the signature form fields:
const signatureFieldsName = signatureForms.map((field) => field.name).toArray(); const signatureWidgets = widgetAnnotations.filter((annotation) => signatureFieldsName.includes(annotation.formFieldName), );
Duplicating the Logic
The next step is to add an event listener for the annotation.create
event. This will let you know when a user creates a signature:
instance.addEventListener('annotations.create', (annotations) => {
console.log(annotations);
});
Electronic Signatures creates signature annotations as instances of PSPDFKit.Annotations.InkAnnotation or PSPDFKit.Annotations.ImageAnnotation.
In the event listener, check if the annotation the user is creating is a signature — whenever an annotation is a signature, its isSignature
flag is set to true
:
instance.addEventListener("annotations.create", (annotations) => { - console.log(annotations) + const signature = annotations.first() && annotations.first().isSignature; + console.log(signature); });
Duplicating an image annotation is easier than duplicating an ink annotation, so let’s start with that. Set the pageIndex
to widget.pageIndex
, and set the bounding boxes to widget.boundingBox
. This will work as long as the widgets on the documents share the same aspect ratio; in other words, if the signature width and height are the same as the widget’s, it can use the same bounding box:
if (signature instanceof PSPDFKit.Annotations.ImageAnnotation) { const duplicatedSignatures = signatureWidgets.map(widget => signature.set("id", null) .set("pageIndex", widget.pageIndex) .set('boundingBox', widget.boundingBox) instance.create(duplicatedSignatures); }
If, for example, the widget’s height is greater, the signature bounding box would be the following:
signature.set('boundingBox', widget.boundingBox.set('top', widget.boundingBox.top + (widget.boundingBox.height - signature.boundingBox.height) / 2));
Now, let’s look at duplicating an InkAnnotation
. One thing to keep in mind is that you not only need to set the bounding box and the page index, but you also need to set the coordinates of the lines. Since an ink annotation could contain multiple segments, you need to change each segment. You can read more about the structure of ink annotations here.
For the lines, create an offsetTranslation
function to “move” them to a different position on the document:
function offsetTranslation(widget, signature) { return new PSPDFKit.Geometry.Point({ x: widget.boundingBox.left - signature.boundingBox.left + (widget.boundingBox.width - signature.boundingBox.width) / 2, y: widget.boundingBox.top - signature.boundingBox.top + (widget.boundingBox.height - signature.boundingBox.height) / 2, }); }
So, duplicating the ink signatures looks like this:
if (signature instanceof PSPDFKit.Annotations.InkAnnotation) { const duplicatedSignatures = signatureWidgets.map((widget) => signature .set('id', null) .set('pageIndex', widget.pageIndex) .set('boundingBox', widget.boundingBox) .set( 'lines', signature.lines.map((line) => { return line.map((point) => point.translate(offsetTranslation(widget, signature)), ); }), ), ); instance.create(duplicatedSignatures); }
The Full Code
const formFields = await instance.getFormFields() // Filter form fields by type. const signatureForms = formFields.filter(formField => ( formField instanceof PSPDFKit.FormFields.SignatureFormField )); // Find all widget annotations on the document. const widgetAnnotations = (await Promise.all( Array.from({ length: instance.totalPageCount }).map(async (_, pageIndex) => (await instance.getAnnotations(pageIndex)).filter(it => it instanceof PSPDFKit.Annotations.WidgetAnnotation) ) )).flatMap(pageAnnotations => pageAnnotations.toArray()) // Find the widget annotations associated with signature forms. const signatureFieldsName = signatureForms.map(field => field.name).toArray() const signatureWidgets = widgetAnnotations.filter( (annotation) => signatureFieldsName.includes(annotation.formFieldName) ); // Add the event listener. instance.addEventListener("annotations.create", (annotations) => { // Check if the annotation is a signature. const signature = annotations.first().isSignature && annotations.first(); if (signature instanceof PSPDFKit.Annotations.InkAnnotation) { const duplicatedSignatures = signatureWidgets.map(widget => signature.set("id", null) .set("pageIndex", widget.pageIndex) .set('boundingBox', widget.boundingBox) .set("lines",signature .lines.map((line) => { return line.map((point) => point.translate(offsetTranslation(widget, signature))); })) ) instance.create(duplicatedSignatures); } if (signature instanceof PSPDFKit.Annotations.ImageAnnotation) { const duplicatedSignatures = signatureWidgets.map(widget => signature.set("id", null) .set("pageIndex", widget.pageIndex) .set('boundingBox', widget.boundingBox)) instance.create(duplicatedSignatures); } }) // "Move" `inkSignature.lines` to a different position on the document. function offsetTranslation(widget, signature){ return new PSPDFKit.Geometry.Point({ x: widget.boundingBox.left - signature.boundingBox.left + (widget.boundingBox.width - signature.boundingBox.width) / 2, y: widget.boundingBox.top - signature.boundingBox.top + (widget.boundingBox.height - signature.boundingBox.height) / 2, })}
Conclusion
In this article, we looked at how to populate all signature form fields present in a document when creating a signature annotation. This is just a practical example of how to use our powerful PDF SDK to fit your needs. For more use cases, you can check out our Catalog example, where we implemented several examples that showcase how customizable our product is, or explore our JavaScript PDF library.