Skip to main content
Benchmark snapshots are captured in benchmarks/chrome.json and benchmarks/safari.json. The tables below reflect the snapshot taken on 2026-03-28.
layout() is the resize hot path — call it on every container width change. prepare() is the one-time cost paid when text first appears. Optimizing resize performance means keeping layout() fast; prepare() budget is spent once per text block.

Top-level batch

Numbers over the shared 500-text batch corpus.
Browserprepare()layout()DOM batchDOM interleaved
Chrome18.85ms0.09ms4.05ms43.50ms
Safari18.00ms0.12ms87.00ms149.00ms
The DOM columns show how much equivalent DOM measurement costs for comparison: batch reads are cheaper than interleaved reads, but both are far slower than layout().

Rich line APIs

Shared corpus

BrowserlayoutWithLines()walkLineRanges()layoutNextLine()
Chrome0.05ms0.03ms0.07ms
Safari0.05ms0.03ms0.07ms

Arabic long-form stress

Numbers under heavy load using the Arabic long-form corpus.
BrowserlayoutWithLines()walkLineRanges()layoutNextLine()
Chrome4.99ms2.17ms6.39ms
Safari4.66ms2.37ms5.53ms

Long-form corpus stress

Chrome baseline. Each row is a single long-form text prepared and laid out at 300px width. prepare() is split into its two internal phases — analyze() for segmentation and glue work, measure() for canvas width measurement — so you can see which script is expensive for which reason.
Corpusanalyze()measure()prepare()layout()segs (analyze→prepared)lines @ 300px
Japanese prose (story 2)1.90ms4.00ms6.10ms0.02ms1,773→2,667193
Japanese prose4.10ms8.30ms12.60ms0.03ms3,606→5,044380
Korean prose2.40ms8.40ms11.40ms0.04ms5,282→9,679428
Chinese prose6.10ms13.10ms19.20ms0.05ms5,433→7,949626
Chinese prose (story 2)3.70ms8.10ms11.80ms0.03ms3,271→4,745375
Thai prose8.10ms5.40ms13.50ms0.05ms10,281→10,2811,024
Myanmar prose0.60ms0.70ms1.30ms<0.01ms797→79781
Myanmar prose (story 2)0.30ms0.60ms0.90ms<0.01ms498→49854
Urdu prose2.20ms3.30ms5.50ms0.03ms6,051→6,051351
Khmer prose5.80ms4.50ms10.40ms0.05ms11,109→11,109591
Hindi prose4.30ms6.60ms11.10ms0.04ms9,958→9,958653
Arabic prose29.10ms34.80ms63.50ms0.17ms37,603→37,6032,643
The segs column shows segment count going from the analysis phase into the prepared handle. Scripts like CJK expand their segment count because words are split into individual graphemes for per-character line breaking. Scripts like Thai, Myanmar, Urdu, Khmer, Hindi, and Arabic do not expand because they do not require that splitting.

Notes

  • Chrome is the main maintained performance baseline. Safari snapshots are included but are noisier and warm up less predictably.
  • The checked-in JSON snapshots are cold checker runs. Ad hoc page-driven numbers, especially in Safari, can differ significantly after warmup.
  • Refresh benchmarks/chrome.json and benchmarks/safari.json when a change affects the text engine hot path (src/analysis.ts, src/measurement.ts, src/line-break.ts, src/layout.ts, src/bidi.ts, or pages/benchmark.ts).

Build docs developers (and LLMs) love