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

text-shaper

v0.1.18

Published

Pure TypeScript text shaping engine with OpenType layout, TrueType hinting, and FreeType-style rasterization

Readme

text-shaper

GitHub Twitter Email Discord Support me

Pure TypeScript text shaping engine with OpenType layout, TrueType hinting, and FreeType-style rasterization. Works in browsers and Bun/Node.js with zero dependencies.

Performance

text-shaper outperforms harfbuzzjs (WebAssembly) and opentype.js across all benchmarks:

| Category | vs harfbuzzjs | vs opentype.js | |----------|---------------|----------------| | Path Extraction | 16x faster | 10x faster | | Text to SVG | 1.2-1.5x faster | 4-6x faster | | Latin Shaping | 1.5x faster | 22x faster | | Arabic Shaping | 1.2x faster | 86x faster | | Hebrew Shaping | 1.6x faster | 33x faster | | Hindi Shaping | 3.6x faster | 11x faster | | Myanmar Shaping | 10.5x faster | 17x faster | | CJK Shaping | 1.3-1.5x faster | 11-13x faster |

Features

  • OpenType Layout: Full GSUB (substitution) and GPOS (positioning) support
  • Complex Scripts: Arabic, Indic, USE (Universal Shaping Engine) shapers
  • Variable Fonts: fvar, gvar, avar, HVAR, VVAR, MVAR tables
  • AAT Support: morx, kerx, trak tables for Apple fonts
  • Color Fonts: SVG, sbix, CBDT/CBLC, COLR/CPAL tables
  • BiDi: UAX #9 bidirectional text algorithm
  • Rasterization: FreeType-style grayscale, LCD subpixel, and monochrome rendering
  • TrueType Hinting: Full bytecode interpreter (150+ opcodes)
  • Texture Atlas: GPU-ready glyph atlas generation with shelf packing
  • SDF/MSDF: Signed distance field rendering for scalable text
  • Zero Dependencies: Pure TypeScript, works in browser and Node.js

Installation

npm install text-shaper
# or
bun add text-shaper

Usage

Basic Shaping

import { Font, shape, UnicodeBuffer } from "text-shaper";

// Load a font
const fontData = await fetch("path/to/font.ttf").then(r => r.arrayBuffer());
const font = Font.load(fontData);

// Create a buffer with text
const buffer = new UnicodeBuffer();
buffer.addStr("Hello, World!");

// Shape the text
const glyphBuffer = shape(font, buffer);

// Access shaped glyphs
for (let i = 0; i < glyphBuffer.length; i++) {
  const info = glyphBuffer.info[i];
  const pos = glyphBuffer.pos[i];
  console.log(`Glyph ${info.glyphId}: advance=${pos.xAdvance}`);
}

High-Performance Shaping

For best performance, reuse buffers with shapeInto:

import { Font, shapeInto, UnicodeBuffer, GlyphBuffer } from "text-shaper";

const font = Font.load(fontData);
const uBuffer = new UnicodeBuffer();
const gBuffer = GlyphBuffer.withCapacity(128);

// Shape multiple strings efficiently
for (const text of texts) {
  uBuffer.clear();
  uBuffer.addStr(text);
  gBuffer.reset();
  shapeInto(font, uBuffer, gBuffer);
  // Process gBuffer...
}

TTC Collections

import { Font } from "text-shaper";

const buffer = await fetch("fonts.ttc").then(r => r.arrayBuffer());
const collection = Font.collection(buffer);

if (collection) {
  console.log(collection.count);
  const names = collection.names();
  const font = collection.get(0);
}

With Features

import { Font, shape, UnicodeBuffer, feature } from "text-shaper";

const glyphBuffer = shape(font, buffer, {
  features: [
    feature("smcp"),      // Small caps
    feature("liga"),      // Ligatures
    feature("kern"),      // Kerning
  ],
});

// Or use convenience helpers
import { smallCaps, standardLigatures, kerning, combineFeatures } from "text-shaper";

const glyphBuffer = shape(font, buffer, {
  features: combineFeatures(smallCaps(), standardLigatures(), kerning()),
});

Variable Fonts

import { Font, shape, UnicodeBuffer, tag } from "text-shaper";

const glyphBuffer = shape(font, buffer, {
  variations: [
    { tag: tag("wght"), value: 700 },  // Bold
    { tag: tag("wdth"), value: 75 },   // Condensed
  ],
});

Rendering to SVG

import {
  Font, shape, UnicodeBuffer,
  glyphBufferToShapedGlyphs, shapedTextToSVG
} from "text-shaper";

const buffer = new UnicodeBuffer();
buffer.addStr("Hello");

const glyphBuffer = shape(font, buffer);
const shapedGlyphs = glyphBufferToShapedGlyphs(glyphBuffer);
const svg = shapedTextToSVG(font, shapedGlyphs, { fontSize: 48 });

Rasterization

import { Font, rasterizeGlyph, buildAtlas, PixelMode } from "text-shaper";

// Rasterize a single glyph
const bitmap = rasterizeGlyph(font, glyphId, 48, {
  pixelMode: PixelMode.Gray,  // Gray, Mono, or LCD
  hinting: true,              // Enable TrueType hinting
});

// Build a texture atlas for GPU rendering
const atlas = buildAtlas(font, glyphIds, {
  fontSize: 32,
  padding: 1,
  pixelMode: PixelMode.Gray,
  hinting: true,
});

SDF/MSDF Rendering

import {
  Font, getGlyphPath, renderSdf, renderMsdf, buildMsdfAtlas
} from "text-shaper";

// Single glyph SDF
const path = getGlyphPath(font, glyphId);
if (path) {
  const sdf = renderSdf(path, {
    width: 64,
    height: 64,
    scale: 1,
    spread: 8,
  });
}

// MSDF atlas for GPU text rendering (handles font internally)
const msdfAtlas = buildMsdfAtlas(font, glyphIds, {
  fontSize: 32,
  spread: 4,
});

Fluent API

Two composition styles for glyph manipulation and rendering:

Builder Pattern (Method Chaining)

import { Font, glyph, char, glyphVar, combine, PixelMode } from "text-shaper";

// From glyph ID
const rgba = glyph(font, glyphId)
  ?.scale(2)
  .rotateDeg(15)
  .rasterizeAuto({ padding: 2 })
  .blur(5)
  .toRGBA();

// From character
const svg = char(font, "A")
  ?.scale(3)
  .italic(12)
  .toSVG({ width: 100, height: 100 });

// Variable fonts
const bitmap = glyphVar(font, glyphId, [700, 100])  // wght=700, wdth=100
  ?.embolden(50)
  .rasterize({ pixelMode: PixelMode.Gray, scale: 2 })
  .toBitmap();

// Combine multiple glyphs
const h = glyph(font, hGlyphId)?.translate(0, 0);
const i = glyph(font, iGlyphId)?.translate(100, 0);
if (h && i) {
  const combined = combine(h, i).scale(2).rasterizeAuto().toRGBA();
}

PathBuilder Methods

// Transforms (lazy - accumulated as matrix)
.scale(sx, sy?)           // Scale uniformly or non-uniformly
.translate(dx, dy)        // Translate by offset
.rotate(radians)          // Rotate by angle in radians
.rotateDeg(degrees)       // Rotate by angle in degrees
.shear(shearX, shearY)    // Shear transform
.italic(degrees)          // Italic slant (convenience for shear)
.matrix(m)                // Apply custom 2D matrix
.perspective(m)           // Apply 3D perspective matrix
.resetTransform()         // Reset accumulated transforms
.apply()                  // Apply accumulated transforms to path

// Path effects (immediate - modifies path)
.embolden(strength)       // Make strokes thicker
.condense(factor)         // Horizontal compression
.oblique(slant)           // Oblique slant effect
.stroke(width, cap?, join?)  // Convert to stroked outline
.strokeAsymmetric(opts)   // Independent x/y stroke widths

// Output
.rasterize(options)       // Rasterize to BitmapBuilder
.rasterizeAuto(options?)  // Auto-sized rasterization
.toSdf(options)           // Render to SDF bitmap
.toMsdf(options)          // Render to MSDF bitmap
.toSVG(options?)          // Export as SVG string
.toSVGElement(options?)   // Export as SVG path element
.toCanvas(ctx, options?)  // Draw to Canvas 2D context
.toPath()                 // Get raw GlyphPath
.clone()                  // Clone the builder

BitmapBuilder Methods

// Blur effects
.blur(radius)             // Gaussian blur
.boxBlur(radius)          // Box blur (faster)
.fastBlur(radius)         // Fast approximated blur
.cascadeBlur(rx, ry?)     // Cascade blur for large radii
.adaptiveBlur(rx, ry?)    // Adaptive quality blur

// Modifications
.embolden(xStrength, yStrength?)  // Expand bitmap
.shift(dx, dy)            // Shift bitmap contents
.resize(width, height)    // Resize (nearest neighbor)
.resizeBilinear(w, h)     // Resize with bilinear filtering
.pad(left, top, right, bottom)  // Add padding
.convert(pixelMode)       // Convert pixel format

// Output
.toRGBA()                 // Export as RGBA Uint8Array
.toGray()                 // Export as grayscale Uint8Array
.toBitmap()               // Get Bitmap object
.toRasterizedGlyph()      // Get RasterizedGlyph with metrics
.clone()                  // Clone the builder

Pipe Pattern (Functional)

import {
  pipe, glyph,
  $scale, $rotate, $embolden,
  $rasterize, $blur, $toRGBA
} from "text-shaper";

// Compose operations functionally
const rgba = pipe(
  glyph(font, glyphId),
  $scale(2),
  $rotate(Math.PI / 12),
  $embolden(30),
  $rasterize({ pixelMode: PixelMode.Gray }),
  $blur(3),
  $toRGBA()
);

Line Breaking & Justification

import { breakIntoLines, justify, JustifyMode } from "text-shaper";

// Break text into lines
const lines = breakIntoLines(glyphBuffer, font, maxWidth);

// Justify a line
const justified = justify(line, targetWidth, {
  mode: JustifyMode.Distribute,
});

Text Segmentation

import { countGraphemes, splitGraphemes, splitWords } from "text-shaper";

// Count grapheme clusters (visual characters)
const count = countGraphemes("👨‍👩‍👧‍👦Hello"); // 6 (family emoji = 1)

// Split into graphemes
const graphemes = splitGraphemes("नमस्ते"); // Devanagari clusters

// Split into words
const words = splitWords("Hello World"); // ["Hello", " ", "World"]

Canvas Rendering

import {
  Font, shape, UnicodeBuffer,
  glyphBufferToShapedGlyphs, renderShapedText
} from "text-shaper";

const canvas = document.getElementById("canvas") as HTMLCanvasElement;
const ctx = canvas.getContext("2d")!;

const buffer = new UnicodeBuffer();
buffer.addStr("Hello Canvas!");

const glyphBuffer = shape(font, buffer);
const shapedGlyphs = glyphBufferToShapedGlyphs(glyphBuffer);

renderShapedText(ctx, font, shapedGlyphs, {
  x: 50,
  y: 100,
  fontSize: 48,
});

BiDi & RTL Text

import {
  Font, shape, UnicodeBuffer,
  processBidi, reorderGlyphs, detectDirection
} from "text-shaper";

// Automatic RTL detection and reordering
const buffer = new UnicodeBuffer();
buffer.addStr("Hello שלום World");  // Mixed LTR/RTL

const glyphBuffer = shape(font, buffer);
// Glyphs are automatically reordered for visual display

// Manual BiDi processing
const bidiResult = processBidi("مرحبا Hello");
console.log(bidiResult.direction);  // "rtl"
console.log(bidiResult.levels);     // Embedding levels per character

// Detect text direction
const dir = detectDirection("שלום");  // "rtl"

Color Fonts

import {
  Font,
  hasColorGlyph, getColorPaint, getColorLayers,  // COLR
  hasSvgGlyph, getSvgDocument,                     // SVG
  hasColorBitmap, getBitmapGlyph,                  // CBDT/sbix
} from "text-shaper";

// Check for color glyph support
const glyphId = font.glyphId("😀".codePointAt(0)!);

// COLR/CPAL (vector color)
if (hasColorGlyph(font, glyphId)) {
  const paint = getColorPaint(font, glyphId);
  // Render paint tree...
}

// SVG color glyphs
if (hasSvgGlyph(font, glyphId)) {
  const svgDoc = getSvgDocument(font, glyphId);
  // Use SVG document directly
}

// Bitmap color glyphs (sbix, CBDT)
if (hasColorBitmap(font, glyphId)) {
  const bitmap = getBitmapGlyph(font, glyphId, 128);  // ppem=128
  // bitmap.data contains PNG/JPEG data
}

Texture Atlas (WebGL/GPU)

import {
  Font, buildAtlas, buildStringAtlas, buildMsdfAtlas,
  atlasToRGBA, getGlyphUV, PixelMode
} from "text-shaper";

// Build atlas from glyph IDs
const atlas = buildAtlas(font, glyphIds, {
  fontSize: 32,
  padding: 2,
  pixelMode: PixelMode.Gray,
});

// Build atlas from string (auto-extracts unique glyphs)
const textAtlas = buildStringAtlas(font, "Hello World!", {
  fontSize: 48,
  padding: 1,
});

// Convert to RGBA for WebGL texture
const rgba = atlasToRGBA(atlas);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, atlas.width, atlas.height,
              0, gl.RGBA, gl.UNSIGNED_BYTE, rgba);

// Get UV coordinates for rendering
const uv = getGlyphUV(atlas, glyphId);
// uv = { u0, v0, u1, v1, ... }

// MSDF atlas for scalable GPU text
const msdfAtlas = buildMsdfAtlas(font, glyphIds, {
  fontSize: 32,
  spread: 4,
});

Browser Usage

// Fetch font from URL
const fontData = await fetch("/fonts/MyFont.ttf").then(r => r.arrayBuffer());
const font = Font.load(fontData);

// Or from File input
const file = input.files[0];
const buffer = await file.arrayBuffer();
const font = Font.load(buffer);

// Works with any ArrayBuffer source

API Reference

Core Classes

| Class | Description | |-------|-------------| | Font | Load and parse OpenType/TrueType fonts | | Face | Font face with variation coordinates applied | | UnicodeBuffer | Input buffer for text to shape | | GlyphBuffer | Output buffer containing shaped glyphs |

Shaping Functions

| Function | Description | |----------|-------------| | shape(font, buffer, options?) | Shape text, returns new GlyphBuffer | | shapeInto(font, buffer, glyphBuffer, options?) | Shape into existing buffer (faster) | | createShapePlan(font, options) | Create reusable shape plan | | getOrCreateShapePlan(font, options) | Get cached or create shape plan |

Rendering Functions

| Function | Description | |----------|-------------| | getGlyphPath(font, glyphId) | Get glyph outline as path commands | | shapedTextToSVG(font, shapedGlyphs, options) | Render shaped text to SVG string | | renderShapedText(ctx, font, shapedGlyphs, options) | Render to Canvas 2D context | | glyphBufferToShapedGlyphs(buffer) | Convert GlyphBuffer to ShapedGlyph[] | | rasterizeGlyph(font, glyphId, size, options) | Rasterize glyph to bitmap | | rasterizePath(path, options) | Rasterize path commands to bitmap | | buildAtlas(font, glyphIds, options) | Build texture atlas |

Feature Helpers

// Ligatures
standardLigatures()      // liga
discretionaryLigatures() // dlig
contextualAlternates()   // calt

// Caps
smallCaps()              // smcp
capsToSmallCaps()        // c2sc
allSmallCaps()           // smcp + c2sc

// Figures
oldstyleFigures()        // onum
liningFigures()          // lnum
tabularFigures()         // tnum
proportionalFigures()    // pnum

// Stylistic
stylisticSet(n)          // ss01-ss20
characterVariant(n)      // cv01-cv99
swash()                  // swsh

Unicode Utilities

| Function | Description | |----------|-------------| | processBidi(text) | Process bidirectional text (UAX #9) | | getScript(codepoint) | Get Unicode script for codepoint | | getScriptRuns(text) | Split text into script runs | | countGraphemes(text) | Count grapheme clusters | | splitGraphemes(text) | Split into grapheme clusters | | analyzeLineBreaks(text) | Find line break opportunities (UAX #14) |

Supported Tables

Required

head, hhea, hmtx, maxp, cmap, loca, glyf, name, OS/2, post

OpenType Layout

GDEF, GSUB, GPOS, BASE

CFF

CFF, CFF2

Variable Fonts

fvar, gvar, avar, HVAR, VVAR, MVAR, STAT

AAT (Apple)

morx, kerx, kern, trak, feat

Color

COLR, CPAL, SVG, sbix, CBDT, CBLC

Vertical

vhea, vmtx, VORG

Hinting

fpgm, prep, cvt, gasp

License

MIT