Open-source layout engine · v1.19.0

The programmable typesetter for the web

Postext takes semantic markdown and applies centuries-old editorial rules — Knuth-Plass justification, balanced columns, floats that know their place — to produce publication-grade pages in HTML, canvas or print-ready PDF.

Every page is computed in your browser

Postext

In short

Postext is free, open-source software that lays out the pages of books, magazines and reports. You write text with a few simple marks, and Postext turns it into finished pages, like the pages of a printed book. It follows rules that printers have used for centuries, so the text is easy to read. The pages can go on a website, in an app, or into a PDF ready to print. It is made for writers, publishers, designers and developers.

Transcript

Everything the narrator says, with a description of what appears on screen.

  1. 0:00

    On screenA dark card reads “MAINZ · 1455” above a thin gold rule. Behind the headline “For five centuries, print learned to set a page.”, the year counts up from 1455 to 2026 in huge outline numerals, and printing terms land around it: Justification, Kerning, Widows, orphans, Balanced columns, Leading, Baseline grid, hy·phen·a·tion, Measure, Floats, Running heads, Rivers. The camera dives into the final full stop, which floods the screen with gold.

    NarrationFor five centuries, print learned how to set a page.

  2. 0:05

    On screenHeadline: “The web learned to lay out interfaces. It never learned to set a page.” A browser window at example.com/longform/article holds a three-column justified article, “Why the web can’t set a page”; red tags flag rivers of white space, a widow, an orphan, a picture landing on the text and an unbalanced last column. Beside it, a code chip reads “column-count: 3;”, then “…and little else.” and a crossed-out list of the same five faults. The window glitches and collapses.

    NarrationThe web learned to lay out interfaces. Never pages. So long-form text still breaks: rivers, widows, orphans, collisions.

  3. 0:18

    On screenAfter a white flash and a sweep of blue, gold and red stripes, the Postext logo and wordmark rise into place with the tagline “The programmable typesetter for the web”. Below it: OPEN SOURCE · MIT LICENCE · v1.7.0 · RUNS IN YOUR BROWSER.

    NarrationMeet Postext: the programmable typesetter for the web.

  4. 0:24

    On screenSection 01 of 07, “Content in”: “Markdown says what things are.” A Markdown file, 03-why-postext.md, types itself out: a part directive, the heading “Why Postext” with a lead, a paragraph that cites figure fig-flow with :ref, a ::resource line and a :::callout block. Next to it, config.ts calls buildDocument with a two-column layout, justified body text and hyphenation for en-us, under the line “Configuration decides how they look. Nothing about layout lives in the text.”

    NarrationYou write semantic Markdown, which says what things are. A configuration decides how they look.

  5. 0:32

    On screenSection 02, “Pretext measures”: “Measured without touching the DOM.” A heading, a paragraph and a callout are scanned one by one and dimension lines give their heights in points; a code panel shows prepare(text, '25px Lora') and layout(prepared, width, 38) returning a height and a line count. Two bars race through 10,000 paragraphs: “Browser reflow · DOM” barely moves while “Pretext · canvas metrics + arithmetic” fills at once, and a counter climbs to 600× above “faster text measurement than the DOM, so a whole book reflows as you type” and “Powered by @chenglou/pretext”.

    NarrationEvery paragraph is measured without touching the DOM, up to six hundred times faster, thanks to Pretext, the library that made it possible.

  6. 0:42

    On screenSection 03, “Postext decides”: “Every line placed before a pixel is drawn.” A ring of seven segments lights up in turn, Structure, Measure, Place, Float, Refine, Balance and Rhythm, each with a one-line description, while a two-column page of the Postext guide assembles beside it: labelled blocks fly into their columns, a figure slides into place under the note “:ref{id="fig-flow"} → column foot”, the opener band drops in, the lines appear, the columns level and a baseline grid sweeps down. The ring loops through iterations 1 to 3 and shows “Settled in 3 iterations” with a tick; a callout gives one line’s bounding box and baseline.

    NarrationThen Postext decides. Seven passes place every line, float every figure and balance every column, looping until nothing moves.

  7. 0:54

    On screenSection 04, “The virtual document”: “Change a word. Only what changed is set again.”, with the note “Like React’s virtual DOM, but for pages: the VDT, a virtual document tree.” Seven pages, p. 7 to p. 13, stand in a row; a caret marks an edit on p. 11, the rest of that column turns gold as DIRTY and a sliver spills onto p. 12, which gets a tick and SETTLED, while earlier pages read SKIPPED and later ones UNTOUCHED. Three bullets state the rule, and a panel headed renderToHtmlIndexed() marks the changed nodes of the document tree as PATCHED.

    NarrationIt all lives in a virtual document, like React's virtual DOM. Change a word, and only what changed is set again.

  8. 1:03

    On screen“A long document? Only what’s on screen gets painted.” A column of book pages scrolls through a gold frame labelled VIEWPORT, with a 200-pixel margin above and below: pages inside are drawn in full, pages outside are dashed outlines. A large counter shows how many of the 48 pages are painted at that moment (IntersectionObserver · +200 px), next to “Geometry: every page. Pixels: only what you see.”

    NarrationAnd in a long book, only the pages on screen are ever painted.

  9. 1:10

    On screenSection 05, “Knuth–Plass justification”: “Every way to break the paragraph. At once.” On the left, a paragraph about line breaking is set first-fit, as a browser does it, and its loosest lines turn red. Below, every word becomes a point on a line: red arcs mark the greedy breaks, faint blue arcs try every feasible break and a gold path picks the best set. On the right, the Knuth–Plass version is evenly spaced, with the badness of each line, the formula “(1 + badness + penalty)²” and a lower total of demerits.

    NarrationA browser breaks lines greedily, one at a time. Postext uses Knuth–Plass, the algorithm behind TeX. It weighs every way to break the whole paragraph, and keeps the most even.

  10. 1:24

    On screenSection 06, “One layout, five outputs”: “What you see is what goes to press.” Under the code line const vdt = buildDocument(content, config); one page splits into five. Canvas (renderToCanvas(), pixel-accurate bitmaps) gets a magnifier over its pixel grid; HTML (renderToHtml(), selectable, resize-aware) gets every block outlined and tagged <h1>, <p>, <figure> and so on; PDF (renderToPdf(), print-ready, PDF/UA, CMYK) has four colour plates sliding into register, then crop marks and a colour bar. EPUB 3 (renderToEpub(), e-book, fixed or reflowable) shows the page in an e-reader tagged “Fixed layout”; the screen narrows to the size of a phone, the text reflows and the tag changes to “Reflowable”. Folio (postext-folio, a 3D book, pages that turn) opens the page into a two-page spread and turns a leaf over the spine.

    NarrationOne layout, five outputs: canvas, HTML, print-ready PDF, EPUB 3, fixed or reflowable, and a book in 3D. What you see is what goes to press.

  11. 1:41

    On screenSection 07, “Why an open standard”: “LaTeX set the bar for print. Publishing now lives on every screen, too.” Two cards: LaTeX, 1984, with crosses against “Built for the printed page”, “Builds tied to one machine’s fonts & packages”, “Compile, wait, repeat” and “No editorial layout on the responsive web”; Postext, 2026, with ticks against “Web, print-ready PDF & EPUB 3 from one layout”, “One portable .postext file carries it all”, “Reflows live, as you type” and “Runs in any browser · zero servers · MIT”. The Postext card lights up.

    NarrationLaTeX set the standard for print. But publishing now lives on every screen.

  12. 1:48

    On screenKicker “One file, the whole book”, headline “An open standard for publishing, beyond where LaTeX reaches.” A .postext file icon bursts into five parts: preset.json (configuration & design), chapters/*.md (one Markdown file per chapter), fonts/*.woff2 (the typefaces themselves), images/ · SVG (every picture, embedded) and resources (tables, figures & captions). Caption: “A plain ZIP: open, documented, MIT-licensed. Any tool can read and write it.”

    NarrationPostext is an open standard: one portable file, documented and MIT-licensed, that any tool can read and write.

  13. 1:58

    On screen“The showcase · set by the real engine”: a wall of book pages recedes in perspective while a counter reaches 48 pages. Text: “12 chapters, 3 parts, a cover, laid out in about half a second. Every page of the Postext guide, computed in a browser by postext v1.7.0 for this film. Nothing placed by hand.” Tags follow: Textbooks, Magazines, Literary editions, Catalogues.

    NarrationThat's why it can lay out books of hundreds of pages in tenths of a second.

  14. 2:05

    On screenEnd card: the Postext logo with the tagline “The programmable typesetter for the web”, the address postext.dev, a terminal line that types “$ pnpm add postext”, and the footer “Open source · MIT · github.com/drnachio/postext”.

    NarrationPostext. Open source, at postext.dev.

Chapter 1 · Why Postext

Editorial layout, computed for the web

Print typography spent five centuries learning how to set a page; browsers learned how to lay out an interface. Postext brings the first to the second.

CSS gives you responsive layout — flexbox, grid, columns. But it cannot balance columns, keep a heading with its paragraph, prevent widows and orphans, or float a figure to the head of the next column. These are editorial decisions, refined over centuries of print.

Postext bridges that gap. Feed it semantic markdown and a configuration object. It measures text 300–600× faster than the DOM using @chenglou/pretext (opens in a new tab), then computes the complete page geometry — every line, float and caption — and renders it through HTML, canvas or print-ready PDF backends.

Markdown in

:::part{number="I" title="Foundations"
  palette="band=#2b4acb"}
:::

# Why Postext {lead="Print typography
spent five centuries…"}

Postext is an **open-source layout
engine** that brings the craft of
print typography to the web, as
:ref{id="fig-flow"} shows.

:::callout{type="try"}
Change a word: the page sets
itself again.
:::

Pages out

Figure 1.1 The same markdown, before and after the engine: a chapter opener, two balanced columns and a figure floated to the head of a column.

In figures

faster text measurement than the DOM, so a whole book reflows as you type
300–600×faster text measurement than the DOM, so a whole book reflows as you type
outputs from one layout: HTML, canvas, print-ready PDF and EPUB 3
4outputs from one layout: HTML, canvas, print-ready PDF and EPUB 3
tagged, accessible PDF output with bookmarks and CMYK colour
PDF/UAtagged, accessible PDF output with bookmarks and CMYK colour
servers: the engine runs entirely in the browser, MIT-licensed
0servers: the engine runs entirely in the browser, MIT-licensed

Chapter 2 · Capabilities

What print typesetters knew all along

Six things a page needs that CSS was never designed to decide — computed by the engine, the same way every time.

Every page of the Postext guide — cover, contents, part dividers and all — is set by Postext, in your browser, as you read it.
— Colophon of the built-in guide

Showcase

Real books, set in your browser

A textbook, a magazine, an annotated epic poem, an exhibition catalogue, a Chinese classic in 120 chapters, the Arabian Nights in Arabic, a Japanese novel set vertically. Open any of them in the Sandbox, change a rule and watch every page reflow.

Chapter 3 · Workflow

Three steps to publication-grade layout

Content in markdown, design in a configuration object, pages from one function call.

  1. 3.1

    Write semantic markdown

    Author your content in enriched markdown: headings, lists, tables, math, callouts and :ref cross-references to your figures. No layout concerns — just content.

  2. 3.2

    Configure the layout rules

    Define page size, columns, typography, heading designs, running heads and float placement in a PostextConfig object — or design it visually in the Sandbox.

  3. 3.3

    Render to HTML, canvas or PDF

    Call buildDocument and receive the complete layout geometry — then render it with the HTML, canvas or print-ready PDF backend.

API

One function, full control

Call buildDocument with your content and a configuration object. You get back the complete page geometry: draw any page as an image with renderPage, or set the whole book as a print-ready PDF with renderToPdf.

book.ts
import { buildDocument, renderPage } from "postext";
import { renderToPdf } from "postext-pdf";

const doc = buildDocument(
  { markdown },
  {
    page: { sizePreset: "21x28" },
    layout: { layoutType: "double" },
    bodyText: {
      fontSize: { value: 12, unit: "pt" },
      textAlign: "justify",
      hyphenation: { enabled: true, locale: "en-us" },
    },
  }
);

// Every page as an image…
const cover = renderPage(doc.pages[0], doc);
cover.toBlob((png) => save(png, "cover.png"));

// …or the whole book as a print-ready PDF
// fontProvider: TTF bytes per family and weight (see the docs)
const pdf = await renderToPdf(doc, { fontProvider });

Chapter 4 · Open format

One file, the whole book

A .postext file carries everything a document needs to be set again, on any machine: an open, editable format that anyone can adopt.

Most publishing formats scatter a project. A LaTeX build depends on the fonts and packages installed on one machine, and the native files of the major layout editors link to pictures and typefaces kept outside them. Move the project and something goes missing. We ran into those limits with LaTeX ourselves.

A .postext is a plain ZIP archive that anyone can open, read and edit: a JSON manifest, one Markdown file per chapter, and the font files and pictures themselves. The Sandbox exports it, the postext-port agent skill writes it, and postext/bundle opens it in any program to set the same pages again. The format is documented and MIT-licensed, ready for any tool to read and write.

Inside postext-guide.postext
  1. 4.1preset.json

    Configuration. Page, grid, typefaces, heading and paragraph styles, colours and running heads, as readable JSON.

  2. 4.2chapters/<lang>/*.md

    Chapters. One Markdown file per chapter: 14 chapters in 2 languages here.

  3. 4.3fonts/*.woff2

    Typefaces. The font files themselves, not just their names: 18 files of Lora, Fraunces, Geist, and Bricolage Grotesque.

  4. 4.4preset.json › resources

    Resources. Captions, numbering, placement and table content for the 26 figures and tables the text refers to.

  5. 4.5resources/*.svg

    Images. The artwork itself, SVG or bitmap, with each language's version: 38 files.

Figure 4.1 The guide's own .postext, 846 KB: everything the book needs, and nothing it has to fetch.

Try it

Open the guide, get its pages

The Sandbox's first example, the Postext guide, is one .postext file. Open it in the browser and turn every page into an image: its typefaces, figures and tables come from the file itself.

Download the guidepostext-guide.postext · 846 KB
open-guide.js
import { openBundle, loadBundleFonts, registerBundleImages,
  buildBundle, renderPageToCanvas } from "postext";

// The Sandbox's guide: config, chapters, fonts and images
const file = await fetch("postext-guide.postext").then((r) => r.blob());
const book = await openBundle(file, { locale: "en" });

await loadBundleFonts(book);      // its own typefaces
await registerBundleImages(book); // its own figures

// Every page of every chapter, as an image
for (const doc of buildBundle(book))
  for (const page of doc.pages) {
    const canvas = document.createElement("canvas");
    renderPageToCanvas(page, doc, canvas, { scale: 0.5 });
    document.body.append(canvas);
  }

Installation

Start in under a minute

One package, no server, no native dependencies. Add postext-pdf when you need print.

quick-start.ts
// Quick start
import { buildDocument, renderPage } from "postext";
import { renderToPdf } from "postext-pdf";

const doc = buildDocument({
  markdown: "# Hello World\n\nYour content here.",
});

// The first page, drawn on a canvas
document.body.append(renderPage(doc.pages[0], doc));

// The whole document as a PDF
const pdf = await renderToPdf(doc, { fontProvider });