glyphshaper

Edit a glyph.
Watch it everywhere.

npm ↗
GitHub ↗
TypeScriptopentype.jsReact + Vanilla JSNo server

Click any character, drag its bezier control points to reshape the outline, then hit Apply. glyphShaper regenerates the font binary in the browser and injects a dynamic @font-face override — every instance of that character on the page re-renders instantly. No server, no export, no page reload.

Interactive demo — click any character

Drop a font file or click to browse

Loaded with Inter by default — swap it for any TTF, OTF, WOFF, or WOFF2 above.

How it works

Parse, edit, regenerate

glyphShaper uses opentype.js to parse the uploaded font binary into a structured object. Each glyph’s path.commands array — moveTo, lineTo, curveTo, quadraticCurveTo — is exposed as draggable SVG control points. When you hit Apply, the modified font object is serialised back to an ArrayBuffer via font.toArrayBuffer().

Dynamic @font-face injection

The regenerated buffer becomes a Blob URL. A <style> element is injected (or replaced) in <head> with a new @font-facerule pointing at that URL. The browser immediately re-renders any element using that font‑family — headings, body text, buttons — with the modified glyph outline.

Bezier control points

Glyph outlines use cubic and quadratic Bézier segments. Anchor points (filled circles) are on-curve endpoints. Handle points (outlined circles) are off-curve control handles that pull the curve without touching it. Moving handles reshapes the curve smoothly; moving anchors shifts the segment endpoint. Pointer capture keeps the drag working even when the cursor leaves the SVG area.

Ephemeral by design

Every edit lives entirely in the browser’s memory. The original font file is never written to disk. Refreshing the page resets everything. This makes glyphShaper ideal for live demos, design explorations, and teaching moments — low commitment, instant feedback, no pipeline.

Usage

TypeScript + React · Vanilla JS

Drop-in editor component

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

const { font } = useGlyphFont('/fonts/MyFont.ttf')

// fontFamily must match the CSS font-family name applied to the child element
<GlyphShaperEditor font={font} fontFamily="MyFont" text="Heading">
  <h1 style={{ fontFamily: 'MyFont' }}>Heading</h1>
</GlyphShaperEditor>

Load from uploaded File

import { useGlyphFont } from '@liiift-studio/glyphshaper'

// Pass a File object from an <input type="file"> onChange handler
// Note: useGlyphFont supports TTF, OTF, and WOFF1 only.
// For WOFF2, use parseFont() directly with a woff2Decompressor callback.
const { font, loading, error } = useGlyphFont(file)

// Pass null to reset the hook to idle state
const { font } = useGlyphFont(null)

Low-level — 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 path commands
const cmds = getGlyphCommands(font, 'A')

// Modify — e.g. shift all anchor y values up by 50 units
const shifted = cmds.map(c =>
  c.type !== 'Z' ? { ...c, y: c.y + 50 } : c
)

// Write back and apply
setGlyphCommands(font, 'A', shifted)
const url = applyFontBlob('MyFont', fontToBlob(font))
// All elements using font-family: MyFont now show the shifted A

API

glyphShaper API
NameDescription
parseFont(buffer, decompressor?)Parse an ArrayBuffer (TTF, OTF, or WOFF1) into a GlyphFont handle. For WOFF2 input, pass a woff2Decompressor callback — the function throws without it.
getGlyphCommands(font, char)Return a deep copy of the path commands for a character.
setGlyphCommands(font, char, cmds)Write modified commands back into the font object (mutates in place).
fontToBlob(font)Serialise the font to a Blob (OTF binary).
applyFontBlob(family, blob, existingUrl?, options?)Inject a @font-face override; returns the Blob URL for later cleanup. Pass existingUrl to revoke the previous Blob URL and prevent memory leaks. options accepts fontWeight and fontStyle.
revokeFont(url)Revoke the Blob URL and remove the override style element.
commandsToPathD(cmds)Convert a PathCommand[] to an SVG path d string for use in a <path> element.
useGlyphFont(source)React hook — accepts a URL string, File, or null (resets to idle). Supports TTF, OTF, and WOFF1; for WOFF2 use parseFont() directly. Returns {font, loading, error}.
GlyphShaperEditorDrop-in React component — handles font loading, glyph state, and bezier editing in a single wrapper. Accepts font, fontFamily, and text.
GlyphSvgEditorLower-level React component exposing just the SVG bezier editor. Use when you want to manage font loading and state yourself.

Font format support

glyphShaper accepts TTF, OTF, WOFF1, and WOFF2. WOFF2 support requires passing a woff2Decompressor callback to parseFont() — the demo uses wawoff2 (a WASM brotli decoder) for this. The useGlyphFont hook does not accept a decompressor and therefore does not support WOFF2; use parseFont() directly if you need WOFF2.