glyphshaper
Edit a glyph.
Watch it everywhere.
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
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 AAPI
| Function | Description |
|---|---|
| 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.