This library is really great!
I really appreciate your many efforts.
I could understand how to fill form in v.1.x.x by @gfb107's helpful code.
https://github.com/Hopding/pdf-lib/issues/185#issuecomment-529146128
I want to try to use custom fonts to fill form, because standard fonts do not include another languages like Japanese.
I already tried in v.0.6.4 example below, but it did not work.
https://github.com/Hopding/pdf-lib/issues/48#issuecomment-441111299
(It freezed at last line PDFDocumentWriter.saveToBytes)
I searched documents but not found.
Could you show us example in v.1.x.x?
With many thanks.
@Hopding
I really appreciate that you keep working hard for this project.
I updated your example on https://github.com/Hopding/pdf-lib/issues/48#issuecomment-441492484 to v.1.x.x.
I want to help you, but this is all what I can do with my knowleadge.
Hope this helps.
pdf-lib_form_fill_example_3.zip
Could you consider improving this example become able to embed custom fonts?
Thank you.
hey, same problem to me, i want to fill form with Chinese, and i have tried custom fonts but didn't work. here is my issue: fill form text fields with special characters, Have you found the answer?
try this:
```js
acroField.set(PDFName.of('V'), PDFString.of('xffxfe-Nx87e'));
try this:
acroField.set(PDFName.of('V'), PDFString.of('\xff\xfe-N\x87e'));
@xia0ming, thank you for your helpful comment.
Thanks to @Hopding, @PlushBeaver, we can use PDFHexString.fromText in v1.2.0.
https://github.com/Hopding/pdf-lib/pull/204
But I still don't know how to embed custom font.
@astanet A pdf form cant be filled with a custom font. We have to specify the font while creating the form.
I really appreciate that you keep working hard for this project.
I updated your example on #48 (comment) to v.1.x.x.
I want to help you, but this is all what I can do with my knowleadge.
Hope this helps.
pdf-lib_form_fill_example_3.zip
Could you consider improving this example become able to embed custom fonts?
Thank you.
@astanet I downloaded v3 (thank you). I get some typescript warnings. Most of them I managed to fix, except for the line acroFields.array.map(ref => acroForm.context.lookup(ref)); array is marked as private in the typescript definitions. Any ideas how to make it ts compliant?
@astanet A pdf form cant be filled with a custom font. We have to specify the font while creating the form.
@kamranmuazzam I'm glad you paid attention to this issue. I think what you are saying is correct, but maybe we can embedding the font by specifying Appearance.
Actually, I already could done it with HummusJS. Here is the official sample below.
https://github.com/galkahana/HummusJSSamples/blob/master/filling-form-values/pdf-form-fill.js
Sadly, HummusJS only runs on nodejs server and cannot be used on the client. So I am hoping for this project.
I would like to know how pdf-lib can implement the same approach as the HummusJS sample above.
Thank you.
I really appreciate that you keep working hard for this project.
I updated your example on #48 (comment) to v.1.x.x.
I want to help you, but this is all what I can do with my knowleadge.
Hope this helps.
pdf-lib_form_fill_example_3.zip
Could you consider improving this example become able to embed custom fonts?
Thank you.@astanet I downloaded v3 (thank you). I get some typescript warnings. Most of them I managed to fix, except for the line
acroFields.array.map(ref => acroForm.context.lookup(ref));array is marked as private in the typescript definitions. Any ideas how to make it ts compliant?
@florianbepunkt I'm glad my example helped you. I don't use Typescript, so I'm sorry but I don't know right way. Maybe I'm manipulating something I shouldn't touch. I hope your efforts will be successful.
Thank you.
pdf-lib now has form creation and filling APIs that should be used instead of the below example(s). They automagically handle creating the appearance streams for you. See the form filling JSFiddle for a working example. Additional information is available in the README and API docs. See also Creating and Filling Forms for an example of filling form fields with a custom font.
Hello @astanet!
In my experience, the most straightforward and flexible way to fill AcroForm text fields is to avoid writing your own appearance streams (that is what the HummusJS code you shared is doing). This is because the reader tends to be able to make things look nicer than custom pdf-lib/HummusJS code can. And perhaps more importantly, allowing the reader to construct its own appearance streams avoids the need to embed custom fonts and having to worry about the character sets they support.
I've documented how to do this here (along with an example script). It's also worth noting that while it is _possible_ to specify a custom font for readers to use while still constructing their own appearance streams, this is challenging to do right now in pdf-lib for embedded fonts (it's easy for standard fonts though). This is because pdf-lib embeds all non-standard fonts as CID fonts, and the only reader I've found that can produce its own appearance streams for CID fonts is Adobe Acrobat.
However, there are certainly still many cases where it is most appropriate to write your own appearance streams. So I've created a script demonstrating how to do this: pdf-lib_custom_font_form_fill.zip
And here's the gist of the script (in case you'd like to browse the code without downloading the ZIP file):
...
const fillAcroTextField = (acroField, text, font, multiline = false) => {
const rect = acroField.lookup(PDFName.of('Rect'), PDFArray);
const width =
rect.lookup(2, PDFNumber).value() - rect.lookup(0, PDFNumber).value();
const height =
rect.lookup(3, PDFNumber).value() - rect.lookup(1, PDFNumber).value();
const N = multiline
? multiLineAppearanceStream(font, text, width, height)
: singleLineAppearanceStream(font, text, width, height);
acroField.set(PDFName.of('AP'), acroField.context.obj({ N }));
acroField.set(PDFName.of('Ff'), PDFNumber.of(1 /* Read Only */));
acroField.set(PDFName.of('V'), PDFHexString.fromText(text));
};
const beginMarkedContent = tag =>
PDFOperator.of(Ops.BeginMarkedContent, [asPDFName(tag)]);
const endMarkedContent = () => PDFOperator.of(Ops.EndMarkedContent);
const singleLineAppearanceStream = (font, text, width, height) => {
const size = font.sizeAtHeight(height - 5);
const lines = [font.encodeText(text)];
const x = 0;
const y = height - size;
return textFieldAppearanceStream(font, size, lines, x, y, width, height);
};
const multiLineAppearanceStream = (font, text, width, height) => {
const size = 12;
const textWidth = t => font.widthOfTextAtSize(t, size);
const lines = breakTextIntoLines(text, [' '], width, textWidth).map(line =>
font.encodeText(line),
);
const x = 0;
const y = height - size;
return textFieldAppearanceStream(font, size, lines, x, y, width, height);
};
const textFieldAppearanceStream = (font, size, lines, x, y, width, height) => {
const dict = font.doc.context.obj({
Type: 'XObject',
Subtype: 'Form',
FormType: 1,
BBox: [0, 0, width, height],
Resources: { Font: { F0: font.ref } },
});
const operators = [
beginMarkedContent('Tx'),
pushGraphicsState(),
...drawLinesOfText(lines, {
color: rgb(1, 0, 0),
font: 'F0',
size: size,
rotate: degrees(0),
xSkew: degrees(0),
ySkew: degrees(0),
x: x,
y: y,
lineHeight: size + 2,
}),
popGraphicsState(),
endMarkedContent(),
];
const stream = PDFContentStream.of(dict, operators);
return font.doc.context.register(stream);
};
(async () => {
const ubuntuFontBytes = await fetch(
'https://github.com/google/fonts/raw/master/ufl/ubuntu/Ubuntu-Regular.ttf',
).then(res => res.arrayBuffer());
const pdfDoc = await PDFDocument.load(fs.readFileSync('./template.pdf'));
pdfDoc.registerFontkit(fontkit);
const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman);
const ubuntuFont = await pdfDoc.embedFont(ubuntuFontBytes);
...
const fillInField = (fieldName, text, font, multiline = false) => {
const field = findAcroFieldByName(pdfDoc, fieldName);
if (!field) throw new Error(`Missing AcroField: ${fieldName}`);
fillAcroTextField(field, text, font, multiline);
};
fillInField(fieldNames.name, 'Mario (Марио)', ubuntuFont);
fillInField(fieldNames.age, '24 years', timesRomanFont);
fillInField(fieldNames.height, `5' 1"`, timesRomanFont);
fillInField(fieldNames.weight, '196 lbs', timesRomanFont);
fillInField(fieldNames.eyes, 'blue', timesRomanFont);
fillInField(fieldNames.skin, 'white', timesRomanFont);
fillInField(fieldNames.hair, 'brown', timesRomanFont);
fillInField(
fieldNames.backstory,
`Mario is a fictional character in the Mario video game franchise, owned by Nintendo and created by Japanese video game designer Shigeru Miyamoto. Serving as the company's mascot and the eponymous protagonist of the series, Mario has appeared in over 200 video games since his creation. Depicted as a short, pudgy, Italian plumber who resides in the Mushroom Kingdom, his adventures generally center upon rescuing Princess Peach from the Koopa villain Bowser. His younger brother and sidekick is Luigi.`.trim(),
ubuntuFont,
true,
);
fillInField(
fieldNames.featuresAndTraits,
`Mario can use three basic power-ups:\n - the Super Mushroom, which causes Mario to grow larger\n - the Fire Flower, which allows Mario to throw fireballs\n - the Starman, which gives Mario temporary invincibility`.trim(),
ubuntuFont,
true,
);
fillInField(
fieldNames.allies,
`Allies:\n - Princess Daisy\n - Princess Peach\n - Rosalina\n - Geno\n - Luigi\n - Donkey Kong\n - Yoshi\n - Diddy Kong\n\nOrganizations:\n - Italian Plumbers Association`.trim(),
ubuntuFont,
true,
);
fillInField(fieldNames.factionName, `Mario's Emblem`, ubuntuFont);
const pdfBytes = await pdfDoc.save();
})();
And here's the output file: filled.pdf

>
I hope this helps. Please let me know if you have any additional questions!
Sidenote: You've probably observed that this requires a fair amount of custom code to achieve. This is true of AcroForm manipulation in general with pdf-lib. @cshenks is doing some exciting work to create a proper AcroForms API that will make all of this significantly easier to do with clean, high-level APIs.
@florianbepunkt You might wish to take a look at the example scripts I mentioned above. They should not generate any TypeScript errors.
@Hopding That's what I really wanted. You always do great jobs. I hope others do not make the same mistake as me. I'm looking forward to use AcroForms API. I am grateful for your and @cshenks's cooperation and effort.
@Hopding that worked great, only issue i had was fields with the same name. But i was able to overcome this by looking its "Kids", and everything pretty much worked as is.
Now i hope the issue can be closed.
On Sun, May 24, 2020, 23:59 Chad Scira notifications@github.com wrote:
@Hopding https://github.com/Hopding that worked great, only issue i had
was fields with the same name. But i was able to overcome this by looking
its "Kids", and everything pretty much worked as is.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Hopding/pdf-lib/issues/205#issuecomment-633267926,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AL2WUZU4CZUTGVXYEIELTFTRTFOBXANCNFSM4I3OBQZQ
.
Hi @Hopding , first of all, awesome library , i've created a js "PdfReader" that references all the work you've done in this thread.
`const {
PDFArray,
PDFHexString,
PDFNumber,
breakTextIntoLines,
PDFOperator,
degrees,
drawLinesOfText,
PDFOperatorNames: Ops,
PDFName,
rgb,
asPDFName,
PDFContentStream,
pushGraphicsState,
popGraphicsState,
} = require('pdf-lib');
const beginMarkedContent = tag =>
PDFOperator.of(Ops.BeginMarkedContent, [asPDFName(tag)]);
const endMarkedContent = () => PDFOperator.of(Ops.EndMarkedContent);
class PdfReader {
logAcroFieldNames (pdfDoc) {
const acroFields = this.getAcroFields(pdfDoc);
acroFields.forEach((acroField) => {
console.log(
'Field Name:',
acroField.get(PDFName.of('T')),
'Field Type:',
acroField.get(PDFName.of('FT'))
);
});
};
textFieldAppearanceStream (font, size, lines, x, y, width, height) {
const dict = font.doc.context.obj({
Type: 'XObject',
Subtype: 'Form',
FormType: 1,
BBox: [0, 0, width, height - (size/2)],
Resources: {
Font: {
F0: font.ref
}
},
});
let lineParams = {
color: rgb(1, 0, 0),
font: 'F0',
size: size,
rotate: degrees(0),
xSkew: degrees(0),
ySkew: degrees(0),
x: x,
y: y,
lineHeight: size + 2,
};
const operators = [
beginMarkedContent('Tx'),
pushGraphicsState(),
...drawLinesOfText(lines, lineParams),
popGraphicsState(),
endMarkedContent(),
];
const stream = PDFContentStream.of(dict, operators);
return font.doc.context.register(stream);
};
multiLineAppearanceStream (font, text, width, height) {
const size = 12;
const textWidth = t => font.widthOfTextAtSize(t, size);
const lines = breakTextIntoLines(text, [' '], width, textWidth).map(line =>
font.encodeText(line),
);
const x = 0;
const y = height - size;
return this.textFieldAppearanceStream(font, size, lines, x, y, width, height);
};
singleLineAppearanceStream (font, text, width, height) {
const size = font.sizeAtHeight(height - 5);
const lines = [font.encodeText(text)];
const x = 0;
const y = height - size;
return this.textFieldAppearanceStream(font, size, lines, x, y, width, height);
};
getAcroForm (pdfDoc) {
return pdfDoc.catalog.lookup(PDFName.of('AcroForm'));
};
getAcroFields (pdfDoc) {
const acroForm = this.getAcroForm(pdfDoc);
if (!acroForm) return [];
const fieldRefs = acroForm.lookupMaybe(PDFName.of('Fields'), PDFArray);
if (!fieldRefs) return [];
const fields = new Array(fieldRefs.size());
for (let idx = 0, len = fieldRefs.size(); idx < len; idx++) {
fields[idx] = fieldRefs.lookup(idx);
}
return fields;
};
fillInField (pdfDoc, fieldName, text, font, multiline = false) {
const field = this.findAcroFieldByName(pdfDoc, fieldName);
if (!field) throw new Error(`Missing AcroField: ${fieldName}`);
let isMultiline = text.indexOf("\r") > -1 || text.indexOf("\n") > -1;
this.fillAcroTextField(field, text, font, isMultiline);
};
findAcroFieldByName (pdfDoc, name) {
const acroFields = this.getAcroFields(pdfDoc);
return acroFields.find(acroField => {
const fieldName = acroField.get(PDFName.of('T'));
return !!fieldName && fieldName.value === name;
});
};
fillAcroTextField (acroField, text, font, multiline = false) {
const rect = acroField.lookup(PDFName.of('Rect'), PDFArray);
const width =
rect.lookup(2, PDFNumber).value() - rect.lookup(0, PDFNumber).value();
const height =
rect.lookup(3, PDFNumber).value() - rect.lookup(1, PDFNumber).value();
const N = multiline ?
this.multiLineAppearanceStream(font, text, width, height) :
this.singleLineAppearanceStream(font, text, width, height);
acroField.set(PDFName.of('AP'), acroField.context.obj({N}));
let ff = multiline ? PDFNumber.of(1 << 0 | 1 << 12) : PDFNumber.of(1);
acroField.set(PDFName.of('Ff'), ff);
acroField.set(PDFName.of('V'), PDFHexString.fromText(text));
};
}
module.exports = PdfReader;`
This works perfectly as a node service that would accept a pdf file and an array of key:values that represent field names and it's values. My issue now is that for multiline text,
i am not able to set the distance between each line. i've tried setting the lineHeight with different values but the output seems to be the same.
is it possible for example if you have a very long field and only 3 lines of multiline text, we can position it vertically in the center?
Thanks and great work!
@Hopding Hi Andrew thanks for a great library.
I tried your solution but got TypeError: acroForm.lookupMaybe is not a function
at getAcroFields (C:UsersgethiDesktoptest2pdf-lib_read_only_form_fill_exampleindex.js:19:30)
I'm using v1.9.
Thank you
pdf-lib now has form creation and filling APIs that should be used instead of the above example(s). They automagically handle creating the appearance streams for you. See the form filling JSFiddle for a working example. Additional information is available in the README and API docs. See also Creating and Filling Forms for an example of filling form fields with a custom font.
@Hopding how dare you! Now I have to recode everything using the new beautiful form api!
(thank you 👍 )
Most helpful comment
Update (9/16/2020)
pdf-libnow has form creation and filling APIs that should be used instead of the below example(s). They automagically handle creating the appearance streams for you. See the form filling JSFiddle for a working example. Additional information is available in the README and API docs. See also Creating and Filling Forms for an example of filling form fields with a custom font.Original
Hello @astanet!
In my experience, the most straightforward and flexible way to fill AcroForm text fields is to avoid writing your own appearance streams (that is what the HummusJS code you shared is doing). This is because the reader tends to be able to make things look nicer than custom pdf-lib/HummusJS code can. And perhaps more importantly, allowing the reader to construct its own appearance streams avoids the need to embed custom fonts and having to worry about the character sets they support.
I've documented how to do this here (along with an example script). It's also worth noting that while it is _possible_ to specify a custom font for readers to use while still constructing their own appearance streams, this is challenging to do right now in pdf-lib for embedded fonts (it's easy for standard fonts though). This is because pdf-lib embeds all non-standard fonts as CID fonts, and the only reader I've found that can produce its own appearance streams for CID fonts is Adobe Acrobat.
However, there are certainly still many cases where it is most appropriate to write your own appearance streams. So I've created a script demonstrating how to do this: pdf-lib_custom_font_form_fill.zip
And here's the gist of the script (in case you'd like to browse the code without downloading the ZIP file):
And here's the output file: filled.pdf
>
I hope this helps. Please let me know if you have any additional questions!