mesh-avatar-studio
일러스트를 코딩 에이전트와 로컬 편집기로 2D 메쉬 애니메이션 아바타로 변환
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기A browser type foundry — pick a font, apply a treatment, export a real installable font
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A browser type foundry. Pick a font, apply a treatment, tweak it live — down to a single
letter — and export a real, installable, correctly-licensed font. Then set it on a
specimen sheet, drive the dials with sound, and take the sheet away as an image or a clip.
Everything runs client-side. No account, no server, nothing leaves your machine — a font you
bring is read in the page and never sent anywhere.
Live at forfontsake.xyz — see docs/DEPLOY.md
for how it is deployed.
New here, or picking this up after a break? Start with docs/STATE.md.README 전문 보기 ↗A browser type foundry. Pick a font, apply a treatment, tweak it live — down to a single letter — and export a real, installable, correctly-licensed font. Then set it on a specimen sheet, set it moving, and take it away as an image, a seamless clip, or a whole week's post at once.
Everything runs client-side. No account, no server, nothing leaves your machine — a font you bring is read in the page and never sent anywhere.
Live at forfontsake.xyz — see docs/DEPLOY.md for how it is deployed.
New here, or picking this up after a break? Start with docs/STATE.md.
Every tool in this category ships a broken download. One fulfils exports by email; another's export hands back the unmodified source file; a third's has been reported dead since March 2025. So the first promise here is dull and load-bearing: the download works, and the build proves it rather than asserting it.
The closest prior art is NaN's Glyph Filters / generativefonts.xyz — 28 named procedural treatments, essentially this idea, but as Python scripts for Glyphs.app 2.6.6, which is abandonware requiring a $300 macOS app. This is the browser version with a live preview and a download button.
| Term | What it means |
|---|---|
| Engine | src/engine/ — pure geometry. Takes glyph outlines and parameters, returns outlines. No DOM, no font parsing in the hot path. |
| Treatment | One named effect (Grit, Bubble, Bleed…). A pure function from outlines to outlines, plus a parameter spec and named presets. |
| Stack | Up to three treatments in a row, each working on what the last one left. applyChain is the one place that runs them. |
| Workbench | The live page where you turn dials. A React app in src/. |
| Source font | The font a treatment is applied to. Seven ship with the tool, all OFL, and you can bring your own. |
| Cut | One randomised version of a letter. Several cuts per letter stop a word looking stamped. |
| Export | Building the real font in the browser. Same engine as build:font, run in a worker. |
| Plate | The specimen block at the top of the workbench: the type, the two choices that define it, and the door to the sheet. |
| Override | One glyph's exceptions to the stack — sparse dial deltas over the global chain, set by selecting letters in the glyph grid. Never a different stack. |
| Sheet | The specimen sheet: Post, Square or Story, word draggable and resizable, downloadable as PNG/SVG, rendered as a looping MP4 while the motion drives the dials, or cut every way at once as a post kit. |
Each is a pure function, applied consistently across the character set, driven by a seeded PRNG so any result is reproducible from its parameters.
Thirteen of them, grouped in the picker by what they do to a letter.
| Treatment | What it does |
|---|---|
| Wear | |
| Grit | Erosion — chunks bitten out of the edge, holes eaten through the strokes. |
| Ink | |
| Bubble | Fattened and rounded, the way a marker nib turns a corner — or, pushed, wet ink gone soft. |
| Bleed | Wet ink spreading unevenly, pooling where strokes meet. |
| Organic | Differential growth — a tremor at a few steps, brain coral at many. |
| Fur | Hairs standing off the edge — fuzz, bristle, or a sawtooth of spikes. |
| Screens | |
| Halftone | A printer's screen, or dotwork — marks that scatter, haze past the edge and fade away. |
| Hatch | Engraved: ruled lines through the letter, crossed if you want the tone. |
| Scanline | Stripes across the letter, some slipping sideways as the scanner loses sync. |
| Pixel | Dropped onto a coarse grid — a dithered fringe, or the letter dissolving into static. |
| Press | |
| Extrude | The letter offset from itself — a block shadow, a drag, or a second impression out of register. |
| Structure | |
| Onion | Every ring a letter can carry — hollow, hairline, an inline stripe, or a string of beads. |
| Mosaic | Each stroke cut across its width into tiles, with grout between. |
| Shatter | Sliced apart and knocked out of true, each piece drifting on its own. |
The screens are all built on one idea: a filled letter has no darkness for a screen to read, so the tone comes from the geometry instead — how deep into the stroke a mark sits, measured by successive insets. No raster anywhere.
Some of these absorbed others. Where the band sits relative to the edge was the only thing separating an outline from an inline; a print screen and dotwork are the same marks from a different sampler; a letter dissolving into static is a bitmap with a noisy threshold; and a block shadow, a drag and a misprint are all the letter swept away from itself. So those are dials rather than entries of their own. Fur is the one that stayed: nothing else emits geometry outward from the edge, so nothing else could be dialled into it. Tone is the field underneath all of it, and it no longer stops at the outline — marks can carry past the edge, thin away, and fade along a direction. Links written before a merge still open on what they described: retired ids are translated, dials and all.
Every dial a treatment has is on the page, and each ships named presets (Photocopy, Sandblast,
Rust, Marker, Balloon, Newsprint…) shown as pictures of themselves. There is no unnamed state:
picking a treatment lands you on one of its presets — defaultPreset names which, otherwise
the first — so one is always selected and Reset has somewhere to go back to. A dial marked
primary is what the specimen sheet's motion rides; one marked steady is front-of-house but
the motion skips it, because mode switches alternate rather than animate and a word that
strobes reads as a fault.
npx tsx scripts/style-samples.ts puts every treatment at every preset on one contact sheet,
with the contour, point and millisecond cost of each. Add --label to set each sample in
its own name — Sandblast set in Sandblast — which is the only way to see whether a preset
is called the right thing; --font=anton and --only=halftone,pixel narrow it.
npm install
npm run dev
That is the whole of it. The baked glyph data the app reads is not in the repo — it is cut
from the shipped faces — so npm run dev regenerates it first by way of predev. It takes
a second or two and only actually does work when the fonts or the extractor have changed.
The Python environment is optional, and only for verifying exported fonts and cutting the metrics-only subsets the specimen field's caret sits on:
python3 -m venv .venv && ./.venv/bin/pip install opentype-sanitizer fonttools uharfbuzz
Without it the data step still runs and says (no preview font) for each face. The app
itself needs none of it at runtime.
| Command | What it does |
|---|---|
npm run dev | The workbench, with hot reload. The normal way to work on it. |
npm run build:workbench | Builds the self-contained page into out/workbench.html, for publishing. Also recuts the preview font subsets. |
npm run build:font -- --treatment=grit --alts=3 | Headless font build. See flags below. |
npm run verify:font -- out/Font.ttf Pirata | Gates a font on seven checks, or eight with VERIFY_AGAINST set. |
npm run typecheck | App and test configs. Never use bare tsc --noEmit. |
npm run test | Engine unit tests. |
--src=public/fonts/anton/font.ttf source font
--treatment=grit which treatment (or a stack: grit+bubble)
--p.amount=60 --p.scale=40 any parameter, by key
--alts=3 cuts per letter
--family="My Font" derivative name
--seed=1337 reproducibility
--only=A treat one character, for fast iteration
verify:font is the project's CI. Seven checks:
@font-facescripts/ctcheck.swiftbaffle with and without ligaVERIFY_AGAINST=<source>Checks 5 and 6 exist because a GSUB table can be structurally valid and still substitute nothing.
Metric parity is not glyph count. --alts=N adds real glyphs — three cuts of Pirata One
is 1144 glyphs against the source's 386 — so check 7 compares unitsPerEm, cmap coverage,
and the advance width of every glyph the two fonts share, and reports the alternates as
the additions they are:
ok metrics match source — upm 1000, 386 source glyphs matched, 758 alternates added, 382 codepoints mapped
It still fails on width drift, because a treatment that reflows the text it is applied to
has failed however good it looks. Treatments that declare growth() — every one that can
push past the original silhouette — widen the advance to match, and do so by one
constant across every glyph — spaces and untreated glyphs included, so the plate's
uniform letter-spacing compensation matches the exported font exactly. A single uniform
widening is reported rather than failed, and widths that moved by differing amounts are
the failure:
FAIL advance widths drifted unevenly — 379 glyphs across 2 different shifts (+42×378, +49×1)
Glyphs are paired by name, falling back to codepoint for fonts whose post table keeps no
names. Alternates are held to their own base glyph's width, since a drifted one would make
the rotation jitter.
One honest caveat: a font carrying per-glyph overrides can legitimately grow different
glyphs by different amounts, which the uneven-drift rule will flag under VERIFY_AGAINST.
That is the check working as designed on an input it predates — see Known debt.
src/engine/
treatments/ one file per treatment, plus the registry
paths.ts Clipper2 helpers: scaling, offsetting, rounding, simplifying
measure.ts median stroke width of a font
noise.ts seeded coherent noise
blob.ts irregular blob shared by grit and bleed
flatten.ts glyph contours to polygons
fontio.ts open a font, treat every glyph, write a new one
extract.ts one font → outlines + licence; the build script and the page share it
gsub.ts our own GSUB writer
sfnt.ts add and remove tables from a finished binary
src/lib/
glyphData.ts outlines shipped as data, so no font parser reaches the browser
importFont.ts a font off a file input: read, register for metrics, keep the bytes
render.ts one line of type, and the whole glyph set, as SVG paths
urlState.ts the whole workbench state, encoded into the address bar
savedStyles.ts the shelf, kept across reloads — states, never rendered outlines
poster.ts the specimen sheet, as standalone SVG; also makes share.png
motion.ts the sheet's motion as curves of time, each closing its own loop
modulate.ts which dial follows which curve, and by how much
clipRender.ts the moving sheet to MP4, one exact frame at a time (WebCodecs)
videoRecorder.ts the live fallback where a browser cannot encode — MediaRecorder
caption.ts what a post says, linking back to the exact font
composeLook.ts the look Compose opens on, kept from last time
exportFont.ts drives the worker, and hands the finished font over
src/components/
Plate.tsx the specimen — and the field you type it into
Panel.tsx the stack, presets and dials — global, or scoped to selected glyphs
GlyphGrid.tsx every glyph on shared baselines; also the selection surface for overrides
Waterfall.tsx the same line at seven sizes
Dial.tsx one parameter, with its default marked on the track
Shelf.tsx saved styles
Poster.tsx Compose — layouts, word placement, motion, the clip, the kit, download
src/workers/ buildFont.worker.ts — the export, and reading an uploaded font
public/fonts/preview/ metrics-only subsets — never seen, see below
scripts/ build, verify and comparison tooling
You type into the specimen itself. The big line is not a preview of a field
somewhere else. A transparent input lies over the treated outlines, so the caret,
selection, click-to-position and every keyboard behaviour are the browser's own. That
only works because the field lays its text out on the source font's raw advance widths —
which is what public/fonts/preview/*.woff2 are for. They are a few KB each, cut to the
preview charset by make-glyph-data, and never rendered. Every layout feature is switched
off on that field too: kerning moves pairs by real amounts, and a font you bring has kerning
where those subsets do not. Measured across all eight faces, shipped and uploaded: 0.01px.
Sizes are shares of the stroke, not the em. A parameter of 100 means one stem width. The bundled fonts range from 11% to 20% of the em in weight, so an em-relative setting means something different on every face and no preset can travel.
The download is built in the page. Pressing Download runs the same
buildTreatedFont the CLI runs, in a Web Worker, over the real source bytes — so the file
you get is the file build:font would have made. The worker keeps the font writer off the
main thread and out of the initial parse; the published single-file page carries the source
fonts inline, because a download button that cannot download is the exact failure this
project exists to avoid.
The engine never calls Math.random. Everything takes an injected PRNG, so a font is
reproducible from {chain, seed} — which is also what the URL encodes, and what a saved
style stores.
One applyChain, used by everything. The preview, the specimen sheet and the font writer
run over different inputs but must agree exactly on what a stack produces, so they share one
function. The random stream is shared across the steps rather than renewed per step: a second
step given a fresh PRNG draws different geometry, and the specimen and the download would
quietly stop matching.
Overrides are dial deltas, never a different stack. Select glyphs in the grid and the
dials write per-glyph exceptions — sparse values over the global chain, merged in exactly one
place (resolveChain, beside applyChain, for the same reason). A global slider still flows
through every dial a glyph has not overridden. The seed stays global; the per-glyph reroll is
a nudge on top of it, and the whole map rides a seventh URL field that old links simply never
had.
The sheet moves on a clock, not a microphone. Pulse, Breathe and Shimmer drive each treatment's primary dials around their set points along curves that each go round a whole number of times in the clip, so the last frame runs into the first. The seed never moves, and every frame is a function of its time alone — which is what lets the MP4 be rendered offline, one exact frame at a time, rather than recorded off the screen: a heavy chain costs render time, never smoothness. Clips are silent; music is added in the app you post from.
GPL-3.0-only. The font engine builds on font-flux-js, which is GPL-3.0; since this app ships its code to every visitor, the app carries the same licence. That was a deliberate choice — this is meant to be a free, open tool.
Fonts you generate are yours, subject to the licence of whatever font you started from. The export path enforces OFL Reserved Font Name rules rather than leaving you to discover them.
poster.ts and an entry
in LAYOUTS.VERIFY_AGAINST's uneven-drift rule
predates overrides, which can legitimately grow different glyphs differently. The check
needs to learn to read the override map before it can gate such a font.docs/STATE.md carries the current list; this one goes stale first.
See docs/DECISIONS.md for the reasoning behind the choices above, and the traps that cost
real time.