uWrap
A 10x faster, kerning-aware Canvas2D text wrapping library in under 2KB minified.
Repository Health
Technical Analysis
uwrap is a tiny, dependency-free JavaScript/TypeScript library that predicts how text will wrap inside a Canvas2D context, solving a gap in the Canvas API, which offers no native text-wrapping support and only an expensive measureText() call. It precomputes per-character widths and kerning-adjusted pairs up front, then scans text in a single forward pass to compute wrap points, line counts, and split lines without repeated measurement calls.
Built specifically for list and grid virtualization, a common UI performance technique for rendering large scrollable datasets, uwrap accounts for font size, variable-width kerning, letter-spacing, and explicit line breaks. It benchmarks roughly 10x faster and more accurate than canvas-hypertxt, the existing alternative, across Chrome, Firefox, and Safari, while shipping in under 2KB minified.
What You Get
- varPreLine(ctx) - a single factory function that binds to your Canvas2D context and returns count/test/split/each methods
- Precomputed kerning LUT - upfront calculation of per-character and kerning-adjusted pair widths for O(1) width lookups during wrapping
- Zero runtime dependencies - ships as a ~2KB minified ESM/IIFE bundle with full TypeScript definitions
- Safari letter-spacing polyfill - manual compensation for Safari’s lack of Canvas letterSpacing support pre-18.4
Common Use Cases
- Virtualized list row heights - compute exact row height for each item before rendering by counting wrapped lines
- Canvas-based text editors or renderers - determine wrap points for custom text layout engines built on Canvas2D
- Grid/table cell truncation - test whether text will overflow a cell width before rendering
- Chat or feed UIs with variable-height rows - predict multi-line message heights for smooth virtualized scrolling
Under The Hood
Architecture The library is a single TypeScript file (src/uWrap.ts) exporting one function, varPreLine(ctx), which closes over a Canvas2D rendering context and returns a small object of methods (each, split, count, test) that all share the same precomputed width/kerning lookup tables. On first call it builds a per-character width table and a kerning-pair lookup table for uppercase-letter combinations, then each() performs a single forward scan over the input string tracking head/tail pointers to find wrap points without backtracking; split, count, and test are thin wrappers around each() with different callback behavior. This closure-based design tightly couples the returned API to one font/style configuration, so a new instance is expected per distinct ctx.font setting; if the core each() scanning logic changes, all four public methods are affected simultaneously since they share it.
Tech Stack Written in strict-mode TypeScript (ES2022 target, ESNext modules) with no runtime dependencies. Rollup, using @rollup/plugin-typescript and @rollup/plugin-terser, compiles the source into an ESM build (dist/uWrap.mjs) and two IIFE builds (a readable dist/uWrap.iife.js and a minified dist/uWrap.iife.min.js), with type definitions generated alongside. Dev-only dependencies are rollup, tslib, and skia-canvas, the last used exclusively in tests to emulate a real Canvas2D context under Node rather than in the browser.
Code Quality Tests live in test/uWrap.test.mjs and use Node’s built-in test runner (node:test, node:assert/strict) against the compiled dist output, rendered through skia-canvas with a real embedded font, covering wrapping edge cases such as leading/trailing whitespace, explicit newlines, and hyphen-based breaks. That choice, testing against actual rendered glyph metrics rather than mocked measurements, is a notably rigorous approach for a measurement-dependent library. Naming inside the hot loop is intentionally terse (single-letter variables) trading readability for performance; TypeScript strict mode with noUnusedLocals/noUnusedParameters catches quality issues at compile time, but no linter or CI configuration is present in the repo.
What Makes It Unique uWrap’s core innovation is precomputing a kerning- and spacing-aware character-width lookup table so that wrap-width calculations become near-constant-time per character instead of repeated, expensive ctx.measureText() calls. This targets a specific, narrow gap: Canvas2D has no native text-wrapping API, and the README’s own benchmarks show it substantially outperforming canvas-hypertxt, the closest existing alternative, across major browser engines while remaining more accurate for kerned, variable-width fonts.