AVAILABLE NOW: Spring 2025 Release
By Andrey Safonov | 2019 Apr 12
5 min
Tags
form
web
tutorial
This is a guide on how to leverage the Apryse WebViewer and the WebViewer Full API to embed a fast and user-friendly, pure client-side form builder into your web application.
You can watch how easy our form builder is to use in this YouTube demo I put together. Or try it out yourself in the form builder demo.
There’s no questioning PDF’s theoretical advantages when it comes to data capture from forms. PDF documents are vendor- and session-independent, and support a Swiss-Army-knife worth of interactive form elements, on top of digital signatures, encryption, and a host of the features.
But actually filling PDF forms in the real world is often frustrating. Hospitals and so on produce many static scanned or malformed PDFs. And all too often, these force users to retreat to old-fashioned printing, filling and signing by hand — a waste of time and trees.
At Apryse, we wanted to help our customers by making form building from static documents as simple and painless as possible, preferably, in a browser, where the feature could be enjoyed by the maximum number of users (from virtually anywhere). So we went and built a sample to show how we solve the problem using our PDF SDK.
You will first need to download the WebViewer SDK.
Next, unzip and navigate to the right directory from the command line.
When that’s done, run the following command:
npm install
This will start up a local server and open the WebViewer samples. (For more on how to integrate WebViewer into any JavaScript framework, such as React and Angular — check out our in-depth guide).
Navigate to http://localhost:3000
in your favourite browser. The form building sample is located under Forms/form-builder. You can now create your interactive form by either creating a new PDF or opening an existing document.
The first step is to let the user place text annotations anywhere inside a PDF to delineate form fields — which users can easily move and resize accordingly. You are also able to easily customize the appearance of these annotations. You can choose from different colors, borders, font families, and more. (Feel free to read the detailed customization guide.)
Function addFormFieldAnnot
allows you to add a text annotation — with custom parameters: type
, name
, value
and flag
.
Type
determines whether the annotation is a signature field, a text field or a checkbox.Name
is useful later as a reference while iterating over fields — and when either programmatically filling or extracting the Value of that form field.Value
should be entered into the form field right away upon its creation.Flag
(a boolean) determines whether the field will be fillable by a user. const addFormFieldAnnot = (type, name, value, flag) => {
var docViewer = readerControl.docViewer;
var annotManager = docViewer.getAnnotationManager();
var textAnnot = new Annotations.FreeTextAnnotation();
textAnnot.PageNumber = 1;
if (type === 'CHECK') {
textAnnot.Width = 50;
textAnnot.Height = 50;
} else {
textAnnot.Width = 200;
textAnnot.Height = 50;
}
textAnnot.X = 100;
textAnnot.Y = 150;
textAnnot.setPadding(new Annotations.Rect(0, 0, 0, 0));
textAnnot.custom = {
type,
value,
flag
};
// set the type of annot
textAnnot.setContents(`${name}_${type}`);
textAnnot.FontSize = '20px';
textAnnot.Author = annotManager.getCurrentUser();
annotManager.addAnnotation(textAnnot);
annotManager.redrawAnnotation(textAnnot);
};
Next, function convertAnnotToFormField
takes all the text annotations that were placed and sized accordingly and converts them into actual form fields. The next step — converting text annotations created in WebViewer into interactive PDF form fields — uses the Full API.
(Here is the full WebViewer guide if you need to access further advanced functionality.)
The sample is as follows:
const convertAnnotToFormField = async() => {
// initialize
const docViewer = readerControl.docViewer;
const annotManager = docViewer.getAnnotationManager();
const annotationsList = annotManager.getAnnotationsList();
const currentDocument = docViewer.getDocument();
await PDFNet.initialize();
const pdfDoc = await currentDocument.getPDFDoc();
await Promise.all(annotationsList.map(async(annot) => {
let field;
if (typeof annot.custom !== 'undefined') {
console.log(annot.custom);
// create a form field based on the type of annotation
if (annot.custom.type === 'TEXT') {
field = await pdfDoc.fieldCreateFromStrings(annot.getContents(), PDFNet.Field.Type.e_text, annot.custom.value, '');
} else if (annot.custom.type === 'SIGNATURE') {
field = await pdfDoc.fieldCreateFromStrings(annot.getContents(), PDFNet.Field.Type.e_signature, '', '');
} else if (annot.custom.type === 'CHECK') {
field = await pdfDoc.fieldCreateFromStrings(annot.getContents(), PDFNet.Field.Type.e_check, '', '');
} else {
// exit early for other annotations
return;
}
// check if there is a flag
if (annot.custom.flag === true) {
field.setFlag(PDFNet.Field.Flag.e_read_only, true);
}
} else {
// exit early for other annotations
return;
}
// translate coordinates
const annotRect = await annot.getRect();
const setTopLeft = currentDocument.getPDFCoordinates(annot.getPageNumber() - 1, annotRect.x1, annotRect.y1);
const setBottomRight = currentDocument.getPDFCoordinates(annot.getPageNumber() - 1, annotRect.x2, annotRect.y2);
// create an annotation with a form field created
const pageNumber = annot.getPageNumber();
const newAnnot = await PDFNet.WidgetAnnot.create(pdfDoc, (await PDFNet.Rect.init(setTopLeft.x, setTopLeft.y, setBottomRight.x, setBottomRight.y)), field);
// delete original annotation
annotManager.deleteAnnotation(annot, false, true);
// customize styles of the form field
Annotations.WidgetAnnotation.getCustomStyles = function(widget) {
if (widget instanceof Annotations.TextWidgetAnnotation) {
return {
'background-color': '#a5c7ff',
color: 'white',
'font-size': '20px'
};
} else if (widget instanceof Annotations.SignatureWidgetAnnotation) {
return {
border: '1px solid #a5c7ff'
};
}
};
Annotations.WidgetAnnotation.getCustomStyles(newAnnot);
// draw the annotation the viewer
const page = await pdfDoc.getPage(pageNumber);
page.annotPushBack(newAnnot);
await pdfDoc.refreshFieldAppearances();
}));
// import newly created form fields
const fdfDoc = await pdfDoc.fdfExtract(PDFNet.PDFDoc.ExtractFlag.e_both);
const xfdf = await fdfDoc.saveAsXFDFAsString();
annotManager.importAnnotations(xfdf);
// refresh viewer
docViewer.refreshAll();
docViewer.updateView();
docViewer.getDocument().refreshTextData();
};
If you’re interested, this is how the sample works:
<Canvas>
annotation coordinates differ from coordinates stored in PDF; so a simple conversion is required. (To learn more about this conversion, refer to the PDF coordinate guide.)The final steps are to delete the original text annotations, save the document, and import new form fields into WebViewer so the user can see the changes.
We’re ultimately just scratching the surface of what can be done with Apryse SDK’s hundreds of unique features to extend the above sample. For example: integrating a feature that would snap annotations to a grid would make it a lot easier for users to align fields.
If you hit any stuck points, want to share how this solution worked out for you, or are just curious about the other capabilities of our SDKs — don’t hesitate to get in touch.
You can reach out to me here via email.
Tags
form
web
tutorial
Related Products
Share this post
PRODUCTS
Platform Integrations
End User Applications
Popular Content