Skip to content

PDFKit crashes when providing fonts as Buffer in table #1743

Open

Description

@ykw9263

Bug Report

Description of the problem

When providing font in table cells with a src of Buffer or UintArray, Not a supported font format or standard PDF font. will be thrown.

Upon some digging, I found that in normalizeCell() the font object is deep-merged with other fonts in table styles

const font = deepMerge({}, colStyle.font, rowStyle.font, cell.font);

This line took an unusually long time to execute, and after this the font src became something like

console.log(font.src)
// { '1': number, '2': number, '3': number, ... }

Code sample

import PDFDocument from "pdfkit";
import fs from "fs";
const doc = new PDFDocument();

doc.table().row([
  {
    text: 'Hello World',
    font: { src: fs.readFileSync(SOME_CUSTOM_FONT), family: FONT_FAMILY },
  }
])

Possible fix

Replace the font deep merge with manual property assignment.
Considering the structure of the font object a shallow merge may also be enough.

// changing to this worked properly in my case
const font = {...colStyle.font, ...rowStyle.font, ...cell.font}

Your environment

  • pdfkit version: 0.19.1
  • Node version: 24.15.0
  • Browser version (if applicable): N/A
  • Operating System: Windows 11

Activity

  1. ykw9263 commented on Jun 25, 2026

    @ykw9263
    Author

    A workaround is to register the font with registerFont and use the alias in table cell font, like below

    import PDFDocument from "pdfkit";
    import fs from "fs";
    const doc = new PDFDocument();
    
    doc.registerFont(ALIAS, SOME_CUSTOM_FONT, FONT_FAMILY);
    
    doc.table().row([
      {
        text: 'Hello World',
        font: { src: ALIAS },
      }
    ])
  2. blikblum commented on Jun 25, 2026

    @blikblum
    Member

    const font = {...colStyle.font, ...rowStyle.font, ...cell.font}

    Please, open a PR with this change

    @hollandjake any thoughts?

  3. ykw9263 commented on Jun 25, 2026

    @ykw9263
    Author

    Thanks, I will fork and open one.

    btw seems that putting font buffer in columnStyles and rowStyles have the same issue even with the suggested fix.
    The DefinitelyTyped type definitions didn't include fonts options in columnStyles or rowStyles though.

  4. changed the title [-]PDFKit crashes when providing fonts as Buffer in table cells[/-] [+]PDFKit crashes when providing fonts as Buffer in table[/+] on Jun 25, 2026
  5. blikblum commented on Jun 26, 2026

    @blikblum
    Member

    btw seems that putting font buffer in columnStyles and rowStyles have the same issue even with the suggested fix.
    The DefinitelyTyped type definitions didn't include fonts options in columnStyles or rowStyles though.

    @hollandjake is this by design?

  6. hollandjake commented on Jun 26, 2026

    @hollandjake
    Contributor

    @blikblum definitely sounds unintentional

  7. blikblum commented on Jun 27, 2026

    @blikblum
    Member

    I examined the font loading and concluded that allowing passing font buffer to font.src options in table row options is a foot gun.

    Doing:

    const fontBuffer = fs.readFileSync(SOME_CUSTOM_FONT)
    doc.table().row([{
        text: 'Row 1',
        font: { src: fontBuffer },
      }]).row([{
        text: 'Row 2',
        font: { src: fontBuffer },
      }]).row([{
        text: 'Row 3',
        font: { src: fontBuffer },
      }])

    Would load 3 different font instances

    So i'm leaning toward only allowing font aliases on table styles. To be fair, the font option in table is not documented at all so is just a matter of documentation

  8. ykw9263 commented on Jun 27, 2026

    @ykw9263
    Author

    I haven't look deeper into the font loading mechanism yet. Does these lines deduplicates font buffers before loading?

    // check for existing font familes with the same name already in the PDF
    // useful if the font was passed as a buffer
    if (
    (font = this._fontFamilies[this._font.name]) &&
    isEqualFont(this._font, font)
    ) {
    this._font = font;
    return this;
    }

    My original use case was to make something like table builder that supports providing fonts with Uint8arrays as the font may be fetched from online sources. Registering an alias for the font would require hashing the buffer or extracting the font name from the buffer if I want the alias not to duplicate.

  9. ykw9263 commented on Jun 27, 2026

    @ykw9263
    Author

    btw seems that putting font buffer in columnStyles and rowStyles have the same issue even with the suggested fix.
    The DefinitelyTyped type definitions didn't include fonts options in columnStyles or rowStyles though.

    @hollandjake is this by design?

    By the PDFKit documentation on Tables Column options and Row options extends Cell options, which includes options for font. So I guess it was a miss

  10. blikblum commented on Jun 27, 2026

    @blikblum
    Member

    I haven't look deeper into the font loading mechanism yet. Does these lines deduplicates font buffers before loading?

    One line above the font instance is created. This code, AFAIK, does not save any resource

  11. ykw9263 commented on Jun 27, 2026

    @ykw9263
    Author

    Oh so at best it will just skip the font registeration on duplicated font buffers. At least GC seems working so the concern is the expensive font creation and comparison. The problem worsens when using column / row options as the font buffer will be applied on every affected cell

    So i'm leaning toward only allowing font aliases on table styles. To be fair, the font option in table is not documented at all so is just a matter of documentation

    The unsupported font error somehow makes sense now :p

  12. blikblum commented on Jun 27, 2026

    @blikblum
    Member

    Using family prop togetther with src: buffer will avoid the font creation every time

  13. ykw9263 commented on Aug 21, 2026

    @ykw9263
    Author

    Seems fixed in #1770. Should I close the issue then?

  14. ykw9263 commented on Aug 21, 2026

    @ykw9263
    Author

    Oh the PR missed the deepMerges inside normalizedRowStyle and normalizedColumnStyle in lib/table/style.js‎

    // Merge defaults
    rowStyle = deepMerge(defaultRowStyle, rowStyle);

    pdfkit/lib/table/style.js

    Lines 117 to 119 in 7438d8a

    // Merge defaults
    colStyle = deepMerge(defaultColStyle, colStyle);

    Passing font as buffers in column and row styles still fails with Error: Not a supported font format or standard PDF font

        test('row buffer font', () => {
          const fontBuffer = fs.readFileSync('tests/fonts/Roboto-Regular.ttf');
          const document = new PDFDocument();
          const fontSpy = vi.spyOn(document, 'font');
    
          const table = document.table({
            rowStyles: [
              { font: { src: fontBuffer } },
            ],
          });
          table.row(['A']);
          expect(fontSpy).toHaveBeenCalledWith(
            fontBuffer,
            expect.toSatisfy(() => true),
          );
        });

    Should be an easy fix by applying the same patch to these two functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions