PDF

Yellow Node + Browser Document TBD

PDF generation — one FFI call per document. Labels / tickets / simple reports.

Version
0.1.1

Install

pnpm add @amigo-labs/pdf

Benchmarks

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
Performance trend for PDF
8 commits · last 2026-06-10

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/pdfpdfkitSpeedup
simple label41 241 Hz— (not benched)
A4 multi-page report2 027 Hz
batch: 100 labels (generateMany)457.5 Hz (≈45 750 labels/s)
  • Install size: 1.35 MB vs pdfkit’s 20.4 MB node_modules.
  • docs/packages.json speedup: "TBD" — to be filled once the comparative bench lands.

Benchmark gaps

  • The entire comparison. pdfkit baseline 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) → Buffer plus generateMany batch 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.json speedup: "TBD"