Announcing Sone 1.4 — multi-page PDFs, balanced wrap, and the Metadata API
Sone 1.4 ships first-class multi-page PDF rendering, balanced line wrapping for headlines, and a Metadata API that exports every laid-out node and segment as YOLO or COCO datasets.
It has been a busy quarter. Sone now ships multi-page PDFs as a first-class feature: pass pageHeight to the render config and your node tree is sliced into pages automatically. Headers and footers are ordinary nodes — pass them inline, or as a function that receives { pageNumber, totalPages } for dynamic page chrome.
The PageBreak() node forces a break exactly where you want one, and node.pageBreak("avoid") hints the layout engine to keep a section together when there is room. Margins, last-page trimming, and per-page headers behave as you would expect from a real document engine.
Balanced line wrapping
Default text wrapping is greedy: each line takes as many words as fit, leaving the last line short and ragged. For headings, pull-quotes, and card titles, that looks awkward. .textWrap("balance") shrinks the effective break-width until all lines come out roughly equal, then sizes the node to the balanced content width — so it composes naturally inside a flex container.
Pair it with .hyphenate("en") for narrow-column body copy and you get tight, even-looking paragraphs without manual line breaks.
The Metadata API
Sone now exposes the computed layout of every node, line, and text segment. Call sone(root).canvasWithMetadata() and you get a tree with x, y, width, height and tag labels for every leaf. Useful for hit-testing, debug overlays, and post-processing.
The headline use-case is dataset generation. Tag any node with .tag("title") or .tag("invoice-number"), then pass the metadata to toYoloDataset() or toCocoDataset(). Out the other end you get bounding-box annotations matching your image — perfect for training Document Layout Analysis models on synthetic, perfectly-labeled data.
Bidirectional text and hyphenation
Bidirectional text now follows the Unicode Bidirectional Algorithm out of the box. Mixed-direction paragraphs (an LTR sentence with an inline RTL number) reorder correctly without any manual intervention. Override per-paragraph with .baseDir() on Text or per-span with .textDir() on Span.
Hyphenation now supports 80+ languages via Knuth–Liang patterns from the hyphen package. Pass .hyphenate("de") for German compound words, .hyphenate("fr") for French — most BCP-47-like locales work out of the box.
What's next
1.5 will focus on browser parity: a fully working SoneRenderer implementation backed by the standard Canvas API, so you can run Sone client-side for live preview UIs without wrapping your own platform shim.
If you are already using Sone in production, drop a note in the GitHub repo — we are collecting case studies and would love to feature yours.