npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

canvas-txt

v5.0.2

Published

Render multiline textboxes in HTML5 canvas with auto line breaks and better alignment system

Readme

Canvas Txt 📐

Text boxes for HTML5 canvas. Draw a box, hand it a string — wrapping, alignment, clipping and justification in one call.


import { drawText } from 'canvas-txt'

drawText(ctx, 'Lorem ipsum dolor sit amet', {
  x: 100, y: 200, width: 200, height: 200,
  fontSize: 24,
})

That's the whole integration. Zero dependencies, ~4 kB gzipped, works in browsers, workers (OffscreenCanvas), node-canvas and skia-canvas.

[!TIP] Upgrading from v4? Most code keeps working — read the migration guide for what changed and what's new.

Why canvas-txt

| | | | --- | --- | | 🌍 Unicode-correct wrapping | Line breaks come from Intl.Segmenter — CJK and Thai break at real word boundaries, emoji and combining marks never split mid-codepoint | | 📐 Full box model | Horizontal + vertical alignment, true justification (no injected characters), overflow: hidden / ellipsis, maxLines | | ⚡ Fast by default | Per-font width caches and memoized layouts — faster than canvas-hypertxt on every workload, with exact measuring | | 🎨 Layout / paint split | layoutText() once, drawTextLayout() every frame — zero re-measuring in render loops, hit-testable line positions | | 🔌 Runs on any 2D context | Typed against a minimal TextContext — no casts for node-canvas, skia-canvas or OffscreenCanvas | | 🐛 Debug mode | Draws the box and alignment guides so you can see the layout |

Install

npm i canvas-txt

Or from a CDN — the global window.canvasTxt carries the same functions:

<script src="//unpkg.com/canvas-txt"></script>

Usage

Draw once

import { drawText } from 'canvas-txt'

const ctx = document.getElementById('myCanvas').getContext('2d')

const { height, width, lines } = drawText(ctx, 'Hello world', {
  x: 100,
  y: 200,
  width: 200,
  height: 200,
  fontSize: 24,
})

Try it live in this JSFiddle.

Layout once, paint forever

drawText is a convenience wrapper. For anything that redraws — animation loops, canvas editors, data grids — lay out once and paint the cached layout:

import { layoutText, drawTextLayout } from 'canvas-txt'

const layout = layoutText(ctx, text, { width: 300, height: 200, fontSize: 18 })

// every frame, as often as you like — no re-measuring:
drawTextLayout(ctx, layout, { x: 50, y: 40 })
drawTextLayout(ctx, layout, { x: 400, y: 40, style: { fill: '#888' } })

// layout.lines → [{ text, x, y, width, words }] — hit-test it, decorate it
// layout.height, layout.width, layout.clipped

Interactive text boxes

If you want the box in the image above — draggable anchors that control x/y/width/height — that interaction layer is yours to build, and it's small: keep the box rectangle in your own state, hit-test pointer events against its corners and edges, update the state on drag, and re-render (draw your selection chrome, then drawText, or repaint a cached layoutText result — layout.lines gives you per-line positions if you need finer hit-testing). This JSFiddle is a complete working example — drag, resize from any handle, live re-wrap — in about 100 lines, and our playground (source) uses the same approach. This is deliberately not part of the library: canvas-txt only handles the rendering, because rendering is the one piece that's identical everywhere — pointer handling belongs to your app's event model, whether that's React state, a Vue component, or an immediate-mode game loop. Staying out of it is what lets canvas-txt drop into any of them.

Node

Works with node-canvas and skia-canvas directly — no type casts needed:

import { createCanvas } from 'canvas'
import { drawText } from 'canvas-txt'
import * as fs from 'node:fs'

const canvas = createCanvas(400, 400)
const ctx = canvas.getContext('2d')

drawText(ctx, 'Hello World!', {
  x: 100, y: 200, width: 200, height: 200, fontSize: 24,
})

fs.writeFileSync('output.png', canvas.toBuffer('image/png'))

Colors and stroke

Without style, text uses the context's current fillStyle:

ctx.fillStyle = '#ff0000'
drawText(ctx, 'Red text', { x: 0, y: 0, width: 200, height: 200 })

// or per call, with an optional outline:
drawText(ctx, 'Outlined', {
  x: 0, y: 0, width: 200, height: 200,
  style: { fill: '#fff', stroke: '#0C8CE9', strokeWidth: 2 },
})

API

Config

| Property | Default | Description | | :--------------: | :----------: | :------------------------------------------------------------------------------------------------------------------- | | width | required | Width of the text box | | height | required | Height of the text box | | x | required | X position of the text box | | y | required | Y position of the text box | | align | center | Horizontal align: left, center, right | | vAlign | middle | Vertical align: top, middle, bottom | | font | Arial | Font family | | fontSize | 14 | Font size in px | | fontStyle | '' | Same as CSS font-style: italic, oblique 40deg, … | | fontVariant | '' | Same as CSS font-variant: small-caps, … | | fontWeight | '' | Same as CSS font-weight: bold, 100, … | | lineHeight | font's own | Line height in px; defaults to the font's natural line height | | letterSpacing | '' | Same as CSS letter-spacing, e.g. '2px' (where the canvas supports it) | | direction | ltr | Set 'rtl' for right-to-left text (where the canvas supports it) | | justify | false | Stretch soft-wrapped lines to the full box width — words are positioned individually, no characters inserted | | overflow | visible | Past the box height: visible draws everything, hidden clips lines, ellipsis clips and appends | | maxLines | — | Hard cap on line count, independent of box height. Combine with overflow: 'ellipsis' for a marker | | style | — | Per-call colors: { fill, stroke, strokeWidth }; defaults to the context's current fillStyle | | cache | true | Reuse widths from the shared per-font cache. Set false for exact per-call measuring with no shared state | | debug | false | Draw the box and alignment guides |

Methods

import {
  drawText, layoutText, drawTextLayout,
  measureText, splitText, getTextHeight,
  clearMeasurementCache,
} from 'canvas-txt'

| Method | Description | | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | | drawText(ctx, text, config) | Lay out and draw in one call → { height, width, lines } | | layoutText(ctx, text, config) | Layout only → TextLayout with positioned lines ({ text, x, y, width, words }), total size, clipped flag | | drawTextLayout(ctx, layout, { x, y }) | Paint a cached TextLayout — repaint freely without re-measuring | | measureText(ctx, text, config) | Dimensions only → { lines, width, height } | | splitText({ ctx, text, width }) | Just the wrapped lines as string[], using the context's current font | | getTextHeight({ ctx, text, style }) | Height of text under a CSS font string, from real font metrics | | clearMeasurementCache() | Flush the shared width cache — call after web fonts finish loading |

Performance

Segment widths live in a module-level per-font cache and identical layouts are memoized (bounded, ~500 entries), so steady-state layout is arithmetic and lookups — not measureText calls. Memoized layouts come back frozen; treat them as immutable.

Measured against canvas-hypertxt's standard mode — 5000 iterations, 600-char strings, node-canvas, Apple Silicon (benchmark in packages/canvas-txt/bench):

| Workload | canvas-txt v5 | canvas-hypertxt | canvas-txt v4 | | ----------------------- | ------------: | --------------: | ------------: | | Same text repeated | 1.1 µs/op | 0.9 µs/op | 1,566 µs/op | | 500 strings cycled | 5.6 µs/op | 48.3 µs/op | 1,423 µs/op | | Never-repeating strings | 51.3 µs/op| 566.2 µs/op | 1,382 µs/op |

Only hypertxt's estimation mode ("hyper wrapping", which guesses widths instead of measuring) is faster on long never-before-seen strings — with exact measuring, canvas-txt is the faster library while staying Unicode-correct.

[!IMPORTANT] Loading a web font changes glyph widths. Call clearMeasurementCache() when fonts finish loading:

document.fonts.ready.then(() => clearMeasurementCache())

Contributing

The repo is a pnpm workspace: the library lives in packages/canvas-txt, the playground in apps/docs.

pnpm install
pnpm dev        # playground with the library hot-reloading
pnpm test       # vitest against real canvas metrics (node-canvas)
pnpm build      # esm + cjs + iife + d.ts

License

MIT © Geon George