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

@zakkster/lite-text-layout

v1.0.1

Published

Zero-GC bitmap font word wrapper. Computes line breaks, kerning-aware widths, and truncation/ellipsis flags into a Float32Array — feeds directly into @zakkster/lite-bmfont's drawWrapped.

Readme

@zakkster/lite-text-layout

npm version npm bundle size npm downloads npm total downloads TypeScript Dependencies License: MIT

📐 What is lite-text-layout?

@zakkster/lite-text-layout is a zero-allocation word wrapper for ASCII bitmap fonts. It walks a string once, computes soft/hard line breaks, kerning-aware widths, and an optional ellipsis-on-overflow flag — then writes everything into a caller-owned Float32Array.

The output is exactly the layout buffer that @zakkster/lite-bmfont's BitmapFont.drawWrapped consumes. Compute layout once, re-render every frame for free.

It gives you:

  • 📦 Soft-wrap at spaces, hard-break inside long words
  • 📏 Per-line pixel widths, kerning-aware (uses the same 64K LUT as the renderer)
  • 📃 Multi-line via \n characters
  • ✂️ Optional truncation with ellipsis flag when content overflows boxHeight
  • 🧹 Zero allocation — single pass over the string, all state in primitives
  • 🪶 ~0.6 KB gzipped, no dependencies

Supports ASCII characters 0–255. Non-ASCII chars contribute zero advance and reset the kerning context (same convention as BitmapFont.draw).

Part of the @zakkster/lite-* ecosystem — micro-libraries built for deterministic, cache-friendly game development.

🚀 Install

npm i @zakkster/lite-text-layout

Pair with the renderer (separate package):

npm i @zakkster/lite-bmfont @zakkster/lite-text-layout

🕹️ Quick Start

import { BitmapFont } from '@zakkster/lite-bmfont';
import { TextLayout } from '@zakkster/lite-text-layout';

const font = new BitmapFont(atlasImage, fontJson);

// Pre-allocate a layout buffer once. 16 lines = 64 floats.
const layout = new Float32Array(64);

// Each frame: layout is virtually free once computed. Re-compute only when
// the text or box dimensions change.
const lineCount = TextLayout.computeWrap(
    'Hello there, traveller!\nWelcome to the inn.',
    font,
    /* boxWidth  */ 200,
    /* boxHeight */ 80,
    /* lineHeight*/ font.lineHeight,
    layout,
    /* scale     */ 1
);

// Hand the layout straight to drawWrapped — no array conversion, no string splitting.
font.drawWrapped(
    ctx, text, layout, lineCount,
    /* box */ 200, 80, 20, 20,
    /* scale */ 1,
    /* align  */ 1,  // center
    /* vAlign */ 1   // middle
);

🧠 Why a separate package?

lite-bmfont is the renderer. lite-text-layout is the planner. Keeping them split:

  • Lets the renderer ship without paying for wrapper code when you don't need it (~0.6 KB saved).
  • Lets you swap in a custom layout strategy (RTL, char-break-only, hyphenation, soft-hyphen markers) without forking the renderer.
  • Lets you compute layout once and re-render every frame for free — typical in HUDs where the dialogue box doesn't resize but its position animates.

📦 Layout buffer format

Each line is 4 consecutive Float32 values:

| Slot | Meaning | |------|---------| | [0] | startIdx — char index in text where this line begins (inclusive) | | [1] | endIdx — char index in text where this line ends (exclusive) | | [2] | lineWidth — measured pixel width of this line, including any ellipsis allowance | | [3] | flagsFLAG_NORMAL (0) or FLAG_TRUNCATED (1, append ) |

The buffer must hold at least lineCount * 4 floats; surplus capacity is ignored, so you can reuse one fat buffer across many strings. The function returns the number of lines actually written.

import { FLAG_TRUNCATED } from '@zakkster/lite-text-layout';

if (layout[lineCount * 4 - 1] === FLAG_TRUNCATED) {
    // The last line was truncated — content was longer than boxHeight allowed.
}

🧩 Wrapping rules

  • Soft-break at the last space when adding the next glyph would exceed boxWidth. The breaking space is excluded from both sides; runs of leading whitespace on the next line are skipped.
  • Hard-break inside a word when no space is available within the current line. Kerning is reset across the break.
  • Explicit \n starts a new line and is not rendered.
  • boxWidth === 0 disables horizontal wrapping (only \n and truncation apply).
  • boxHeight === 0 disables vertical truncation entirely.

✂️ Truncation

When boxHeight > 0 and the wrap would push content past the bottom of the box, the last fitting line is flagged FLAG_TRUNCATED. The renderer appends (three ASCII . glyphs) — no per-frame string allocation.

To make the ellipsis fit cleanly, computeWrap tracks the latest position on each line where content + ellipsis still fits within boxWidth. The truncated line ends there.

Edge cases worth knowing:

  • If the font doesn't include '.' (code 46), ellipsisWidth is treated as 0 and no truncation marker is drawn. Content is still truncated to fit boxHeight.
  • If boxWidth is so narrow that not even one glyph + ellipsis fits anywhere on the line, the truncated line falls back to FLAG_NORMAL (no ellipsis attempt) so the renderer doesn't draw dots that would themselves overflow the box.

⚙️ API

computeWrap(text, font, boxWidth, boxHeight, lineHeight, outBuffer, scale?) → number

Computes the layout. Writes 4-tuples into outBuffer and returns the line count.

  • text — source string
  • font — anything with glyphs: Int16Array and kerning: Int16Array; a BitmapFont instance works directly
  • boxWidth — px, 0 for no horizontal limit
  • boxHeight — px, 0 for no vertical limit
  • lineHeight — px at scale=1, usually font.lineHeight
  • outBuffer — pre-allocated Float32Array, capacity caps line count at floor(length / 4)
  • scale — applied to all widths and the height-fit check (default 1)

Constants

  • FLAG_NORMAL = 0 — normal line
  • FLAG_TRUNCATED = 1 — renderer should append

🧪 Benchmark

Word-wrapping a 50-word paragraph each frame at 60 fps:
  ctx.measureText + manual line-build:  allocates strings/arrays every frame
  TextLayout.computeWrap(buffer):       zero allocation — output buffer is reused

Re-rendering an already-laid-out paragraph at 60 fps:
  any text engine:               re-runs layout per frame
  drawWrapped(layout):           zero work — layout is just an array of indices

📦 TypeScript

Full TypeScript declarations included in TextLayout.d.ts. Exports:

  • TextLayout.computeWrap(...)
  • FLAG_NORMAL, FLAG_TRUNCATED
  • Types: BitmapFontData, LayoutLine, LineFlag

📚 LLM-Friendly Documentation

See llms.txt for AI-optimized metadata and usage examples.

🗒️ Changelog

1.0.0

  • Initial release. computeWrap with soft-break, hard-break, explicit-newline, truncation with ellipsis flag, and full kerning support.

License

MIT