A programmable typesetter for the web.
π Website: postext.dev Β· π Documentation: postext.dev/en/docs
Postext is a layout engine that bridges the centuries-old craft of print typesetting and the modern web. It takes semantic content β enriched markdown with referenced resources β and applies professional editorial layout rules to produce publication-grade output for HTML, PDF and EPUB.
Built on top of @chenglou/pretext for DOM-free text measurement.
How It Works
Enriched Markdown Postext engine Output
βββββββββββββββββ βββββββββββββββββββββββββ βββββββββββββ
ββββββββββββββββββββββββββ
# Heading β β HTML + CSS
Paragraph text... β 1. Parse & structure β βββΊ (web rendering)
More text with a ββββΊ β 2. Measure (pretext) β ββββ€
:ref{id="fig"} and β 3. Apply layout rules β βββΊ PDF
a $formula$. β 4. Compute geometry β (print rendering)
β β
ββββββββββββββββββββββββββ
β²
Configuration files
(columns, rules, spacing)
-
Input. Enriched markdown with referenced resources: images, tables, figures, formulas, pull quotes. The content is semantic, not visual β it describes what to present, not how.
-
Engine. postext parses the content structure, calls pretext for pixel-perfect text measurement without touching the DOM, then runs the layout algorithm: column balancing, resource placement, typographic quality rules, reference systems. All driven by configuration files.
-
Output. Format-agnostic layout geometry β precise coordinates and dimensions for every element. Renderers translate this geometry to the target format: canvas for page images, HTML/CSS for the web, PDF for print, EPUB 3 for e-book readers, and a 3D book in the browser (Folio).
Why postext?
The gap between print and web
A professional typesetter β the person who lays out a magazine, a newspaper, or a book β applies dozens of rules that have been refined over centuries:
- No orphans (a single line of a paragraph stranded at the top of a column).
- No widows (a single line of a paragraph left behind at the bottom).
- Balanced columns (roughly equal height across columns on a spread).
- Text flowing around figures, pull quotes, and inset images.
- Proper placement of footnotes at the bottom of the column where they are referenced.
- Margin notes aligned with the paragraph that cites them.
- Consistent and deliberate spacing around headings, block quotes, and figures.
- Hyphenation that avoids rivers and excessive rag.
CSS gives you almost none of this.
column-count exists but is fragile β there is no control over resource placement within columns, no overflow awareness, and no cross-column orphan/widow prevention. There is no native concept of "flow text around an arbitrarily positioned obstacle." Multi-column editorial layouts on the web remain essentially hand-crafted, usually as static designs that break on different screen sizes.
The web has had responsive layout (flexbox, grid) for over a decade. What it has never had is responsive editorial layout β the kind where content reflows intelligently through columns, around images, past footnotes, obeying the rules that make text pleasant to read.
A decade of trying
This problem is not new. The Content First project, started around 2014, was an early attempt to bring editorial layout quality to the web. I built column-based layouts, experimented with text flow algorithms, and ran into the same wall repeatedly: you cannot make good layout decisions without knowing exactly how much space text will occupy.
DOM-based measurement is expensive. Every time you ask the browser "how tall is this paragraph at this width?", you trigger a layout reflow. Do this hundreds of times for a complex layout and the page becomes unusable. I needed a way to measure text that was fast enough to run speculatively β trying different layouts, different column widths, different break points β without blocking the main thread.
The missing piece
In 2025, Cheng Lou released pretext: a DOM-free text measurement library that is 300-600x faster than DOM measurement and pixel-perfect across Chrome, Safari, and Firefox. It uses canvas font metrics and pure arithmetic to compute text height and line breaks without ever touching the DOM.
With pretext, the core bottleneck disappears. You can measure thousands of text blocks in milliseconds. You can try ten different column configurations and pick the best one. You can run the full layout algorithm on every resize.
postext is what happens next.
pretext + postext
The naming is intentional.
pretext is what happens before text is placed: measuring how much space it will need. DOM-free, canvas-based, operating on pure numbers.
postext is what happens after measurement: the editorial decisions. Given exact dimensions for every text block at any width, the engine decides where each element goes β which column, at what position, flowing around which resources, obeying which typographic rules.
import { prepare, layout } from '@chenglou/pretext';
import { buildDocument } from 'postext';
// pretext: measure (the "pre" work)
const prepared = prepare(paragraphText, '16px/1.5 Inter');
const { height } = layout(prepared, columnWidth, 24);
// => "This paragraph is 144px tall at this column width."
// postext: decide (the "post" work)
const doc = buildDocument(content, config);
// => "Put this paragraph in column 2, starting at y=320.
// Move the image to the top of column 3.
// Float the table to the top of page 3.
// Break here to avoid a widow."
pretext gives you the measurements. postext gives you the layout.
pretext was created by Cheng Lou. postext builds on top of it.
Features
Everything below ships today in postext and its companion packages (postext-pdf, postext-epub, postext-scorm, postext-folio, postext-citeproc) and in the postext command line. A fuller list, with a link to the page that explains each part, is on the wiki's Features page.
Column-based layouts
- Single, double, and column-and-a-half layouts (
layoutType: 'single' | 'double' | 'oneAndHalf') with configurable gutter width, an optional column rule, mirrored margins, a page-wide baseline grid, and crop marks with bleed for print production. - The column-and-a-half layout can turn its side column into a channel for figures, tables and callouts (
sideColumnRole: 'floats'), placed at the outer edge of every page β the marginal column of a textbook β with side captions set level with their figure. :::columnsswitches the column count mid-page,:::pagebreakforces a break with parity control,:::spaceadds explicit vertical space, and:::paperchanges the paper stock for a run of pages.- Left- and right-bound books (
page.binding), with the PDF asking viewers for right-to-left spreads, and vertical writing (layout.writingMode: 'vertical-rl') for Chinese and Japanese books. - Headings span a column or the full page, with parity-aware page breaks (
'odd','even','always-odd', β¦) for chapter openings.
Column balancing
- Columns on a page end level: the engine closes each column's gap with the least visible lever β grid lines above headings, space after lists, room under top floats, and finally loosened or tightened paragraphs within word-spacing and tracking limits.
- Closing pages of a chapter balance too: the trailing band before a page-span box is levelled, short final columns are evened out, and a syllable is never stranded just to fill a column.
- Balancing converges chapter by chapter, so a chapter laid out alone and inside the whole book produce the same pages.
Optimal justification
- Knuth-Plass optimal line breaking β whole paragraphs are broken globally, not greedily line by line. A Rust build of the breaker, compiled to WebAssembly, answers where WebAssembly is available, with the same breaks as the TypeScript one.
- TeX-pattern hyphenation in 8 locales (English, Spanish, French, German, Italian, Portuguese, Catalan with the IEC rule for lΒ·l, Dutch); overlong words are divided by syllable, then by character.
- Orphan, widow, and runt control, plus keep-together rules (a heading with its first lines, a colon with the list it introduces).
Resources as first-class citizens
- Bitmaps, SVGs, tables and videos are declared once alongside the content and incorporated by inline
:ref{id="β¦"}references β the first reference floats the resource into the first free top or bottom band after it, spanning the column, the page, or the side channel. - Floats of one numbering sequence never overtake each other; figures can be fitted to the page, and an explicit
::resource{id="β¦"}embeds a resource inline. - A picture may carry a safe area, the region that must stay in view: the engine crops it down to that area or lets it grow back to the whole picture, so a photo fills a short column or keeps an inline embed inside its column.
- Videos (YouTube, Vimeo or your own MP4/WebM file) are placed, captioned and numbered like pictures (Video 1.1). Print shows the poster frame with a play mark and a QR code that opens the video, and the PDF links the poster; the HTML viewer and EPUB play the video with the book's player options (no download button, loop, mutedβ¦).
- Typed first-reference numbering:
FigureandTablebuilt-ins (localized per document language) plus custom resource types, with configurable reference styles and caption prefixes. - Tables taller than the page split between rows across as many pages as needed, repeating their header rows and a (cont.) caption; rowspans and group-head rows are never cut.
- Rotated tables (
placement.rotate), cell fills, cell images (bitmap or SVG, aligned within the cell), in-cell lists, and inline colour swatches for legends. diagramStyle.singleInkrecolors SVG diagrams to luminance-mapped tints of a single ink, so diagrams survive single-spot-colour printing.
Callouts, pull quotes and marginal material
:::calloutboxes with namedcalloutStyles: fill, frame, radius, title, icon marker column, and their own paragraph typography β sidebars, key concepts, activities, pull quotes.- Callouts can nest, float to the top or bottom of the page (
placement), sit in the side channel, and split across columns and pages when they cannot be kept together, with a continuation that drops the title and icon. - Named
paragraphStylesapplied with:::paragraphs{style="β¦"}for bibliographies, glossaries, notes and hanging-indent entries; tables and figures carry their own notes and source lines. :::verseblocks for poetry, including Arabic verse set as two hemistichs.- Inline chips,
:chip[text]{style="β¦"}: boxed words (word banks, keys, tags) that wrap as one unit, painted as real text in every backend.
Exercises
:::exerciseblocks hold the questions and their answers in the Markdown, in twelve kinds: choice, true or false, gaps and word banks, matching, sorting, ordering, scrambles, words to mark, open questions with ruled writing areas, word searches, crosswords, and operations in columns (long division set as each country's schools set it).exerciseStylesdecide how they print: the number badge, option labels, blanks, writing areas and activity icons. The solutions go after the exercise, upside down, at the end of the chapter or of the book, or nowhere, and the same text gives a teacher's edition with every answer filled in.- In the HTML viewer and in both EPUB renditions the exercises can be answered on screen (typing, ticking, dragging to join or sort, marking words) and checked. Limits for a test (
exercises.digital: checks per attempt, resets, when the solution shows, the pass mark) hold in the HTML, the EPUB and a SCORM package. See Exercises.
Composed pages
- Fixed pages (a textbook spread, a workbook page, a magazine feature) set from page templates (
pageTemplates): named frames, groups that share their space, a decoration and spreads, defined once in the configuration. - In the Markdown a
::::page{template="β¦"}fence sends each piece of content to a frame by name; positions and sizes never appear in the text, so a page is edited and translated as Markdown. A longer translation grows its box and pushes the boxes under it. - Repeating layouts are declared once: a grid lays one card per item and goes on by whole rows, and
:::moduleblocks stack bands of frames down the page, side by side or in rows, nested to any depth. Named frame looks (frameLooks) change a card's colours without a new style, and decoration can be drawn only on pages whose fence asks for it (when/unless). - Connectors join frames with vector lines that follow them (straight, elbow, tree), leaders run from a label to a point of the picture and end in a ring or a bar, and path elements draw bars, ribbons and chevrons as vector shapes in every output.
- A frame that overflows tries, in order, its free space, its group, smaller pictures and smaller text, then continues on another page; what still does not fit is reported with proposals. Words on a map or a diagram are labels written in the Markdown and placed at anchor points of the picture.
- The Sandbox edits templates and frame styles, checks every edition, and moves text typed into an older configuration back into the Markdown (
moveTextToMarkdown).
Comics
:::pagecuts a page into panels with a split expression (slanted gutters, bleeds, spreads); each panel takes a picture without lettering and its lines of script.- The engine letters the balloons, thoughts, captions and sound effects from that script in the book's language, so one set of pictures serves every edition. Strips (
:::strip) for newspapers, right-to-left manga and Arabic editions, and Kindle Panel View in the fixed-layout EPUB.
Notes, citations and indexes
- Footnotes (
[^id]) set at the foot of the column that cites them, or gathered at the end of the chapter; numbered per chapter, through the book, per page or per column, in decimal, roman or circled numbers (β ). - Cross-references to headings, figures, tables and anchors (
{#id}), clickable in the PDF. - Citations in Pandoc syntax (
[@garcia2020, p. 33]) formatted in a CSL style (APA, Chicago, MLA, IEEE, Vancouver, ISO 690, GB/T 7714, OSCOLA, β¦) with a generated bibliography, from BibTeX, CSL-JSON or CSL-YAML. With a note style each citation becomes a footnote. The styles are applied bypostext-citeproc. - A back-of-book index built from
:index[β¦]marks and printed with:::index: sub-entries, sort keys, principal pages in bold and several indexes per book. The page numbers are found after layout, so they follow the text.
Languages and scripts
- A document language (
locale) drives hyphenation, built-in strings, numerals, text direction and the language tagged in the PDF. - A book in several languages keeps general settings that every edition reads and, per language, an override with only the fields that language sets differently (the faces of its script, a label's wording), merged field by field (
configVersion21). - Chinese (Simplified, Traditional, Hong Kong), horizontal or vertical: line breaking, punctuation widths and hanging punctuation, HanβLatin spacing, the character grid, emphasis dots, proper-name and book-title marks, ruby (pinyin, zhuyin) and warichu. See Chinese layout.
- Arabic (Modern Standard and Classical): the bidirectional algorithm, right-bound books with right-to-left spreads, words shaped whole through HarfBuzz in the PDF, kashida justification instead of letter-spacing, vowel marks, Arabic-Indic digits and number words, and classical verse. See Arabic layout.
- Japanese, vertical or horizontal, by JLReq and JIS X 4051: kinsoku, yakumono spacing and paragraph-start brackets, vertical forms and tate-chΕ«-yoko, furigana (jukugo ruby, 1:2:1), sesame emphasis marks and side lines, kanbun kunten, Japanese counters (第δΈη« ), θ‘εγ headings and εδΈγ blocks, notes, a gojΕ«on index with readings, SIST 02 citations and Aozora Bunko sources. See Japanese layout.
Books, parts and front matter
- Books laid out one chapter at a time with an engine
continuation: page numbers, parity, heading and figure counters and open parts carry across chapters, or the whole book is built as one document. :::partdivider pages with their own design, and part palettes that recolour the design and flow of every chapter in the part.- Named
headingStylesthat govern a whole section β running heads, page geometry, body typography, palette β so front matter can live beside decimal-numbered chapters in one configuration. :::tocprints a table of contents fed by the book's outline, with leaders, numbers and page labels.
Styling configuration
tableStyle/tableStylesβ table typography independent of body text, named variants picked withtable.styleId, and a rounded outer frame.captionStyle,chipStyles,calloutStyles,paragraphStyles,headingStylesβ every style is a named, reusable object.- Color palettes let a whole document re-ink from one place.
figureStylesets the figures of each text style: old-style for running prose, lining for headings, tabular for table columns, proportional where they read best. The engine applies the font's OpenType features in the canvas, HTML, PDF, EPUB and command-line outputs; a face without them keeps its own figures.- Design text and chips can be set as dotted letters for a child to trace, and repeated along a line β the tracing copies of a handwriting workbook stay live text.
Math
- LaTeX math, inline (
$β¦$) and display ($$β¦$$), rendered to crisp SVG via MathJax in every backend, loaded on demand (await initMathEngine()). MathJax ships pre-bundled, so it loads the same from a bundler, Node or a CDN.
Music notation
- Music is written as text in the chapter, in a
```abc,```musicxml,```meior```humdrumfence, and engraved by Verovio at the width of its column. A long score is cut into systems that run on over columns and pages like lines of text, andscoreStyle.minSystemsAtBreaknever leaves a system alone at a break. - Music examples the text cites are
scoreresources, numbered, captioned, floated and referenced like figures (Example 3.2), from inline notation or a MusicXML file exported from Dorico, Sibelius, Finale or MuseScore. scoreStyleand namedscoreStylesset the staff size, the music font (Bravura, Leland, Petaluma, Leipzig, Gootville), the lyrics font, spacing, bar numbers and ink.- Scores are vector paths in the Canvas, HTML and PDF (a tagged
Figurewith its alt text), inline SVG in a reflowable EPUB, and their notation fence in Word. The engraver loads only for books that have music. See Music notation.
Headers & footers
- Design slots composed of text, rule, box, and image elements with precise placement, painted in array order and bounded to the trim box.
- Placeholders resolve page numbers, chapter numbers and titles, part titles, and document
