Skip to content

The pdf exporter needs pdfmake's font VFS registered, and a missing font is a hang rather than an error #1

Closed

Description

@MichalAFerber

Found by Shawn 2026-09-04, consuming this package as an ESM git dependency from a new Astro app (MichalAFerber/wizard-web#98). Worked around app-side to unblock; filing here because this package owns the pdf exporter and the next consumer will rediscover it.

The defect

src/exporters/pdf.js does import pdfMake from 'pdfmake' and calls pdfMake.createPdf(dd). Nothing registers pdfmake's embedded Roboto faces, so through a bundler the PDF export fails:

File 'Roboto-Medium.ttf' not found in virtual file system

The standalone markdownwizard page never hit this because it loaded js/vendor/vfs_fonts.js as a <script>, and that file self-registers by looking for a global:

if (typeof _global.pdfMake !== 'undefined' && typeof _global.pdfMake.addVirtualFileSystem !== 'undefined') {
  _global.pdfMake.addVirtualFileSystem(vfs);
}
if (typeof module !== 'undefined') { module.exports = vfs; }

Under a bundler there is no global to attach to, so the first branch is skipped and nothing registers the fonts. The README says the ESM entry is "what an Astro app wants", and it is — this is the one thing missing to make that true for fmt: 'pdf'.

The part that cost real time: it hangs, it does not throw

pdfMake.createPdf(dd).getBlob(cb) never invokes the callback when a font is missing. The Promise the exporter returns therefore never settles. await exportAs({ fmt: 'pdf', … }) does not reject — it waits forever. A probe that exported all eight formats in a loop sat for thirty minutes before an external timeout killed it, with the only clue a console.warn in the page.

So the failure mode is: seven formats work, the eighth never returns, and the error text appears as a warning rather than a rejection. Worth guarding against on its own terms, separately from the VFS fix.

Suggested fix

Register the fonts inside the package, where the dependency on them lives:

import pdfMake from 'pdfmake';
import vfs from 'pdfmake/build/vfs_fonts';

// pdfmake resolves fonts through a virtual file system that its <script> build
// self-registers against a global. Through a bundler there is no global, so the
// package that imports pdfmake must register them itself — otherwise every
// createPdf fails on "Roboto-Medium.ttf not found", and getBlob never calls
// back, so the caller hangs rather than erroring.
if (typeof pdfMake.addVirtualFileSystem === 'function') {
  pdfMake.addVirtualFileSystem(vfs);
}

Notes for whoever takes it:

  • Verified against pdfmake 0.2.23. Its package.json has main: src/printer.js (the Node printer) and browser: build/pdfmake.js, so a bundler honouring browser gets the browser build, which is the one exposing addVirtualFileSystem. vfs_fonts.js is CommonJS exporting the vfs object directly.
  • vfs_fonts is roughly 768 KB of base64 font data, so importing it unconditionally at module scope adds that to any consumer bundling this package — including one that only ever exports .txt. If that matters, doing it lazily inside the pdf exporter's function body, or exporting a registerPdfFonts() the app calls once, are both better than the current silence. Whichever way, it should not stay the app's job by accident.
  • The IIFE build is unaffected: the offline page loads vfs_fonts.js as a script and the self-registration works there, which is exactly why this went unnoticed.
  • Consider making the hang loud regardless of where the fonts get registered — wrapping getBlob with a timeout that rejects, or checking the VFS is populated before createPdf. A missing font should be an error, not a stall.

Verified after the app-side workaround

With pdfMake.addVirtualFileSystem(vfs) called once before use, all eight formats produce real blobs from the same document in a browser: md 85B, txt 84B, html 1579B, doc 1866B, dot 1866B, rtf 572B, docx 8990B, pdf 13580B. So the exporter itself is correct — only the font registration was missing.

Activity

  1. MichalAFerber commented on Sep 5, 2026

    @MichalAFerber
    MemberAuthor

    Grok is taking this. Do not start a parallel implementation.

    • Agent: Shawn (Sr Developer)
    • Branch: shawn/pdf-vfs-register
    • Worktree: ~/GitHub/.worktrees/markdownwizard-tools/shawn-pdf-vfs-register
    • Scope: register pdfmake's font VFS inside the package (lazily, in the pdf exporter — not at module scope, so .txt consumers do not eat 768 KB of Roboto), and make a missing font an error rather than a hang.

    PR will open as a draft (Sr Dev → DevOps marks ready → Michal merges).

  2. MichalAFerber commented on Sep 5, 2026

    @MichalAFerber
    MemberAuthor

    Draft PR: #2

    Fonts are registered lazily inside pdf() via pdfmake/build/vfs_fonts.js + addVirtualFileSystem. A missing font no longer hangs: getBlob is bounded at 15s and rejects with the Roboto-Medium message. IIFE still uses the page's vfs_fonts.js script (stub alias, no 768 KB in dist/).

  3. added a commit that references this issue on Sep 5, 2026
    237bd6e
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