Zero-allocation text layout engine for the web. Sub-microsecond cached layouts, 5KB core.
Live Demo — interactive pipeline visualization with real ported algorithms, benchmark runner, and 62 browser tests.
Canvas-based text layout allocates on every frame. DOM-based layout triggers reflow. ZeroText does neither.
- Zero allocation -- arena pooling and pre-allocated typed arrays eliminate GC pauses entirely
- O(1) glyph lookup -- perfect hash table with ASCII fast path (codepoints 0-127 cached without hashing)
- O(log n) line breaking -- prefix-sum binary search over a DFA-driven break table (UAX#14)
- Sub-microsecond hot path -- LRU cache keyed by numeric FNV-1a hashes (no string allocation on lookup)
- WASM SIMD + WebGPU -- optional acceleration paths for batch workloads
| Metric | Target | Actual |
|---|---|---|
| Bundle size | <8 KB | ~5-6 KB |
| Initial layout (cold) | <8 us | 5.6 us |
| Hot layout (cached) | <1 us | 0.1 us (100 ns) |
| GC pauses | 0 | 0 |
Run locally: npx tsx benchmarks/features.ts
| # | Feature | Status | Algorithm |
|---|---|---|---|
| 1 | Line breaking | Done | UAX#14 DFA + prefix-sum binary search |
| 2 | Glyph lookup | Done | Perfect hash table, O(1) + ASCII cache |
| 3 | Arena memory | Done | Pool allocator, generational buffers |
| 4 | LRU cache | Done | Doubly-linked list + Map, FNV-1a keys |
| 5 | Bidi/RTL | Done | UAX#9 stack-based embedding levels |
| 6 | Ligatures | Done | Flat trie substitution (fi, fl, ff, ffi, ffl) |
| 7 | Kerning | Done | Pair-wise FNV-1a hash table |
| 8 | Hyphenation | Done | Liang algorithm + soft-hyphen insertion |
| 9 | Alignment | Done | Left, center, right, justify |
| 10 | Truncation | Done | End, middle, start with ellipsis |
| 11 | Decoration | Done | SoA bitflags (underline, strikethrough, overline) |
| 12 | Vertical writing | Done | vertical-rl, vertical-lr coordinate transform |
| 13 | Hit testing | Done | Binary search over lines and spans |
npm install @zerotext/coreimport{createEngine}from"@zerotext/core";importtype{GlyphEntry}from"@zerotext/core";constglyphs: GlyphEntry[]=[{codepoint: 72,width: 8},// H{codepoint: 101,width: 6},// e{codepoint: 108,width: 3},// l{codepoint: 111,width: 7},// o// ...];constengine=createEngine({});constprepared=engine.prepare("Hello world",glyphs);constlayout=engine.update(prepared,200);// 200px container widthconsole.log(layout.lines.length,layout.height);import{createEngine,createGlyphTable}from"@zerotext/core";constengine=createEngine({enableBidi: true,enableLigatures: true,enableHyphenation: true,textAlign: 1,// CenterwritingMode: 0,// Horizontaltruncate: 0,// NonemaxLines: 0,// Unlimited});constglyphTable=createGlyphTable(glyphs);consttext=newUint32Array([/* codepoints */]);constresult=engine.layoutFull({
glyphTable,
text,width: 300,lineHeight: 20,});constcaret=engine.hitTest(mouseX,mouseY,layout);console.log(caret.offset,caret.lineIndex);constrects=engine.getSelectionRects({start: 0,end: 10},layout,);import{ZeroTextProvider,useZeroText}from"@zerotext/react";importtype{GlyphEntry}from"@zerotext/core";functionApp(){return(<ZeroTextProviderconfig={{enableLigatures: true}}><TextBlocktext="Hello world"/></ZeroTextProvider>);}functionTextBlock({ text }: {text: string}){constglyphs: GlyphEntry[]=[/* glyph entries */];const{ ref, layout, isReady }=useZeroText(text,{glyphEntries: glyphs,lineHeight: 20,});return<divref={ref}>{isReady&&<span>{layout!.lines.length} lines</span>}</div>;}| Package | Description |
|---|---|
@zerotext/core | Layout engine, arena, cache, all text features |
@zerotext/compiler | Ahead-of-time font subset + ZTB binary format |
@zerotext/flow | Text wrapping around obstacles |
@zerotext/react | React bindings (useZeroText, ZeroTextProvider) |
@zerotext/vue | Vue composable |
@zerotext/svelte | Svelte action |
@zerotext/wasm | WASM SIMD acceleration (Rust) |
@zerotext/webgpu | WebGPU compute shader batch layout |
Build plugins: @zerotext/vite, @zerotext/webpack, @zerotext/rollup
Text input
|
v
Arena (pre-allocated typed arrays, zero GC)
|
v
PrefixSum (cumulative glyph widths)
|
v
DFA (UAX#14 line break classification)
|
v
BinarySearch (O(log n) break point selection)
|
v
Cache (LRU, FNV-1a numeric keys, O(1) hot path)
|
v
LayoutResult (lines, spans, positions)
Optional pipeline stages (bidi, ligatures, hyphenation, alignment, truncation, decoration, vertical transform) are applied in engine.layoutFull().
See CONTRIBUTING.md for setup, testing, and coding guidelines.
Apache-2.0. See LICENSE.