Can pdf-lib be used to programmatically fill out text fields in a template PDF?
UPDATE: Yes! pdf-lib has form creation and filling APIs. See the create form and fill form examples.
pdf-lib now has form creation and filling APIs that should be used instead of the below example(s). See the form filling JSFiddle for a working example. Additional information is available in the README and API docs. The PDFTextField.setText method is of particular relevance to this issue.
Yes, this is possible. No high level API currently exists for it, so the code is a bit ugly. But it works all the same!
Here's an example Node script that fills out a DoD character sheet:
const getAcroFields = (pdfDoc) => {
if (!pdfDoc.catalog.getMaybe('AcroForm')) return [];
const acroForm = pdfDoc.index.lookup(pdfDoc.catalog.get('AcroForm'));
if (!acroForm.getMaybe('Fields')) return [];
const acroFields = pdfDoc.index.lookup(acroForm.get('Fields'));
return acroFields.array.map(pdfDoc.index.lookup);
};
const findAcroFieldByName = (pdfDoc, name) => {
const acroFields = getAcroFields(pdfDoc);
return acroFields.find((acroField) => {
const fieldName = acroField.getMaybe('T');
return !!fieldName && fieldName.string === name;
});
};
const fillAcroTextField = (
pdfDoc,
acroField,
fontObject,
text,
fontSize = 15,
) => {
const fieldRect = acroField.get('Rect');
const fieldWidth = fieldRect.get(2).number - fieldRect.get(0).number;
const fieldHeight = fieldRect.get(3).number - fieldRect.get(1).number;
const appearanceStream = pdfDoc.register(
PDFContentStream.of(
PDFDictionary.from({
Type: PDFName.from('XObject'),
Subtype: PDFName.from('Form'),
BBox: PDFArray.fromArray([
PDFNumber.fromNumber(0),
PDFNumber.fromNumber(0),
PDFNumber.fromNumber(fieldWidth),
PDFNumber.fromNumber(fieldHeight),
], pdfDoc.index),
Resources: PDFDictionary.from({
Font: PDFDictionary.from({
FontObject: fontObject
}, pdfDoc.index)
}, pdfDoc.index),
}, pdfDoc.index),
drawLinesOfText(text.split('\n'), {
x: 2,
y: fieldHeight - 13,
font: 'FontObject',
size: fontSize,
colorRgb: [0, 0, 0],
})
),
);
acroField.set('V', PDFString.fromString(text));
acroField.set('Ff', PDFNumber.fromNumber(1));
acroField.set('AP', PDFDictionary.from({ N: appearanceStream }, pdfDoc.index));
};
const pdfDoc = PDFDocumentFactory.load(fs.readFileSync('./template.pdf'));
// ...Embed fonts and images...
// ...Define hardcoded field names dictionary...
const fillInField = (fieldName, text, fontSize) => {
const field = findAcroFieldByName(pdfDoc, fieldName);
if (!field) throw new Error(`Missing AcroField: ${fieldName}`);
fillAcroTextField(pdfDoc, field, FontHelvetica, text, fontSize);
};
fillInField(fieldNames.name, 'Mario');
fillInField(fieldNames.age, '24 years');
fillInField(fieldNames.height, `5' 1"`);
fillInField(fieldNames.weight, '196 lbs');
fillInField(fieldNames.eyes, 'blue');
fillInField(fieldNames.skin, 'white');
fillInField(fieldNames.hair, 'brown');
// ...Fill in remaining fields...
const contentStream = pdfDoc.register(
pdfDoc.createContentStream(
drawImage('MarioImage', {
x: 50,
y: 450,
width: marioImageDims.width * 0.6,
height: marioImageDims.height * 0.6,
}),
drawImage('MarioEmblem', {
x: 447,
y: 520,
width: marioEmblemDims.width * 0.75,
height: marioEmblemDims.height * 0.75,
}),
),
);
const pages = pdfDoc.getPages();
pages[0]
.addImageObject('MarioImage', marioImageRef)
.addImageObject('MarioEmblem', marioEmblemRef)
.addContentStreams(contentStream);
const pdfBytes = PDFDocumentWriter.saveToBytes(pdfDoc);
The above script omits a few details for readability, as it's pretty verbose. Here's a zip file containing the complete script (along with the template PDF and images): pdf-lib_form_fill_example.zip
If you're on a Mac/Linux machine, then after downloading the zip file, you can run the script like so:
$ unzip pdf-lib_form_fill_example.zip
...
$ cd pdf-lib_form_fill_example
...
$ npm install
...
$ node index.js
Filled-in Template Written To ./filled.pdf
(Note the script is also runnable on Windows machines, I just don't know the CLI commands for Windows)
Here's the template PDF: template.pdf
And here's the filled out version output by the script: filled.pdf
This is so cool, thanks!
This is awesome! Question; is there a way to simply set the text only for the fields? For example, in your template.pdf, the 'CharacterName 2' field is pre-configured for centered text with auto font size. In the output we are changing those characteristics so they are set to left justified and 15pt font.
@mikeciz
Yes, this can be done as well.
The downside to this approach is that because the rendering of the text field's content is left up to the PDF reader, different readers can (and do) render the fields in slightly different ways. Of course, the upside is you don't have to fiddle around trying to get everything to look good. The most popular readers are probably Chrome and Adobe Acrobat, and these do a pretty good job of automatically rendering the text fields (Mac's Preview app works too, but doesn't render them as nicely).
Brief overview of the differences between the two versions of the script:
/* New helper function */
const getAcroForm = (pdfDoc) => {
return pdfDoc.index.lookup(pdfDoc.catalog.get('AcroForm'));
}
/* Different implementation of this helper */
const fillAcroTextField = (
pdfDoc,
acroField,
fontObject,
text,
fontSize = 15,
) => {
acroField.set('V', PDFString.fromString(text));
acroField.set('Ff', PDFNumber.fromNumber(
1 << 0 // Read Only
|
1 << 12 // Multiline
));
};
/* This has to be done before the `fillInField` calls */
const acroForm = getAcroForm(pdfDoc);
acroForm.set('NeedAppearances', PDFBoolean.fromBool(true));
@Hopding it would be great if you could update example for version 1.0.1. With newest version I'm getting (node:49908) UnhandledPromiseRejectionWarning: TypeError: pdfDoc.catalog.getMaybe is not a function
Thanks
UPDATE: Migrating to v1.0.1 instruction are here https://github.com/Hopding/pdf-lib#migrating-to-v100
@msantic
Thank you for your meaningful comments.
I updated example to v.1.1.1
https://github.com/Hopding/pdf-lib/issues/205#issuecomment-538655660
Hope this helps.
Hi @Hopding can i make this code for dynamic data and am having the html template which i need in fillable pdf format then need the user to fill and generate the user filled details in another pdf. can you suggest anything please help me out this
I've provided an updated implementation of the appearance stream generation code here: https://github.com/Hopding/pdf-lib/issues/205#issuecomment-568938999.
@Balaguru2409 I'm afraid I don't quite understand what you are trying to do. Would you please clarify your question and provide some additional details?
Hi @Hopding i need a editable pdf that pdf contain some text fields and also it should render from ejs file
@Balaguru2409 I'm afraid pdf-lib doesn't provide any specialized support for HTML or EJS templating (see https://github.com/Hopding/pdf-lib/issues/176). If you want to use pdf-lib, you'll need to figure out a way to translate your template into pdf-lib API calls. You can take a look at the Usage Examples to see what sort of APIs are provided by pdf-lib.
Is it possible to save the document as read-only?
Im using this to fill in forms in an existing pdf document.
I use .save() to save the changes made and then i write the file to an output path in node.
The problem i have is that the saved pdf allows for the form fields to be edited post write when opened in chrome for instance.
Are there any options that i have missed to make it read-only on save?
@MartinNilss Are you setting the read-only flag on your text fields? Here's an example demonstrating how to do this in the latest version of pdf-lib: https://github.com/Hopding/pdf-lib/issues/219#issuecomment-568613515
Hello, I am trying to join several acroforms on a page, but when I try to join them, the fields are blank, I am saving them with the save () method, I also use embedpdf and that is when the values no longer appear
@davidra24 Would you please open a new issue explaining your problem? Please include any code and PDFs needed to reproduce your issue.
I'm trying to prefill a pdf using React and allow them to overwrite the fields before submission to Rails. Is there a non-node version of const fillAcroTextField() ?
Attempting to map some json and fillInField("text1", this.state.user_name);
I'am trying to use your second example whitin my project and y receive the message
Cannot read property 'load' of undefined in the line
const pdfDoc = PDFDocumentFactory.load(fs.readFileSync(__dirname,'/public/pdf/consentimiento.pdf'));
Canyou help me ???
Second question : Can y fill a Digital signature field ???
Thanks
Dardo
Note : Alone you example works perfect
pdf-lib now has form creation and filling APIs that should be used instead of the above example(s). See the form filling JSFiddle for a working example. Additional information is available in the README and API docs. The PDFTextField.setText method is of particular relevance to this issue.
Hi @Hopding we have an pdf reader in local system, wanted editable pdf so that we can bind the data from data base to the editable pdf finally download after updating with new data, can you help in this issue. Or else do we have an possibility of creating an own editable pdf in local using pdf-lib. So please share API link here. Thanks in advance......
Most helpful comment
pdf-libnow has form creation and filling APIs that should be used instead of the above example(s). See the form filling JSFiddle for a working example. Additional information is available in the README and API docs. ThePDFTextField.setTextmethod is of particular relevance to this issue.