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

@liiift-studio/glyphshaper

v1.0.8

Published

Interactive glyph path editor — click any character, reshape its bezier curves, and watch every instance on the page update instantly

Downloads

703

Readme

Glyph Shaper

npm License: MIT part of liiift type-tools

CSS and JavaScript have no native way to reshape individual glyph outlines after the font loads. glyphShaper parses the font binary in the browser, lets you drag bezier control points to edit any character's outline, then regenerates the font and injects a @font-face override — every instance of that character on the page re-renders instantly, no page reload required.

glyphshaper.com · npm · GitHub

TypeScript · React · Requires opentype.js + wawoff2


Install

npm install @liiift-studio/glyphshaper opentype.js wawoff2

opentype.js and wawoff2 are required peer dependencies — they handle font parsing and WOFF2 decompression respectively and must be installed alongside this package.


Usage

Next.js App Router: this library uses browser APIs. Add "use client" to any component file that imports from it.

Font format: glyphShaper accepts TTF, OTF, WOFF1, and WOFF2. The font must be loaded from a URL accessible to fetch() (or supplied as a File object via <input type="file">).

React component

The GlyphShaperEditor component handles font loading, character palette, the SVG bezier editor, undo history, and the apply-to-page step in one self-contained component.

'use client'
import { useGlyphFont, GlyphShaperEditor } from '@liiift-studio/glyphshaper'

export default function MyPage() {
  const { font } = useGlyphFont('/fonts/MyFont.ttf')

  return (
    <GlyphShaperEditor font={font} fontFamily="MyFont" text="Headline">
      <h1 style={{ fontFamily: 'MyFont' }}>Headline</h1>
    </GlyphShaperEditor>
  )
}

The fontFamily prop must match the CSS font-family value already applied to your page text — this is what the @font-face override targets.

React hook — font loading only

Use useGlyphFont alone when you want to drive the lower-level functions directly.

'use client'
import { useGlyphFont, getGlyphCommands, setGlyphCommands, fontToBlob, applyFontBlob } from '@liiift-studio/glyphshaper'

export default function MyEditor() {
  const { font, loading, error } = useGlyphFont('/fonts/MyFont.ttf')

  if (loading) return <p>Loading…</p>
  if (error)   return <p>Error: {error}</p>
  if (!font)   return null

  const cmds = getGlyphCommands(font, 'A')
  // … modify cmds …
  setGlyphCommands(font, 'A', cmds)
  const blob = fontToBlob(font)
  applyFontBlob('MyFont', blob)
}

Vanilla JS

import { parseFont, getGlyphCommands, setGlyphCommands, fontToBlob, applyFontBlob } from '@liiift-studio/glyphshaper'

const res    = await fetch('/fonts/MyFont.ttf')
const buffer = await res.arrayBuffer()
const font   = await parseFont(buffer)

// Read glyph outline commands for 'A'
const cmds = getGlyphCommands(font, 'A')

// Modify commands (e.g. shift the first anchor point)
const modified = cmds.map((cmd, i) =>
  i === 0 && cmd.type === 'M' ? { ...cmd, x: cmd.x + 20 } : cmd
)

// Write back and inject override
setGlyphCommands(font, 'A', modified)
const blob = fontToBlob(font)
applyFontBlob('MyFont', blob)

TypeScript

import type { GlyphFont, PathCommand, GlyphShaperOptions, CmdM, CmdL, CmdC, CmdQ, CmdZ } from '@liiift-studio/glyphshaper'

const opts: GlyphShaperOptions = {
  fontWeight: 'bold',
  fontStyle: 'normal',
}

API

useGlyphFont(source)

React hook. Fetches and parses a font from a URL string or File object. Returns { font, loading, error }.

| Parameter | Type | Description | |-----------|------|-------------| | source | string \| File \| null | Font URL, user-uploaded File, or null to reset |

parseFont(buffer)

Async. Parses an ArrayBuffer (TTF, OTF, WOFF1, or WOFF2) into a GlyphFont handle. WOFF2 is transparently decompressed via wawoff2 before being handed to opentype.js.

getGlyphCommands(font, char)

Returns a deep copy of the path commands for char as a PathCommand[]. Returns [] for characters with no outlines (e.g., space).

setGlyphCommands(font, char, commands)

Writes modified commands back into the font object in place. The change takes effect on the next fontToBlob() call.

fontToBlob(font)

Serialises the (possibly modified) font back to an ArrayBuffer using opentype.js's download path.

applyFontBlob(fontFamily, blob, previousUrl?)

Injects a @font-face override rule targeting fontFamily with the supplied blob. Creates a Blob URL, appends a <style> tag to the document, and returns the Blob URL so it can be revoked later. If previousUrl is supplied it is revoked before the new rule is injected.

revokeFont(url)

Revokes a Blob URL returned by applyFontBlob and removes the corresponding <style> tag from the document.

commandsToPathD(commands)

Converts a PathCommand[] to an SVG d string suitable for use in a <path> element.


GlyphShaperEditor props

| Prop | Type | Default | Description | |------|------|---------|-------------| | font | GlyphFont \| null | — | Parsed font from useGlyphFont() or parseFont(). Pass null while loading | | fontFamily | string | — | CSS font-family name the @font-face override will target | | text | string | 'Typography' | Text used to derive the character palette. Unique printable characters appear as clickable tiles | | children | ReactNode | — | Content rendered with the font applied. If omitted, text is rendered as a paragraph |


How it works

Font parsing: parseFont() uses dynamic import('opentype.js') so the parser is only loaded when called. WOFF2 fonts are first decompressed with import('wawoff2') (WASM brotli decoder), then passed to opentype.js as raw bytes.

Path command model: opentype.js exposes each glyph's outline as a flat array of path commands (M, L, C, Q, Z). glyphShaper deep-copies this array into React state so edits are non-destructive until the user clicks "Apply".

SVG editor: The inline bezier editor renders the glyph outline in a fixed-coordinate SVG (viewBox 0 0 360 360). A y-flip transform reconciles glyph space (y-up) with SVG space (y-down). Pointer capture keeps drags active when the cursor leaves a control point circle. getScreenCTM().inverse() converts pointer events at any CSS scale back to viewBox coordinates.

Undo: Each drag operation pushes a pre-drag snapshot of the commands array onto a bounded history stack (max 50 entries). Undo restores the last snapshot. Ctrl+Z / Cmd+Z is handled via a keydown listener while the editor panel is open.

Font-face override: After "Apply", setGlyphCommands writes the modified path back into the live opentype.js font object, fontToBlob() re-serialises the entire font to an ArrayBuffer, and applyFontBlob() creates a Blob URL and injects a @font-face rule at a higher specificity than the original. Every instance of the character on the page re-renders immediately without a reload.


Peer dependencies

Both peer dependencies are required (not optional):

| Package | Purpose | |---------|---------| | opentype.js | Font parsing, glyph path access, and font serialisation | | wawoff2 | WOFF2 decompression (WASM brotli) — only loaded when a WOFF2 font is used |

If you are bundling for the browser and your bundler tries to resolve Node.js built-ins (fs, path) pulled in by wawoff2, stub them as empty modules. For webpack / Next.js:

// next.config.ts / webpack config
config.resolve.fallback = { ...config.resolve.fallback, fs: false, path: false }

Current version: v1.0.7