Blog Post

How to Populate All Signature Form Fields When Creating a Signature Annotation

Veronica Marini
Illustration: 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.

Example of populated 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.

Author
Veronica Marini Web Engineer

Veronica’s passion for puzzles got her into programming. She likes everything frontend, bringing design to life, and measuring herself with coding. She also collects hobbies: from yoga to surfing to playing Brazilian drums.

Share post
Free trial Ready to get started?
Free trial