mbingpdf v0.1.0

TypeScriptMIT0 dependencies

Documents,
typeset
in code.

mbingpdf turns a description of a document into a print-ready PDF: in the browser, on a server, at the edge. It measures every glyph itself, so the same input gives the same file everywhere.

npm install mbingpdf

Open the playground Read the quick start

A certificate on its press sheet, with bleed, crop marks and registration marks, as mbingpdf wrote it

A4 landscape3 mm bleedcrop + registration marks20.5 kB · 15 ms

22ms

for a one-page invoice with an embedded font. Three times faster than the next library we measured.

0

runtime dependencies. No headless browser, no native binary, nothing to install on the server.

5,000

race bibs in one file, each font embedded once, in a test that runs on every commit.

4→1

Node, Bun, Deno and browsers write the same bytes. One output, wherever it runs.

01 — How it reads

You describe the page.
It works out the rest.

Elements are functions; a document is what they return. No template language, no coordinates, no build step. The receipt on the right is exactly what the code on the left produces: it was laid out when this page was built.

const bold = fonts.jakarta.instance({ wght: 700 });

return Document(
  { title: "Receipt", font: fonts.jakarta, size: 10 },
  Page(
    { size: [mm(80), mm(96)], margin: mm(8), gap: 12 },
    Text({ font: fonts.barlow, size: 36 }, "THANK YOU"),
    Table({
      columns: ["1fr", { width: 70, align: "end" }],
      rows: [
        ["Kopi susu, iced", "Rp 22.000"],
        ["Pisang goreng", "Rp 15.000"],
      ],
      footer: [[Text({ font: bold }, "Total"), Text({ font: bold }, "Rp 37.000")]],
    }),
    Spacer(),
    Row(
      { gap: 10, align: "center" },
      QrCode({ value: "https://example.com/r/8841", size: 48 }),
      Text({ color: "#6B665E" }, "Scan for your receipt.\nSee you again."),
    ),
  ),
);
A small receipt: a heading, two lines, a total and a QR code

02 — What is in the box

Everything a real document needs.

  1. 01

    Layout that paginates

    Rows and stacks sized like flexbox, tables that repeat their header, columns, grids. Content breaks across pages by itself, keeps headings with their text, and knows “page 3 of 12”.

    Row · Stack · Table · Columns · Grid
  2. 02

    Type, measured

    Reads TrueType, OpenType and variable fonts and embeds only the glyphs used. Ligatures, kerning and accents match HarfBuzz. Hyphenation for English and Indonesian.

    Font · instance() · hyphenate · fit · truncate
  3. 03

    Ready for the press

    CMYK and spot colours, bleed, crop and registration marks, an ICC output intent and PDF/X-4. The file a print shop asks for, not a screenshot of a web page.

    cmyk() · spot() · bleed · marks · PDF/X-4
  4. 04

    Archival and accessible

    PDF/A-2 and A-3 for the archive; tagged PDF and PDF/UA-1 for screen readers, built from the elements you already wrote. Every level is validated with veraPDF in CI.

    conformance · tagged · alt · role
  5. 05

    Charts, barcodes, QR

    Bar, line and pie charts as vectors in the document's own font. Code 128, EAN-13, ITF and QR codes, each one decoded again by two independent scanners in the tests.

    BarChart · LineChart · PieChart · Barcode · QrCode
  6. 06

    Locked when it must be

    AES-256 passwords and permissions. Files carried inside the PDF, including the XML of a Factur-X e-invoice in a PDF/A-3 file.

    encryption · attachments · facturX()
  7. 07

    Seen before it is printed

    The same layout drawn as SVG: a preview component for React and for Vue, and a dev server that redraws your document every time you save.

    PdfPreview · usePdf · mbingpdf dev
  8. 08

    The same bytes everywhere

    No DOM, no canvas, no native code, no dependencies. Node, Bun, Deno, browsers and workers produce byte-identical files, checked on every commit.

    render() · render.sync() · deterministic

04 — Measured

Fast where it counts.
Honest where it is not.

Three documents, built by four libraries from the same data with the same font. mbingpdf writes the smallest file in all three and is the fastest on the invoice. It is not the fastest everywhere: pdfmake lays out a 2,000-row table in half the time, and PDFKit sets plain paragraphs faster. Method and full results.

Invoice one page, a table of 12 lines

LibraryTime, drawnTimeFile
mbingpdf22 ms18.3 kB
@react-pdf/renderer72 ms20.4 kB
pdfmake77 ms19.8 kB
PDFKit293 ms19.4 kB

Long table 2,000 rows, header on every page

LibraryTime, drawnTimeFile
pdfmake1.02 s250.3 kB
mbingpdf1.90 s156.4 kB
@react-pdf/renderer25.93 s313.1 kB
PDFKit29.10 s248.6 kB

Running text 300 paragraphs, 24 pages

LibraryTime, drawnTimeFile
PDFKit261 ms87.8 kB
mbingpdf468 ms80.1 kB
pdfmake677 ms219.7 kB
@react-pdf/renderer1.60 s116.7 kB

Median of three runs, 10 November 2026, Node 26 on an Intel Core i7-8565U laptop. Times vary by 10–30% between runs.

05 — Start

One package. Three more if you want them.

Runs onBrowsersNode 22+BunDenoWorkers