insomni-msdf-text
insomni-msdf-text turns TTF/OTF font files into a GPU
multi-channel signed distance field
(MSDF) atlas, glyph by glyph. It owns three responsibilities:
- Font loading — parse a
.ttf/.otfviaopentype.jsand expose glyph outlines in em-relative coordinates (Font,loadFont). - Atlas management — lazily generate one MSDF per
(font, codepoint)into a singlergba8unormGPU texture, with a shelf packer (MsdfGlyphAtlas). - MSDF generation — the compute-shader pipeline and the CPU prep that feeds
it (
MsdfGenerator,prepareGlyphSegments, and friends).
All glyph metrics live in em-relative units (1 em = unitsPerEm font units).
One MSDF entry per codepoint is reused at every render size — the shader scales
the distance field at draw time. Coordinates are y-up (font files are y-down;
the loader flips Y at parse time).
Loading a font
Section titled “Loading a font”loadFont accepts a URL, an ArrayBuffer, or a Uint8Array and resolves to a
Font.
import { loadFont } from "insomni-msdf-text";
// From a URL.const font = await loadFont("/fonts/Inter.ttf");
// From bytes (e.g. a fetched / base64-decoded buffer).const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));const font2 = await loadFont(bytes, { family: "Inter" });loadFont(source, options?)
Section titled “loadFont(source, options?)”| Param | Type | Notes |
|---|---|---|
source | FontSource = string | ArrayBuffer | Uint8Array | URL is fetched; a Uint8Array is copied into a fresh ArrayBuffer. |
options.family | string | Optional human-readable family label. Defaults to the font’s own family name. |
Returns Promise<Font>. Throws if a URL fetch fails.
A parsed font with cached, em-relative glyph outlines keyed by codepoint.
| Member | Type | Description |
|---|---|---|
family | string | Family label (from options.family or the font’s own name). |
unitsPerEm | number | Font design units per em. |
ascender | number | Ascender, em units. |
descender | number | Descender, em units (negative). |
lineGap | number | Line gap from the hhea table, em units. |
getOutline(codepoint) | GlyphOutline | null | Em-relative outline (y-up). null if the font has no glyph. Whitespace returns zero contours but a real advance. Cached. |
getKerning(left, right) | number | Horizontal kerning between two codepoints, em units. 0 when the font has no kerning data for the pair. |
You can also construct Font directly from an opentype.Font via
new Font(otf, options?) if you have already parsed the bytes yourself.
Building an atlas
Section titled “Building an atlas”MsdfGlyphAtlas owns the GPU texture and generates each glyph’s MSDF on first
request. One atlas corresponds to one Font — use multiple atlases for multiple
fonts.
import { loadFont, MsdfGlyphAtlas } from "insomni-msdf-text";
const font = await loadFont("/fonts/Inter.ttf");
const atlas = new MsdfGlyphAtlas({ device, // a GPUDevice font, atlasSize: 2048, // square texture side, texels (default 2048) atlasFontSize: 64, // em → texels base resolution (default 64) pxRange: 8, // distance range in texels (default 8)});
// Warm the atlas with the glyphs you expect to draw.atlas.preload("Hello, world! 0123456789");
// Look one up.const g = atlas.getGlyph("H".codePointAt(0)!);if (g) { // g.uvMinX..uvMaxY index into atlas.textureView (rgba8unorm); // g.advance / bearingX / bearingY / width / height are em-relative.}
// Sample atlas.texture / atlas.textureView from your own pipeline.MsdfGlyphAtlasOptions
Section titled “MsdfGlyphAtlasOptions”| Option | Type | Default | Description |
|---|---|---|---|
device | GPUDevice | — | Required. The device that owns the texture and compute pipeline. |
font | Font | — | Required. The font this atlas rasterizes. |
atlasSize | number | 2048 | Side length of the square rgba8unorm texture. |
atlasFontSize | number | 64 | Em size in atlas texels — base rasterization resolution. Larger = sharper at high zoom, more atlas pressure. |
pxRange | number | 8 | Distance range in atlas texels. Wider = thicker outlines / softer shadows, lower precision. |
label | string | msdf-atlas:<family> | GPU texture label. |
MsdfGlyphAtlas members
Section titled “MsdfGlyphAtlas members”| Member | Type | Description |
|---|---|---|
texture | GPUTexture | The rgba8unorm atlas texture (usage includes TEXTURE_BINDING, STORAGE_BINDING, COPY_DST). |
textureView | GPUTextureView | Default view of texture. |
font / atlasSize / atlasFontSize / pxRange | — | The resolved config values. |
getGlyph(codepoint) | MsdfGlyph | null | Returns the entry, generating it on first request. null if the font lacks the glyph or the atlas is full (a warning is logged once). Whitespace returns an entry with zero w/h but the correct advance. |
getGlyphById(id) | MsdfGlyph | undefined | Fetch by stable atlasGlyphId (allocation order). |
preload(text) | void | Pre-generate every glyph in a string — useful for warming the atlas at startup. |
glyphCount | number | Number of glyphs allocated so far. |
version | number | Monotonic counter bumped on each new allocation. Compare against a stored value to detect new entries (e.g. to sync a parallel GPU metrics buffer). |
fillPercent | number | Approximate atlas fill, 0–100, from the shelf cursor. |
destroyed | boolean | Whether destroy() has run. |
destroy() | void | Destroys the GPU texture + generator and clears caches. |
MsdfGlyph
Section titled “MsdfGlyph”The per-glyph record returned by getGlyph / getGlyphById. UVs are normalized
[0,1]; the metric fields are em-relative.
| Field | Type | Description |
|---|---|---|
codepoint | number | The Unicode codepoint. |
atlasGlyphId | number | Stable sequential id (allocation order). Use as the index into a parallel GPU metrics buffer. |
uvMinX / uvMinY / uvMaxX / uvMaxY | number | Normalized UV bounds of the glyph region (inset by half a texel to avoid cross-region bleed). |
pxRange | number | Distance range used at generation, in MSDF texel units. |
advance | number | Horizontal advance, em units. |
bearingX / bearingY | number | Left / top bearing, em units. |
width / height | number | Padded glyph box size, em units. |
Glyph outline types
Section titled “Glyph outline types”Font.getOutline returns a GlyphOutline built from these shared shapes
(exported as types). These are the CPU-side representation the MSDF generator
consumes.
| Type | Shape | Notes |
|---|---|---|
Vec2 | { x, y } | 2D point, y-up in em space. |
Edge | { kind: "line"; p0; p1 } | { kind: "quad"; p0; p1; p2 } | { kind: "cubic"; p0; p1; p2; p3 } | One contour edge. Quads/cubics carry control points. |
Contour | { edges: Edge[]; windingSign: 1 | -1 } | A closed sequence of edges; +1 outer (CCW in y-up), -1 hole. |
GlyphOutline | { codepoint; contours: Contour[]; advance; bbox } | contours is empty for whitespace. bbox is the tight ink box, em units. |
Generating MSDFs directly
Section titled “Generating MSDFs directly”MsdfGlyphAtlas drives generation for you, but the lower-level pieces are
exported for custom packers or offline tooling. The two-step flow is: prep the
CPU segment buffer with prepareGlyphSegments, then dispatch the compute shader
with a MsdfGenerator.
import { MsdfGenerator, prepareGlyphSegments, type GenerateRegion } from "insomni-msdf-text";
const generator = new MsdfGenerator(device, atlasTextureView);
const outline = font.getOutline("g".codePointAt(0)!)!;const { data, count } = prepareGlyphSegments(outline);
const region: GenerateRegion = { originX: 0, originY: 0, width: 96, height: 96, emMin: { x: outline.bbox.minX, y: outline.bbox.minY }, emMax: { x: outline.bbox.maxX, y: outline.bbox.maxY }, range: 8 / 64, // pxRange / atlasFontSize, in em units};generator.generate(data, count, region); // writes into the atlas textureGeneration exports
Section titled “Generation exports”| Export | Kind | Description |
|---|---|---|
MsdfGenerator | class | Owns the WGSL compute pipeline and a reusable, power-of-2-grown segment storage buffer. new MsdfGenerator(device, atlasView); generate(segments, count, region); destroy(). |
prepareGlyphSegments(outline) | fn | Full CPU prep: outline → flattened, edge-colored, packed { data: Float32Array, count }. Filters degenerate contours and normalizes winding. |
packSegments(contours) | fn | Lower-level packer: colored-segment lists → { data, count }. |
GenerateRegion | type | The atlas region + em-space bounds + distance range (em units) for one dispatch. |
SEGMENT_FLOATS | const | 8 — floats per segment in the GPU storage buffer (32 bytes). |
SEGMENT_FLAG_LINE | const | 1 << 0 — segment is a line (vs. a quadratic Bézier). |
SEGMENT_FLAG_HOLE | const | 1 << 1 — parent contour is a hole (winding -1). |
MSDF_WGSL | const | The raw WGSL compute-shader source string. |
See also
Section titled “See also”- Text — how
insomni/text-ttfconsumes this atlas and the shared font/atlas interfaces the renderer’s shaper expects.