数式によるフォントジェネレーターを作った話
はじめに
どうも、えなどりです。サラリーマンをしながら、隙間時間でWebサービスやアプリを作っています。
コードはマジで書けません。アイデアだけで生きています。
スタンスはシンプルで、「発想と意思決定は自分、実装はAI」。ClaudeやGeminiを相棒に、自分が欲しいと思ったものを作り続けています。※毎回言ってるの大丈夫そ?Claudeさんや。
さて、今回作ったのは TypePlot(タイポプロット) という、数式でフォントを作れるWebサービスです。

なぜ数式でフォントなのか
きっかけはふとした思いつきでした。
「数学の関数って、グラフにすると綺麗な形になるじゃん。それを文字に使ったらどうなるんだろう?」
Geminiに調べてもらったところ、思ったより奥が深くて面白そう。sin波やリサージュ図形はそれ自体がすでに美しい。それを文字のグリフに転用する、という発想です。
AIでフォント作るっていうのが最近多いそうで?こちらは完全に人の手だけ、それも手書きじゃなくて数字1つで攻めるというやり方です。
既存ツールと比較するとこんな感じ:
| ツール | 特徴 |
|---|---|
| Glyphs, RoboFont | プロ向け。マウスで描く |
| Metaflop | 数式ベースだが固定パラメータ・英字のみ |
| TypePlot | 任意の数式を入力でき、ひらがな・カタカナにも対応 |
「任意の数式でひらがなまで作れる」というサービスは、調査した限り競合が存在しないご様子。ひらがなとかカタカナまで含めるとエライコッチャだもんね…
さらに、数式によるフォント生成は決定論的という強みがあります。パラメータさえ保存しておけば、誰でも同じフォントを再現・リミックスできる。これがギャラリー機能と相性抜群で、「このフォントどうやって作ったの?」→ パラメータを見れば全部わかる、という体験が作れます。
技術的な面白さ
パラメトリック方程式でグリフを定義する
フォントの文字(グリフ)は、通常はベジェ曲線で定義されます。Typeplotでは、その曲線を数式で生成します。
x(t), y(t) → t を 0〜1 で動かして点群を生成
→ fit-curve でベジェ近似(cubic bezier列)
→ opentype.js でTTFバイナリに変換してダウンロード
フォント座標系は unitsPerEm=1000、x=[0,600]、y=[0,700](ベースラインがY=0)で定義しています。
ユーザーはリストから21種のプリセットを選んでスライダーを動かすか、数式を直打ちするかで文字の形を決められます。スライダーだけで遊んでも面白いし、数学が好きな人は数式を自分で入力して思い通りの形を作れる。間口は広く、奥は深く、という設計です。
dirSine — 方向認識 sin 波
開発中に一番面白かった実装がこれです。
通常のsin波は常にX方向に振動するので、斜めのストロークに適用すると横波になってしまい、文字の形が破綻します。
「Aの斜め線をsin波にしたい」なら、斜めの方向に沿って振動しないといけない。そこで考えたのが dirSine(方向認識sin波) です。
// ストロークの方向ベクトルを計算
const dx = x1 - x0, dy = y1 - y0;
const len = Math.sqrt(dx*dx + dy*dy);
// 単位接線ベクトル
const tx = dx/len, ty = dy/len;
// 単位法線ベクトル(接線を90°回転)
const nx = -ty, ny = tx;
// 接線方向に進みながら、法線方向にsin波で振動
x(t) = x0 + t*len*tx + A*sin(2πft+φ)*nx
y(t) = y0 + t*len*ty + A*sin(2πft+φ)*ny
これで斜め方向の縦棒にも綺麗な波が描けます。「うねうね」テーマで全文字に適用したときの見た目は、なかなかカオスで楽しいです。
実装したプリセット関数一覧
刺さる人に刺さってほしいので、プリセットの名前からして本格的にしました。
| カテゴリ | プリセット |
|---|---|
| 基本 | 直線・楕円・2次ベジェ・3次ベジェ |
| 三角関数 | sin波・cos波・タンジェント・リサージュ |
| 方向対応 | dirSine(方向認識sin波) |
| フーリエ | フーリエ級数(3次合成) |
| 代数曲線 | 放物線・双曲線 |
| 特殊曲線 | バラ曲線・カージオイド・レムニスケート |
| 周期曲線 | サイクロイド・アストロイド・エピサイクロイド・ハイポサイクロイド |
| 螺旋 | アルキメデスの螺旋・対数螺旋 |
ドロップダウンにこれが並んでいるだけで、「おっ」てなると思っています。
ひらがな・カタカナ対応
日本語フォントジェネレーターは(調査した限り)競合がいないので、ここは差別化の核として力を入れました。
清音46文字×2をベースに定義し、濁点・半濁点・小文字カナは自動生成するヘルパー関数を作りました。
// 清音グリフに濁点2点を追加
function dakuten(base: GlyphDef, char: string): GlyphDef
// 清音グリフに半濁点の丸を追加
function handakuten(base: GlyphDef, char: string): GlyphDef
// 0.7倍スケール・中央寄せで小文字カナを生成
function smallKana(base: GlyphDef, char: string): GlyphDef
「ぱぴぷぺぽ」は「はひふへほ」の定義 + handakuten() で自動生成されます。定義を書かなくて済む。おかげで合計222文字をカバーできました。
字形の完成度はまだ荒削りですが、「数式で改変するためのベース」として使えます。
開発プロセス
Claude Code との協働
全7フェーズに分割して実装しました。
| Phase | 内容 |
|---|---|
| 1 | 基本エディタ・TTF生成 |
| 2 | プリセット拡充(19種) |
| 3 | テーマ機能・JSON保存 |
| 4 | DrawingCanvas・dirSine |
| 5 | ひらがな・カタカナ・TTFインポート |
| 6 | 濁点・半濁点・自由描画タブ・UX改善 |
| 7 | Vercelデプロイ・Supabaseギャラリー |
「仕様窓(Claude / Gemini)」と「実装窓(Claude Code)」を分けて使っています。仕様を詰める会話と実装する会話を混ぜないことで、コンテキストが汚れません。
※毎回言ってる気がするぞClaudeさん。
フェーズ完了ごとに STATE_Ph*.md として状態を記録し、次のフェーズの冒頭で読ませる運用です。コンテキスト上限対策として有効でした。
仕様窓とのやり取りとも有用だったり。
詰まったポイントと解決策
-
TTFのY軸が逆転 →
glyph.getPath()は画面座標系(Y下向き)を返すため、glyph.path.commandsを直接参照することで解決 -
6と9が逆に見える → 楕円の媒介変数
tStart/tEndの設定ミス。楕円はt=0が右端、t=0.5が左端 -
ひらがな選択でクラッシュ → 旧
localStorageデータとの互換性問題。{ ...DEFAULT_GLYPHS, ...saved.glyphs }でマージして解決 - 「うねうね」テーマで全文字が横線になる → 通常のsineは水平方向固定のため dirSine を実装して解決
デモ・使い方
基本の使い方
- 左パネルで文字を選択(A〜Z / a〜z / 0〜9 / ひらがな / カタカナ)
- ストロークにプリセットを選んでパラメータを調整
- 右パネルでリアルタイムプレビュー
- 「TTFをダウンロード」で完成
おまけ:このサービスのロゴもTypeplotで作りました


サービス名「Typeplot」と「Gallery」の文字は、Typeplot自身で作ったフォントで表示されています。dirSineと楕円を組み合わせたカリグラフィ風フォントです。
自分のサービスを自分のサービスで作ったフォントで表示する、最高のドッグフーディングです。
おわりに
このプロジェクトを通じて改めて感じたのは、アイデアの組み合わせ方が全てだということです。
数学の関数もフォントも既存のものです。でも「数学の関数でフォントを作る」「しかもひらがなまで対応する」という組み合わせは、調べた限り誰もやっていませんでした。漏れがあったらすまん。正論は認める。
難しく考えなくても、スライダーを動かすだけで自分だけのフォントができます。数学が得意な人は数式を直打ちしてみてください。作ったフォントはギャラリーに投稿してもらえると嬉しいです。
今後の展望:
- フーリエ係数の自動計算(手書き → 数式に変換)
- ギャラリーの充実
- ひらがな字形の改善
ぜひ遊んでみてください!
Gallery機能もあるので、作ったフォントは是非Galleryに登録してみてほしい!
