question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Fill form text fields with custom font in v.1.x.x

See original GitHub issue

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.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:18 (3 by maintainers)

github_iconTop GitHub Comments

8reactions
Hopdingcommented, Sep 16, 2020

Update (9/16/2020)

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.

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):

...

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

<kbd> output file </kbd>

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.

3reactions
Hopdingcommented, Sep 16, 2020

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Setting custom attributes on form fields (e.g. font, point size ...
Q: I have everything up and running and the new demo is working fabulously - compiles wonderfully on XCode where I do my...
Read more >
PDF form field properties, Adobe Acrobat
In Adobe Acrobat, how a form field behaves is determined by settings in the Properties dialog box for that individual field.
Read more >
Text Input and Custom Fonts (Godot Retro Text Adventure ...
In this tutorial we add a LineEdit node so that we can collect text input from the player in our Zork-like retro text...
Read more >
Text fields - Material Design
A mobile UI for a contacts app form with many filled text fields. Mobile form using filled text fields ... Input text, Subtitle...
Read more >
font-face - CSS: Cascading Style Sheets - MDN Web Docs
Chrome Edge @font‑face Full support. Chrome1. Toggle history Full support... OpenType CBDT and CBLC rendering Full support. Chrome66. Toggle history Full support... OpenType COLRv0 rendering Full...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found