
Efficiency and convenience are key when it comes to handling documents and forms. Traditional paper-based forms can be cumbersome and prone to errors, making fillable PDFs a popular alternative. Fillable PDFs allow users to easily enter information, select items from drop-down boxes, and check fields, providing a seamless and interactive experience. In this blog, we will explore the concept of fillable PDFs, their benefits, and how to implement them in a web application using Angular and Java.
We will delve into the technologies and steps involved in creating fillable PDFs, from setting up the front-end with Angular and the ng2-pdf-viewer library to handling the back-end with Java and Apache PDFBox. Whether you're looking to reduce paper consumption, improve legibility, or streamline data collection, this guide will provide you with the knowledge and tools needed to integrate fillable PDFs into your applications effectively. Let's get started!
Fillable PDF forms work the same as any other online form. You may enter information, select items from drop-down boxes, and also check fields as needed. You can complete the form online or save a copy of the form on your computer and use any modern PDF Reader to fill in the form.
I have worked on a project where clients will digitally sign the documents which are shared with them. They were interested in additionally updating some fields in the document digitally.
This is a two-step process as outlined below.
I am using REST API to send form details to the backend.
Prerequisite: we use the angular open-source library ng2-pdf-viewer component written by Vadym Yatsyuk to let the user view the PDF and make updates to it. This is a PDF Viewer component implemented using pdf.js.
Ng2-pdf-viewer has a predefined event that notifies when the PDF has finished loading and rendering successfully. It also provides a PDFDocumentProxy object containing PDF annotations and form field details.
PDF annotations have details like field type, default value, etc., We have three types of fields: Tx, Ch, and Btn.
Note: I will elaborate on how to differentiate between the radio button and the checkbox.
Using npm, add ng2-pdf-viewer to your existing angular application.
npm install ng2-pdf-viewer
For more details on dependencies, refer to the dependency chart here.
Use the above library to preview the PDF.
<pdf-viewer [src]="<link to pdf>"
[render-text]="true"
[original-size]="false"
(after-load-complete)="loadCompleted($event)"
></pdf-viewer>
There are a lot of options available in the library to view a PDF in a Web Application. Please refer to library documentation for more details.
public loadCompleted(pdf: PDFDocumentProxy): void {
this._currentFormValues = [];
for (let i = 1; i <= pdf.numPages; i++) {
// track the current page
pdf.getPage(i).then(p => {
return p.getAnnotations();
}).then(ann => {
const annotations = (<any>ann) as PDFAnnotationData[];
annotations
.filter(a => a.subtype === 'Widget')
.forEach(a => {
this._createInput(a);
});
});
}
}
The above code will get all annotations and filter out only form fields.
private _createInput(annotation: PDFAnnotationData): void {
const input = new PDFFillableField();
input.name = annotation.fieldName;
input.id = annotation.id;
switch (annotation.fieldType) {
case "Tx":
input.type = PdfFieldTypeEnum.TEXT;
input.value = annotation.buttonValue || '';
break;
case "Ch":
input.type = PdfFieldTypeEnum.DROPDOWN;
input.value = annotation.buttonValue || '';
break;
case "Btn":
if (annotation.checkBox) {
input.type = PdfFieldTypeEnum.CHECKBOX;
input.value = true;
} else {
input.type = PdfFieldTypeEnum.RADIO;
input.value = annotation.buttonValue;
}
break;
default:
input.type = PdfFieldTypeEnum.TEXT;
input.value = annotation.buttonValue || '';
}
this._formFields.push(input);
}
The above code will create input and set a default value from Object and set it empty otherwise. Also, do note that there is a property called checkBox in the annotation object. If this property is set to true, then it is a checkbox or a radio button otherwise. In the above code, you can see the if-condition to check this and assign default values based on that.
A checkbox will have a default value of true. We will handle this later and change it to false if the user didn’t select the checkbox.
On the other hand, the radio button’s default value is an integer number which is the order of the button starting from 0. We will be sending selected radio buttons’ order numbers to the backend.
PDF preview will render an annotation layer that has HTML form elements. Use javascript to get values from all form fields.
private _getFieldValues(): void {
this._formFields.map(input => {
const ele = <HTMLInputElement>document.getElementById(input.id);
if (TypeValueUtil.isDefined(ele)) {
if (input.type === PdfFieldTypeEnum.CHECKBOX) {
this.formValues[input.name] = ele.checked;
} else if (input.type === PdfFieldTypeEnum.RADIO) {
if (!ele.checked) {
} else {
this.formValues[input.name] = input.value;
}
} else {
this.formValues[input.name] = ele.value;
}
}
});
}
The formvalues object will have all details about the field and user-entered values. Use REST API to pass this payload to the Backend.
// FormValues field will have below details
class PDFFillableField {
name: string;
value : any;
type: string;
id: string;
}
On the back end, we will be using apache pdfbox in Java to set values for the PDF form.
The Apache PDFBox® library is an open-source Java tool for working with PDF documents. This project allows the creation of new PDF documents, manipulation of existing documents, and the ability to extract content from documents. Apache PDFBox also includes several command-line utilities. Apache PDFBox is published under the Apache License v2.0.
The Below code will get values from UI and append them to the actual PDF. Load needed PDF document using PDPageContentStream into PDDocument. I resorted to sending the document in bytes.
PDDocument overlayDoc = new PDDocument();
PDPage page = new PDPage();
overlayDoc.addPage(page);
Overlay overlayObj = new Overlay();
PDFont font = PDType1Font.TIMES_ITALIC;
PDPageContentStream contentStream = new PDPageContentStream(overlayDoc, page);
PDDocument originalDoc = PDDocument.load(inputDocumentContents);
PDDocumentCatalog docCatalog = originalDoc.getDocumentCatalog();
try {
// Fillable PDF
if (null != docCatalog.getAcroForm()) {
fillPDFFields(docCatalog.getAcroForm(), values);
}
// Make PDF form fields readOnly.
originalDoc.setAllSecurityToBeRemoved(true);
} finally {
overlayObj.close();
overlayDoc.close();
originalDoc.close();
}
You might have noticed that setAllSecurityToBeRemoved is set to true. This will make the PDF Form Fields readyOnly. This is to ensure that the user is not able to edit the same value again when he downloads the same PDF and opens it.
Note: setAllSecurityToBeRemoved is an optional parameter. Set value for it based on your requirement.
private void fillPDFFields(PDAcroForm acroForm,
Map<String, Object> fieldValues) throws IOException {
if (null != fieldValues) {
List<PDField> fields = acroForm.getFields();
fields.forEach((field) -> {
String fieldName = field.getFullyQualifiedName();
try {
String value = fieldValues.get(fieldName) == null ? "" : String.valueOf(fieldValues.get(fieldName));
if (StringUtils.isNotEmpty(value)) {
if ("Tx".equals(field.getFieldType())) {
PDTextField textField = (PDTextField) field;
textField.setValue(value);
} else {
if (field instanceof PDCheckBox) {
PDCheckBox checkBox = (PDCheckBox) field;
if ("true".equalsIgnoreCase(value)) {
checkBox.check();
} else {
// Make unchecked checkbox to false
checkBox.unCheck();
}
} else if (field instanceof PDRadioButton) {
PDRadioButton pdRadioButton = (PDRadioButton) field;
pdRadioButton.setValue(Integer.parseInt(value));
} else if (field instanceof PDComboBox) {
PDComboBox pdComboBox = (PDComboBox) field;
pdComboBox.setValue(value);
}
}
}
} catch (IOException e) {
LOGGER.error("Error while populating value in pdf", e);
}
});
acroForm.flatten();
}
}
The above code will set values for all fields based on their type. Just setting a value in the Field object will write and append value to PDF.
Set the string value in PDTextField Object.
For the checkbox, if the value from the user is true, then mark PDCheckBox as checked using the check() function. If otherwise, mark it as unchecked using the uncheck() function.
For the Radio button, you have to set an integer value, which is the order of the radio button checked starting from 0 in PDRadioButton.
It is as simple as the text field. All you have to do is set the string value in the PDComboBox Object.
Finally, once you have set the values in the PDF form, save the document wherever you need to.
Fillable PDFs offer a powerful solution for modern data collection and user interaction, transforming traditional paper forms into interactive digital experiences. By utilizing technologies like Angular and Java, businesses can create seamless workflows that enable users to easily fill out, submit, and manage forms efficiently. This not only enhances user satisfaction but also promotes a greener, paperless environment.
As organizations increasingly shift towards digital solutions, adopting fillable PDFs can streamline operations and improve data accuracy. Whether for client interactions, internal processes, or regulatory compliance, integrating fillable PDFs into your workflow is a smart investment for the future.

