にせねこメモ

はてなダイアリーがUTF-8じゃないので移ってきました。

フォントのアウトラインを法線方向に太らせたり細らせたりしてみる

フォントのアウトラインの制御点を法線方向に沿って動かしたら文字の線をうまいこと太らせたり細らせたりできるのではないかと思ってやってみて、結果小さい移動量においてはそこそこになったように思う。

読まなくてもいい前書き

現在一般のコンピュータで使われているフォントは、文字を表す図形の輪郭をベクタフォーマットで収録したアウトラインフォントである。そのため、線の太さはアウトライン毎に固定され、線の太さを変えたければ別のアウトラインを用意するしかない*1
そこで、一つの書体のアウトラインを変形して文字の線の太さをうまいこと変化させられれば、太さの違う書体を用意しなくてもよいということになる*2


以下は、フォントのアウトラインの制御点を法線方向に沿って動かせばうまいこと太らせたり細らせたりできるんじゃないか!?と思いついたのでやってみたという記録である。恐らくこのような処理を実装しているソフトウェアはあるだろうし、これよりさらに良いアルゴリズムもあるだろうので、歪な車輪の再発明なのではという気はする。

ここで、アウトラインを「フォントの」と限定しているのは、アウトラインフォントに含まれるcontourがすべてclosed pathであり、open pathがないからという理由である。すなわち、contourによって表現される図形の外側と内側がはっきりとしているため、法線ベクトルを一意に定めることができる。

OpenTypeフォントではアウトラインを直線およびベジエ(Bézier)曲線の集合によって表現する。OpenTypeにはアウトラインデータの格納方式としてCFF形式とTrueType形式を選ぶことができるが、PostScript由来のCFFベースのもの(一般にこれが「OpenTypeフォント」と呼ばれる)では3次ベジエ曲線が使われ、TrueTypeベースのもの(これは一般に「TrueTypeフォント」とよばれている)は2次ベジエ曲線が曲線の表現に使われている。
この記事においてはTrueType形式のものについて扱うが、CFF形式の方でも同様に操作可能であると考えられる。

一続きの直線あるいはベジエ曲線によって表現される図形をcontourとよび、制御点列によって表現される。一つのグリフ(フォントによって描画を行う際の操作単位で、大抵は文字と一対一で対応付けられる)は0個以上のcontourからなる。

手法

一般に点に対して法線は求まらないが、ここでは、制御点の法線を、その制御点に隣接する2制御点とその制御点のなす2辺について、それぞれの単位法線ベクトルを足し合わせた方向のものとする。
つまり、 m個の制御点からなるあるcontourについて、制御点が \boldsymbol{p}_{k-1},\ \boldsymbol{p}_k,\ \boldsymbol{p}_{k+1} ( 0 \le k \le m-1)の順で並んでいたとき( \boldsymbol{p}_{-1} = \boldsymbol{p}_{m-1},\ \boldsymbol{p}_{m} = \boldsymbol{p}_{0}とする)、点 \boldsymbol{p}_{k}における法線ベクトル \boldsymbol{n}_{\boldsymbol{p}k}は、2点 \boldsymbol{p}_{k-1},  \boldsymbol{p}_{k}を通る直線の単位法線ベクトル \boldsymbol{n}_{k-1}と、2点 \boldsymbol{p}_{k},  \boldsymbol{p}_{k+1}を通る直線の単位法線ベクトル \boldsymbol{n}_{k}を用いて、
 \boldsymbol{n}_{\boldsymbol{p}k} = s(\boldsymbol{n}_{k-1} +  \boldsymbol{n}_{k} ),\ s > 0
と表せる。
なお、 s \displaystyle s = \frac{1}{\|\boldsymbol{n}_{k-1} + \boldsymbol{n}_{k}\|}と定めると単位ベクトルになる( \|\cdot\|ユークリッドノルムを指す)。
f:id:nixeneko:20171211190003p:plain
TrueTypeアウトラインにおいて、contourの制御点を順番にたどっていった時の右手側が塗りつぶされることになると決まっているので、 \boldsymbol{n}_{k} = (x_k,\ y_k)^\topとしたとき、 \boldsymbol{n}_{k}は、スクリーン座標系(左上原点)において次のように計算できる。
 \displaystyle \boldsymbol{n}_{k} =\frac{1}{\| n_k - n_{k-1} \|} (y_k - y_{k-1},\  x_{k-1} - x_k )^\top

ここで、フォントの座標系は左下原点だがスクリーン座標系は左上原点であり、y軸正方向が反転していることに気を付ける。

実験1

各頂点における単位法線ベクトルに沿って移動させ、色を変えて描画した図を次に示す。黒が元々のアウトラインであり、外側に移動させたものは緑~赤、内側に移動させたもの緑~青で描画している。
f:id:nixeneko:20171211230418p:plain
見ればわかる様に、頂点の前後の辺がなす角度に関わらず頂点の移動量が一定のため、全体として形が崩れていると感じられる。

実験2

次に、 s
 \displaystyle s = \frac{1}{1+\cos\theta} ( \thetaはその頂点を通る2辺の法線のなす角、上図参照)
とした場合の法線ベクトルの定数倍制御点を移動させてみる。
f:id:nixeneko:20171211231642p:plain
そうすると、移動量が大きい部分については自己交差して破綻している部分もあるが、小さい移動量においては割と全体的の形を保ったまま移動できているように思う。

ちなみに、 \displaystyle s = \frac{1}{1+\cos\theta}というのは、2辺の法線のなす角度が \theta = 0のときは \displaystyle s = \frac{1}{2} \displaystyle \theta = \frac{\pi}{2}のときは s = 1となるように定めた。

コード

描画に使用したPython 3コードを次に挙げる。描画に用いたフォントはM+フォントのmplus-1p-regular.ttfである。
フォントの読み込み~アウトラインの制御点列の抽出はFontToolsを利用した。
アウトラインの描画・画像の出力はPillowを利用した。
gist.github.com
制御点座標の操作はsetで表して泥臭くやったが、Google関わっているフォント系のライブラリにおいては制御点の表現に複素数型を使っていたし、あるいはNumPyのndarrayなどによって制御点を表すと座標計算が楽になるのではないかと思う。

考察

ベジエ曲線は凸包性(convex hull property)をもつ。凸包性とは、(n+1)個の制御点によって定義されるn次ベジエ曲線が(n+1)個の制御点の凸包(convex hull)の内部に含まれるという性質であり、要するに制御点がなす多角形によって曲線のだいたいの形が推測できるということで、これによって、ベジエ曲線の集まりで構成されるcontourについて、制御点のなす多角形(polygon)が均等に太るように動かすとそれによって表現されるベジエ曲線等もまあまあ均等に太るようになっている気がする。
何にせよ、ベジエ曲線(からなる図形)の操作に多角形の操作手法が適用できるというのは言えるのではないかと思われる。3DCGでオブジェクトをメッシュの法線方向に拡縮する手法があるので、それを適用してみてもいいかもしれない。

調べたら次のサイトが引っかかった。

このサイトによると、ベジエ曲線をオフセットした曲線を(同次)のベジエ曲線で表現することは不可能である、とのことであり、適当に近似するしかない。紹介されている手法では、適当にベジエ曲線を分割し、それらに対して法線方向に移動するというものである。

次のサイトでもいくつかの手法が紹介されているが、制御点を移動してみて、精度が十分でなかったら分割するという方針らしい。

結局、太らせたり細らせたりする方向にベジエ曲線を移動したものを同じ制御点数で正確に表現できないので、本手法では限界があるようではある。しかし、本手法で楽なのは、制御点の数が変化せず、また曲線を分割する必要がないため単純に前後の制御点をみて移動させるだけで済むという部分である。自己交差さえ何とかなれば何かに使えるかもしれない。

*1:2016年にリリースされたOpenType 1.8で導入されたvaribale fontではユーザーが書体の線の太さ等を動的に変えられるが、フォントデータには太さ等の違う2種類以上のアウトライン(に相当する)情報を含ませる必要がある。

*2:実際には、市販の書体ファミリー、特に極太書体では、字が潰れない様に部分的に細くしたりバランスを整えるために再配置したり要素を融合させたりといった調整を行っているため、機械的な太さの変更では一般に品質は低下すると考えられる。