@libs-jd/pdf-lib-bulk
v0.1.1
Published
Bulk PDF generation for pdf-lib — font parsing cached across documents (4x) and template cloning for identical documents (31x). Generate 100k PDFs without re-parsing the font 100k times.
Maintainers
Readme
pdf-lib-bulk
Bulk PDF generation for pdf-lib — without re-parsing your font for every single document.
pdf-lib calls fontkit.create(bytes) inside every embedFont(). Generate 100,000 invoices and you parse the same TTF 100,000 times. This library amortizes that away:
| Workload (100 one-page PDFs, custom TTF, Apple M1) | pdf-lib naive | pdf-lib-bulk | Speedup |
| --- | --- | --- | --- |
| Variable text per document (bulkGenerate) | 1,399 ms | 347 ms | 4.0x |
| Identical documents (makeTemplateCloner) | 1,515 ms | 48 ms | 31x |
Reproduce with bun run bench/bench.ts.
Install
npm install @libs-jd/pdf-lib-bulk pdf-lib @pdf-lib/fontkitUsage
Variable content per document (invoices, letters, tickets)
import fontkit from "@pdf-lib/fontkit";
import { bulkGenerate } from "@libs-jd/pdf-lib-bulk";
const fontBytes = await fs.promises.readFile("Inter-Regular.ttf");
const pdfs = await bulkGenerate(invoices, {
fontkit, // parsed once, reused for every document
fonts: { body: fontBytes },
draw({ doc, fonts, item }) {
const page = doc.addPage();
page.drawText(`Invoice #${item.id} — ${item.total}`, {
x: 50, y: 700, size: 24, font: fonts.body,
});
},
});
// pdfs: Uint8Array[] — one standalone PDF per invoiceIdentical documents (certificates, blank forms)
import { makeTemplateCloner } from "@libs-jd/pdf-lib-bulk";
// build the document once, however you like (fonts embedded once, layout once)
const templateBytes = await buildCertificateTemplate();
const cloner = await makeTemplateCloner(templateBytes);
const pdfs = await cloner.cloneMany(10_000);Just the font cache
If you want to keep your own generation loop, wrap fontkit yourself:
import fontkit from "@pdf-lib/fontkit";
import { cachingFontkit } from "@libs-jd/pdf-lib-bulk";
const fk = cachingFontkit(fontkit);
// ...
doc.registerFontkit(fk); // same API, parse happens once
const font = await doc.embedFont(fontBytes, { subset: true });How it works
cachingFontkit— memoizesfontkit.create()per font buffer (WeakMapkeyed by theUint8Arrayyou pass), so the expensive TTF/OTF parse happens once per font instead of once per document. Fonts are still embedded and subset per document, so every output PDF is standalone.bulkGenerate— a generation loop with the cache pre-wired: create doc → embed cached fonts → yourdrawcallback → save.makeTemplateCloner— loads your finished template once and produces copies viacopyPages, skipping layout, font parsing, and font embedding entirely.
About
Contributions welcome — please open an issue or PR. If this saved your batch job some hours, a ⭐ helps others find it.
Related
- pdf-lib — the PDF library this builds on
- @libs-jd/xlsx-parse-table — parse tables from Excel worksheets
Author
Jeet Dhandha — GitHub
