0002. Layout is DOM-free; text measurement is injected
- Status: Accepted
- Date: 2026-10-01 (recorded with the initial engine and
@nextgensoftwares/folio-fonts, commits13a251b,20dc183)
Context
The previous paginator measured DOM nodes and moved them between page containers. Pagination then depended on the browser's text engine, zoom, font loading and timing; the PDF (rendered by a headless browser) never quite matched the screen; and large documents were slow because every measurement forced style and layout. We want one layout that drives the editor view, the PDF and DOCX decisions, that runs in Node and in workers, and that is reproducible.
Decision
@nextgensoftwares/folio-model, @nextgensoftwares/folio-layout, @nextgensoftwares/folio-formatter and the export writers compile with lib: ["ES2022"] only: no DOM types, no DOM calls. Layout takes an injected TextMeasurer (width, optional metrics and positions); math and media sizes are injected the same way (MathMeasurer, MediaSizer). Production measurement is HarfBuzz (WASM) over the actual font files (@nextgensoftwares/folio-fonts), with CSS-style face matching and per-grapheme fallback. The browser's own text metrics are never used for layout decisions. Only @nextgensoftwares/folio-editor and @nextgensoftwares/folio-react may touch the DOM.
Consequences
- Layout is deterministic and portable: the same document, theme and fonts give the same pages in the browser, a worker, Node (PDF export, server-side page counts) and tests.
- Tests use a trivial fixed-advance measurer, so pagination tests have round numbers and run fast.
- Fidelity against browsers is measured, not assumed:
tools/chrome-compare.mjsshows 205/206 blocks breaking identically to Chrome, heights within 0.01 px. - Hosts must ship real font files (not just CSS stacks) and register the same bytes with the browser for drawing.
- Math typesetting stays a host concern (KaTeX in the browser, or any server-side typesetter), behind the
MathMeasurerinterface. - The TypeScript configuration enforces the rule: a DOM call in a core package fails to compile.