PDF generation — one FFI call per document. Labels / tickets / simple reports.
- Targets
- Node + Browser
- Version
- 0.1.1
Install
pnpm add @amigo-labs/pdfBenchmarks
Trend (8 pts)Benchmark
simple label
- @amigo-labs/pdf generate napi 41.24K hz
Benchmark
A4 multi-page report
- @amigo-labs/pdf generate napi 2.03K hz
Benchmark
batch 100 labels
- @amigo-labs/pdf generateMany napi 457 hz
README
@amigo-labs/pdf
PDF generation — document-as-data API. One FFI call per document, no fluent-chain pattern. Backed by
printpdf.
Install
pnpm add @amigo-labs/pdf
Usage
import { generate, generateMany } from '@amigo-labs/pdf'
// Single document — one FFI crossing:
const buf = generate({
title: 'Invoice',
pages: [
{
width: 210, // A4 width in mm
height: 297, // A4 height in mm
elements: [
{
kind: 'text',
text: { x: 20, y: 275, text: 'Invoice', fontSize: 24 },
},
{
kind: 'line',
line: { x1: 20, y1: 265, x2: 190, y2: 265, thickness: 0.5 },
},
{
kind: 'rect',
rect: { x: 20, y: 100, width: 170, height: 50, filled: false },
},
],
},
],
})
fs.writeFileSync('invoice.pdf', buf)
// Batch — one FFI crossing for the whole label-printing job:
const buffers = generateMany(labelSpecs)
Install for the browser
The same import works in Angular, React, Vite, esbuild, and webpack ≥ 5 — the bundler picks the WASM build via the browser conditional export:
import { generate } from '@amigo-labs/pdf'
The PDF engine makes this one of the heavier WASM bundles in the family — consider lazy-importing in code-split routes:
const { generate } = await import('@amigo-labs/pdf')
Element shapes
type PdfElement =
| { kind: 'text'; text: { x: number; y: number; text: string; fontSize?: number } }
| { kind: 'line'; line: { x1: number; y1: number; x2: number; y2: number; thickness?: number } }
| { kind: 'rect'; rect: { x: number; y: number; width: number; height: number; filled?: boolean } }
Scope (v0.1)
- Built-in Helvetica font (size only).
- Lines and rectangles (outlined / filled, black only).
- Multi-page documents.
generateMany(docs)for batch label-printing.
Scope cuts
- No fluent chain API. Per-call callbacks would multiply FFI
crossings — see
docs/perf-review/pdfkit.md. - No custom fonts. Fast-follow.
- No images (JPEG / PNG embedding). Fast-follow.
- No text layout (word-wrap, flowing text). You provide coordinates.
- No curves / arbitrary vector paths.
- Origin is bottom-left, units are mm. Flip Y if migrating from pdfkit.
See __conformance__/divergences.md.
License
MIT
Perf review
Perf-Review: @amigo-labs/pdf
Status: 🟡 Yellow leaning 🟢 Green (predicted — comparative bench pending) · Reviewed: 2026-07-02 · Version: 0.1.1
Verdict
Predicted-only. The bench shard contains absolute throughput for @amigo-labs/pdf but no pdfkit baseline rows, so no measured multiplier exists yet — docs/packages.json honestly says "TBD". The candidate review predicted Yellow leaning Green: PDF generation is real serialization work (content streams, xref tables, deflate) with a compact Buffer result, a shape that survives the FFI boundary well. The absolute numbers are strong (a simple label renders at ~41 kHz; the batch API sustains ≈45.7k labels/s), and the install-size story is dramatic on its own.
Evidence
Measured absolute throughput (docs/benchmarks/pdf.json, 2026-06-10, commit 8c743bf)
| Scenario | @amigo-labs/pdf | pdfkit | Speedup |
|---|---|---|---|
| simple label | 41 241 Hz | — (not benched) | — |
| A4 multi-page report | 2 027 Hz | — | — |
batch: 100 labels (generateMany) | 457.5 Hz (≈45 750 labels/s) | — | — |
- Install size: 1.35 MB vs
pdfkit’s 20.4 MBnode_modules. docs/packages.jsonspeedup:"TBD"— to be filled once the comparative bench lands.
Benchmark gaps
- The entire comparison.
pdfkitbaseline rows for the same three scenarios are the open item; until then the verdict stays the candidate’s prediction.
What shipped vs. the candidate prediction
The candidate review scoped “a new package, not a pdfkit drop-in” — that is what shipped:
- Declarative
generate(spec) → BufferplusgenerateManybatch API; no fluent chain, no streams. - Helvetica only, no images, no automatic text layout in v0.1.
Divergences
Not a pdfkit drop-in by design; output PDFs are structurally valid (conformance suite parses them) but not byte-comparable to pdfkit’s. See crates/pdf/__conformance__/divergences.md.
Pre-port assessment: pdfkit.md
References
- Crate:
crates/pdf - Bench shard:
docs/benchmarks/pdf.json docs/packages.jsonspeedup:"TBD"