万年筆の⽣々しい濃淡はどう作られた?「イワタつづり」インク溜まり⽣成の⾃動化スクリプトを徹底解説(前半)
こんにちは。イワタ開発部の狩野です。
今⽉のイワタnoteでは、イワタテックノート番外編として、4⽉10⽇に発売した新書体「イワタつづり」のカラーフォントのインク溜まり(⾊が濃くなっている部分のこと。「イワタつづりLayer」としても製品化)をどのようにして作成したかについて、編集補助プログラムを開発したエンジニアの⽴場からお話しします。

また、本テーマは詳細な技術解説や開発のプロセスを含み、内容が多岐にわたるため、「前半」と「後半」の2回に分けてお届けします。
前半となる今回は、膨大な手作業を自動化するべくプログラム開発に至った経緯と、ツールの概要、そしてインク溜まり生成の基本となる「交差点」の処理アルゴリズムについて解説します。
難しくなりがちな技術解説の部分は図解を多用し、読まなくても、見れば分かる説明を心がけました。説明が難しいと感じたら、文章を飛ばして先に図だけを最後まで見て、まず全体の流れを掴むことをお奨めします。
1. プログラム開発の経緯
「イワタつづり」開発初期の2022年6月、まだ「つづり」という書体名は影も形もなく、「万年筆書体」と呼んでいた頃のことです。
「せっかく万年筆風なのだから、インクの濃淡も表現できたら面白いよね」という社内会議の一コマをきっかけに、本書体のデザイン担当の坂口が作成したサンプルを見て、感動しました。
万年筆という筆記具にこだわった所から生まれた発想を形にした、数文字のサンプルでしたが、最初の1文字からすぐにその可能性を理解できました。

(字形・インク溜まりの形とも開発途中のバージョンですが、インク溜まりの形を見やすくするためコントラストを上げて表示しています)
2色で色分けされているだけなのに、小さなサイズで見ると、本当に万年筆で書いた筆圧が加わっているように生々しく感じられたのです。

(色・デザインとも開発途中のバージョンそのままですが、改行位置のみ調整しています)
アウトラインの中は黒一色で塗りつぶされるもの、という常識を打ち破るこの感動を皆様に伝えたい、ぜひ製品として実現しなくてはならない、エンジニアとしてそう考えました。
しかし同時に⽬の前の⽂字のインク溜まりを数えたところ、インク溜まりの総数は概算で1フォントあたり約20万個にのぼりました(後に2ウェイトに増えたので、実際に作成したのはその倍です)。
⼿作業では絶対に完成しないと思い、ならば⾃動化処理でやるしかないだろうと。その困難さには⽬をつぶり、密かに決⼼しました。
同年(2022年)の秋口にプログラム開発に挑戦しましたが、当時はまだ経験が足りず、開始まもなく行き詰まりました。
過去「イワタ福まるご」の開発時にも、Glyphs(フォント編集ソフトウェア)のデータを解析し、パス(1本の閉じた曲線)のエラー箇所を検出するプログラムの作成は経験していましたが、今回は遥かに複雑です。
パスを構成するベジェ曲線1本1本の形を見るだけではなく、向かい合った2本のベジェ曲線の形状からペン先の動きを推測する必要があるのです。
それでもフォントでインク溜まりを表現するという夢を諦めきれず、完成品に近いフォントのアウトラインデータが蓄積してきた2024年の6⽉に再び自動化に挑み始めました。
今度は事前の準備期間を置き、Processingという画像⽣成に特化したプログラミング⾔語で線の端を切り抜く処理を書き起こしていきました。切り抜く場所を求める作図処理の途中で引いた補助線などを逐⼀⽬で確認できるようになり、やっと開発が進み出しました。
当初の構想にあった切り出し⽅法は未完成でしたが、ある程度成果が蓄積していた基本処理の部分を2024年10⽉からGlyphsのPythonマクロに移植し、新たな⼯夫を加えて実際の切り抜き処理を作成していきました。
一時は完成も危ぶまれましたが2025年1⽉の終わりに最初の原型のデモにこぎつけ、カラーフォントの開発が決定しました。完全⾃動化ではなく⼈間が適宜補正しながら進める割り切ったワークフローに切り替えたことが功を奏しました。
プログラムがエラーで中断することなく処理できるようになったのは5⽉。
その後通常版の「イワタつづり」のデザインが完了してカラーフォントの編集作業がいよいよ始まる7⽉半ばまで性能や精度向上を重ねてプログラム開発は完了となりました。
その後は10⽉頭にパラメータ調整をしたRegular⽤を提供、11⽉末まで⼤勢で⼿分けしてインク溜まりのデザインを整備しました。技術部によるエンジニアリング、前例のないフォントの動作検証などを経て、2026年4⽉10⽇(フォントの⽇)にカラーフォントやLayerフォントを含んだ「イワタつづり」を発売することができました。
2. 成果物の概要
インク溜まり生成に伴う工程を軽減するため、以下のような操作画⾯をもつGlyphsマクロを作成しました。

処理したい⽂字を選択してメニューの順番通り[処理を実⾏]ボタンを押していけば⾃動的にインク溜まりのパターンが設定されたカラーの⽂字が出来上がるように作られていますが、実際には各段階で意図通りにパターンが形成されるとは限りませんので、⾃動処理の結果を⼿で調整するか、アンドゥで⼀段階戻って、⾃動処理が正しく動くようなデータ修正を⾏う必要があります。
例えば、図4は「6.線の切り⼝にカマボコ形の膨らみを追加」の⾃動処理を⾏った直後ですが、⽂字の左下隅を切り抜いたパーツの縦画方向は、カマボコ形(=インク溜まりの膨らみ)を付け加えるべき所、追加処理が実行されず平らなままになっています。実は、直線のように⾒えるこの切り⼝部分は、Glyphsに切り抜き処理をさせた時に余分な点が追加されたために折れ線になっているのです。
余分な点を削除してからカマボコ形を付ける処理を再度実⾏すれば、ストロークが折れ曲がる⾓の両側にカマボコ形のインク溜まりの形状が付け加えられます。

3. 技術解説
ここからはいよいよ技術的な解説に入っていきます。
インク溜まりの形状⽣成をプログラム化するにあたって、若⼲の割り切り・単純化を⾏っています。万年筆のインクは、ペン先の動きが遅くなる所と画が重なる所に余分に溜まるという仮定のもとに、
① 2本の画線の交差点(⼗字路と丁字路)
② 画線の始点と終点
③ 画線が折れる場所
の3箇所の周辺を切り出し、線の切り⼝にカマボコ状の図形を追加してインク溜まりの形を⽣成することとしました。
3.1. 交差点の検出方法
「イワタつづり」の⽂字(一部の記号類を除く)は、ストロークごとに分割してデザイン作業を⾏っていますが、この時点ではアウトラインの⾄る所が滑らかなパスからできています。Glyphsで選択するとスムースポイント(図6:緑⾊の●)だけでできていることが分かります。
フォント化のための最終仕上げの段階で、重なり合った複数のストロークを合体して1本のパスにまとめる処理を行います。その時、2本の画線の交差点部分に鋭く尖った⾓が⽣まれます。そこは、2本のベジェ曲線が、つなぎ⽬で突然⽅向転換している場所(コーナーポイント)になっています(図6:⻘⾊の■)。しかも、交点の周りの⾓は凹⾓(180°より⼤きい⾓)になっています。

図6(右)には⾚丸で囲んだ2箇所に交差点があります。交差点の角の座標(各2個または4個)がバラバラな順番で与えられたとき、交差点がどこにいくつあるかを検出するにはどうすればいいでしょうか。
図6の例の場合、6個しか凹⾓が無いので座標値が近い(相互に距離が近い)ものをまとめていけば簡単に、交差点ごとに角のまとまり(クラスタ)を2つに分離することができます。
しかしもっと複雑な場合も考えられます。
交差点がたくさんあったり、それらが密集していたり、3本以上の画線が1箇所で、三叉路・五叉路といった複雑な交差点を作っていたら…?
幸いなことに、「イワタつづり」では3本以上の画線が1箇所に集まることがないようにデザイン上配慮されています。1箇所に集中すると、そこに「⿊みが溜まる」ので、ほんの少しだけズラしてあるのです。例えば下の図7の「⽶」では、2画⽬の左払いが速書きの影響で縦画に交差していますが、横画と縦画の交点のギリギリ上をうまく通しています。
各交差点は⾮常に近い位置にありますが、それぞれ4個または2個の凹角の集まりが⼗字路か丁字路を構成しており、それぞれの交差点領域をきれいに分離可能です。そういうわけで、全ての交差点は⼗字路か丁字路であると仮定して、先に進むことにしましょう。

これらの点をどのようにしてクラスタ化すればいいでしょうか。交差点を構成する点どうしの距離より、隣の交差点の点との距離のほうが近い所もあるので、単純に座標の近さでまとめるわけにはいきません。
単純な例を図8に挙げれば、4個の点の配置が全く同じでも「1個の⼗字路」または「2個の丁字路」の両⽅の可能性があり得るため、それぞれの点に付随する凹⾓の情報を考慮に⼊れないと正しくクラスタ化することができません。

しかしもう⼀つ幸いなことに、この書体は画線の太さがほとんど⼀定です(線幅は標準線幅の1.5倍以内に収まると⾒ていいでしょう)。
なので、各凹⾓を標準線幅の半分だけ内側に寄せる前処理を行えばそれらの点がほぼ同じ場所に収束し、容易にクラスタ化できるようになるはずです。

各凹⾓を標準線幅の半分だけ内側に移動させると、⼗字路の中央にはそれぞれ4個ある点が、あまりに近すぎて3個しか⾒えないほど近づきます。
一方、丁字路は丸みを持った線の端が別の線に交差しているので、若⼲の誤差が⽣じています。とはいえ、これだけ凝集していれば「相互の距離が線幅よりも近い」という条件だけでクラスタ化しても何の問題もありません。
このようにして、⽂字に含まれる凹⾓をクラスタ化して、各交差点を検出することができました。
3.2. 交差点の近くを垂直に切る
交差点が検出できたら、その近くを垂直に切ります。
インク溜まりの形が⾃然になるためには、できるだけ短く切りたいのですが、あまり短く切ると、整数値への丸め誤差のために曲線が⼤きく歪んでしまいます。両者の妥協の結果、凹⾓から直線距離で5メッシュだけ離れた点(つまり、凹⾓を中⼼とする半径5の円が元のパスと交差する場所)を切断位置とすることにしました。その地点からパスの法線(パスの⽅向に対し垂直な直線)を引き、ストロークの反対側のパスと交差する位置を求めるのです。
画線が斜めに交差している場合、出発点の選択は要注意です。交差点の内部に法線が⼊り込まないような出発点を選ぶ必要があります。⼗字路の四隅にできた切れ込み(凹⾓の残りの部分)のうち鋭角になっている方の2つを選び、その2つから両サイドに法線を引けば、その延⻑線は交差点から離れる向きなので、アウトラインと再度交差する場所は同じ筆画の反対側のパス上に到達することが期待できます。下の図10の場合は、左図のように左上や右下の凹⾓を選ぶのが正しく、右図のように外側が鈍⾓になっている左上や右下の凹⾓付近から法線を引くと内部を突き抜けてしまいますので、交差点の形を正しく切り出すことが出来ません。

交点を⾒つけることができたら、その座標(⼩数の端数を含んでいます)をそれぞれ⻑さ1メッシュだけ延⻑します。(丁字路の場合、Tの頭には線幅の1割延⻑します)。
その後、それらを直線でつなぎ合わせて多角形(ポリゴン)を作成します。これが、切り抜き場所を⽰すマスクとなります。パスの切り抜き処理はGlyphsの機能を使うので、スクリプトで⾏うのはポリゴンの座標計算までです。⼩数値を含むポリゴンでも、切り抜き時にGlyphsが⾃動的に座標を整数値に丸めてくれます。

前半はここまでとなります。
今回は、インク溜まり生成プログラムの開発経緯から、全体の中では比較的ロジックを組みやすかった「交差点」の検出・切断アルゴリズムまでを解説しました。
後半では、本開発において最も苦労し、今なお完全な自動化には至っていない最大の課題「線の端(始筆・収筆)」の検出処理について解説します。
また、プログラムによる算出の限界に対して、最終的なフォントの品質を担保したデザイナー陣の精緻な修正作業(美意識と執念)についても触れる予定です。
後半も以下リンクよりぜひご覧いただければ幸いです。
付録:用語解説
① 凹角と凸角

本文中の説明では凹角を「180°より大きい角」と簡単に定義しましたが、それが「凹角」 と呼ばれるのは、多角形の凹んだ所に必ず出現する角だからです。
凹んだ所が存在しない多角形(凸多角形)では、多角形上のどの2点を結ぶ線分も図形の内部に収まり、外側にはみ出すことは決してありません。一方、凹んだ部分がある多角形(凹多角形)では、図形内の点同士を結んだ線分が図形の外側に飛び出すことがあります(図A左下の赤線部分が一例)。
そのとき、赤線がつないだ図形の輪郭上の2点の中間には必ず凹んだ角が存在します(中下図の赤丸の部分)。緑色の◯で囲まれた角は90°であるのに対し、赤色の◯の部分は4分の3が水色に塗りつぶされていて、直角3つ分の270°の角になっています。角をだんだん大きくしていくと、全く曲がりのない直線上の点がなす角である180°(平角)を境にして、凸角が凹角に変わります。
さて、上の図に示した多角形の輪郭線上を1周してみることにしましょう。ここではフォントデータの慣習に従い、反時計回り(より正確に言えば、塗り潰す側が左手側)に回ります。凸多角形では常に左に曲がり続けるのに対し、凹多角形では凹角で右側に曲がります。曲がる角度(左側に曲がる時にプラス、右側に曲がる時にマイナスとして)をすべて足し合わせると360°になります(これは多角形に限らず、曲線を含む図形でも同じで、外周を1周すれば必ず左回りに360°、ちょうど1回転だけ旋回します)。
ちなみに、凹角は英語ではreflex angleまたは reentrant angleという呼び方が一般的(「凹んだ角」を直訳したconvex angleという言い方は稀)ですが、プログラムの中で何度も使うには長すぎるので、V字形の切り込みを表すノッチ(notch)という単語を使っています。
② 法線と接線
法線(normal)とは、曲線上の1点において、その接線(tangent)に直交する直線のことです。ラフな言い方をすると、曲線上のある1点から立てた垂線のことです。
一般には、法線ベクトルの向きは図形の外側に向くように取ることが多いのですが、このプログラムの自動処理では、画線を垂直に切断する場所を探すため、それを近似した初期値として法線を使うので、内向きの法線 (inward normal)のほうが重要です。
接線・法線とも、滑らかでない箇所(コーナーポイントなど)では一意に定めることができません。直線とベジェ曲線を連結して描かれている図形では、その点に至る直前までの進行方向と、その点が直後に向かう進行方向が一致しない場合があります。
そのような場合、便宜的に2つのベクトルの向かう角度のちょうど中間の角度を取ると処理を破綻せず進められることがあります(図4の処理もこの方式で算出したベクトルを利用しています)。
