glyphshaper

Edit a glyph.
Watch it everywhere.

npm ↗
GitHub
TypeScript·opentype.js·React + Vanilla JS·No 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

Drop a font file or click to browse

Loaded with Merriweather by default — swap it for any TTF, OTF, WOFF, or WOFF2 above. Click a character tile to open its bezier path editor. Drag anchors (filled circles) or handles (outlined circles) to reshape the glyph. Hit Apply to page and every instance — headings, body text, wherever that font-family is used — re-renders instantly via a dynamic @font-face override. No server. No export. Changes reset on page reload.

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')

<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
const { font, loading, error } = useGlyphFont(file)

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

FunctionDescription
parseFont(buffer)Parse an ArrayBuffer (TTF, OTF, WOFF1, or WOFF2) into a GlyphFont handle.
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)Inject a @font-face override; returns the Blob URL for later cleanup.
revokeFont(url)Revoke the Blob URL and remove the override style element.
useGlyphFont(source)React hook — accepts a URL string or File; returns {font, loading, error}.

Font format support

glyphShaper accepts TTF, OTF, WOFF1, and WOFF2. WOFF2 is transparently decompressed in the browser using wawoff2 (a WASM brotli decoder) before being passed to opentype.js — no conversion step needed.