canvas-hypertxt

A zero-dependency library for laying out and wrapping text on an HTML5 canvas at high speed.

Library
npm
v1.0.3
137stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
37/100Needs Attention
Development Activity0
Maintenance20
Community52
Maturity56
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
65/100Good
Architecture65
Code Quality62
Innovation78
Learning Curve55

canvas-hypertxt is a featherweight, zero-dependency TypeScript library that computes line-wrapped text layout for the HTML5 Canvas API. Rather than rendering text for you, it focuses purely on the layout step — figuring out where lines should break for a given font, width, and string — and hands the resulting lines back so your own rendering pipeline can draw, align, or animate them however it needs to.

Its headline feature is “hyper wrapping”: once a font has accumulated enough measured samples, the library trains a lightweight per-character width model and stops calling the comparatively expensive ctx.measureText on every guess, instead estimating line breaks from the trained weights. This trades a small, bounded accuracy margin for dramatic speedups on repeated wraps of the same font — the library’s own benchmarks show it running many times faster than comparable canvas text libraries at longer string lengths, and even faster again once hyper wrapping kicks in.

canvas-hypertxt was extracted from Glide’s own glide-data-grid project, where it handles cell text wrapping at scale, and is now published as a standalone package for anyone building canvas-based grids, editors, or custom text renderers who needs fast, dependency-free line-break calculation.

What You Get

  • A single split() function that returns wrapped lines for a given canvas context, string, font, and width
  • Optional break-opportunity callback support for wrapping non-space-delimited text (e.g. via the linebreak package for CJK or hyphenated content)
  • A clearCache() function to reset internal measurement caches, useful after fonts finish loading and metrics change
  • “Hyper wrapping” mode that trains a per-character width model per font and skips ctx.measureText calls once enough samples are collected, for a large performance gain on repeated wraps
  • Zero runtime dependencies and dual CJS/ESM builds with bundled TypeScript type declarations

Common Use Cases

  • Canvas-based data grids - wrapping cell text to a fixed column width without relying on DOM/CSS text layout, as used internally by glide-data-grid
  • Custom canvas text editors or WYSIWYG surfaces - computing line breaks so the app can control exactly how and where text is rendered, including custom alignment
  • Game UI and HUD text rendering - laying out dialogue or label text on a canvas at high frame rates where DOM-based wrapping would be too slow
  • Chart and visualization labels - wrapping axis labels, tooltips, or annotations drawn directly to canvas rather than as HTML overlays

Under The Hood

Architecture The library is structured around two files: src/index.ts, a thin re-export barrel exposing split and clearCache, and src/multi-line.ts, which holds all core logic. State lives in module-level Maps — a memoized result cache keyed by text+font+width, a per-font average-character-width table, and per-font hyper-wrapping weight maps — making the library a singleton with implicit shared state rather than an injectable instance. The single entry point checks the cache, splits input on newlines, and for overflowing lines repeatedly computes a split point by guessing an index from a width ratio, then walking forward or backward while re-measuring until the guess fits, finally snapping to the nearest word boundary or a caller-supplied break-opportunity list. There is no abstraction over CanvasRenderingContext2D — it is passed directly into nearly every function — so the library is tightly coupled to that one browser API surface.

Tech Stack canvas-hypertxt ships with zero runtime dependencies, matching its “featherweight” claim. It is authored in strict TypeScript targeting ES6, built with esbuild into both a minified CommonJS bundle and an ESM bundle, with a separate tsc pass emitting type declarations, all wired together through the package’s exports map for dual CJS/ESM/type resolution. Development tooling includes Jest 28 with ts-jest and jest-environment-jsdom plus jest-canvas-mock to exercise canvas APIs under Node, Storybook 6 for benchmark demos, and an extensive ESLint configuration (typescript-eslint, react, import, sonarjs, unicorn). It has no server, database, or framework dependency — it is a leaf utility meant to be embedded in larger canvas-rendering projects such as Glide’s own glide-data-grid.

Code Quality Testing lives in a single file, test/multi-line.test.tsx, covering short strings, long word-wrapped strings, custom break-opportunity callbacks, zero-width edge cases, explicit newlines, and a dedicated hyper-wrapping test that runs one million iterations to force the trained fast path — thorough for the public entry point, though internal helpers like the weight-adjustment function and the split-point search are exercised only indirectly. Error handling favors defensive early returns (an empty array for non-positive widths, a sentinel value when a line already fits) over thrown exceptions, with no explicit input validation. Types are strict (strict: true, noImplicitAny) with readonly return types signaling immutability intent, and naming is clear and domain-specific throughout.

API Design The public surface is deliberately minimal — two functions, split and clearCache — letting a consumer get a working canvas-wrapping call running in under ten lines, as the README demonstrates. This narrowness is itself the library’s design bet: unlike libraries that also render text for you, canvas-hypertxt returns only line breaks and leaves drawing, alignment, and positioning to the caller, trading convenience for composability. Its standout technical idea, “hyper wrapping,” switches a font from per-guess measureText calls to a self-trained per-character width model once enough samples accumulate, estimating breaks with near-zero marginal measurement cost at a small, bounded accuracy trade-off — a genuinely narrow but effective optimization for this specific problem. The README also explicitly documents what is out of scope (text justification, debug rendering, managed line-height handling), which communicates the API boundary clearly rather than leaving gaps implicit.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search