Normal vs pre-wrap
A single option on prepare() decides whether whitespace is a compositor or a carrier. The default, whiteSpace: 'normal', collapses runs of spaces, tabs, and newlines into single word-breaks — the same rule you've been using in every prose lesson so far. Flip it to 'pre-wrap' and those same characters survive as visible structure: indents, blank lines, poetic spacing.
Same text, same font, same width. Two panes. Drag the shared width slider.
Mechanism
During prepare(), Pretext runs a normalization pass over the input before segmenting it. Under the default whiteSpace: 'normal', any run of ASCII whitespace (" ", \t, \n) collapses to a single break opportunity — the CSS white-space: normal rule every browser applies to body text.
Under whiteSpace: 'pre-wrap', the normalization pass stops collapsing. The engine now distinguishes preserved spaces, tabs, and hard breaks as separate segment kinds. A preserved space inside a long run still participates in wrapping, but it also occupies visible width. A tab advances to the next tab stop (default tab-size: 8). A \n forces a new line regardless of width.
Handles produced in each mode are not interchangeable — the segmentation is different — so you pick the mode at prepare() time, not layout() time.
Application
One layout engine, two registers. The same call site that fits your prose paragraphs can also fit:
- A poem where indentation is meaning — Whitman's breath-ruled lines, Dickinson's dashes, a haiku spaced on the page the way it was written.
- A
<textarea>-like field whose height you need to know before insertion, with the user's literal spaces and line breaks intact. - A code block that wraps at the column but preserves every indent.
- ASCII art, diffs, log output — anything where the whitespace is the diagram.
No flag, no extra CSS, no branching pipeline. One option.
"A noiseless patient spider,
I mark'd where on a little promontory it stood isolated,
Mark'd how to explore the vacant vast surrounding,
It launch'd forth filament, filament, filament, out of itself,
Ever unreeling them, ever tirelessly speeding them."
Direct Claude
prepare() — whiteSpace: 'normal'
"preserve the layout as typed"
→
prepare(text, font, { whiteSpace: 'pre-wrap' })
"measure the textarea before it mounts"
→
pre-wrap handle + layout() at the target width
"render verse and prose through the same engine"
→
one call site, pick the mode per content type
import { prepare, layout } from '@chenglou/pretext';
const font = "400 20px 'EB Garamond', serif";
const lh = 20 * 1.55;
// Normal: runs of whitespace collapse to single break opportunities.
const normalHandle = prepare(verse, font);
// Pre-wrap: tabs, newlines, and repeated spaces survive as visible structure.
const prewrapHandle = prepare(verse, font, { whiteSpace: 'pre-wrap' });
// layout() is the same in either case — pure arithmetic on the cached widths.
const a = layout(normalHandle, width, lh);
const b = layout(prewrapHandle, width, lh);