UNIX のフォント事情

2016-07-21: このページの記述は古いうえに、(当初から)致命的に間違っている箇所があります。

今のところ気づいているのは、

特に一番目については内容が古くなったのではなく私の勘違い。すいません。

*

「 Linux はフォントが綺麗だ」という人は多いです。本当にそうでしょうか。綺麗だと思う人は Acrobat Reader のレンダリング結果と比べてみましょう。かなり汚いのが分かるはずです。実際、 Windows や Mac OS X と比べても綺麗とは言えません。 X-Window のフォント周りは多くの問題を抱えています。

X-Window のフォントシステム

X-Window のフォントシステムは大きく分けて 2 種類あります。

Xcore

昔から X-Window に備わっているフォントシステム。 X サーバ側でラスタライズを行います。アンチエイリアスなどは不可能。 "-misc-fixed-r-normal..." みたいなフォント指定をするのはこれです。今回は触れません。

freetype & cairo, Xft & Xrender, glitz...

新しいフォントシステム。クライアント側でラスタライズを行います。アンチエイリアスが可能。 GTK2, Qt を用いたソフトウェアのほんどは、こちらを利用しています。いくつかのライブラリからなっており、分担は以下の通りです。

cairo
グラフィックスライブラリ
pango
組版ライブラリ
freetype
フォントラスタライザ

ちなみにあまり知られていない気もしますが、 Xft は既に obsolete です。 cairo はフォントレンダリングに Xft を使用しません。(ただし obsolete とは言っても Firefox を筆頭に、 Xft を使っているプログラムは多くあります)

フォントのラスタライズに関する一般的な話

フォントのラスタライズとは一言でいえば、ベクトルデータをラスタデータに落とすことです。

Truetype フォントは二次、 Type1 フォントは三次のパラメトリック曲線で構成されています。ベジェ曲線とか B スプライン曲線とか言われるやつです。 Illustlator などのドロー系ソフトを思い浮かべれば分かり易いと思います。ここで終われば話は楽(?)なのですが、フォント特有の処理がいくつかあり、それが品質を大きく左右します。

ヒンティング

フォントは小さな領域に複雑な形状を含みます。とくに一般的な PC のディスプレイはせいぜい 100 px/inch 程度で、形状の複雑さに対して十分な数の画素が対応しません。そのまま二値のラスタデータに落とすと、見るに耐えないガタガタの画像が出力されてしまいます。そのため、あらかじめベクトルデータをグリッドにフィットさせるような処理が必要です。これがヒンティングと呼ばれる処理です。実際には単純なフィッティングではなく、線の太さや間隔が一定になるように、など細かい処理が行われます。行われていると思います。たぶん。僕はヒンティングの詳細を知りません。ごめんなさい。

TrueType フォントはこの処理を行うため、なんと VM のコードを積んでいます(参考: http://developer.apple.com/textfonts/TTRefMan/RM05/Chap5.html )。 PostScript のことを考えれば、それほど驚くことでもないのかもしれませんが、やっぱり恐ろしい。 freetype は後述する特許の関係で、この情報を無視して自分でヒント情報を生成する機構を持っています。

ただ個人的には、後述のγ補正をきちんと行えば、もうヒンティングは不必要な処理ではないかと思っています。実際、 Mac OS X や Adobe Flash のフォントエンジンはヒンティングを行っていないようです。

カーニング

一般にフォントは各文字ごとに幅が異なります。さらに "VA" と "XV" を見ると分かるように、文字の組み合わせによっても間隔は変化します。場合によっては一部が隣の文字に食い込むこともあります。このフォントの組み合わせごとに間隔を変える処理がカーニングです。上述のヒンティングによってフォントの幅が変わるなど、ヒンティングとカーニング処理は密接に関わっています。この情報はフォントに含まれています。

サブピクセルサンプリング

Microsoft の ClearType, Adobe の CoolType と同義です。液晶ディスプレイなどは、画素が "R G B R G B..." と横に並んでいます。これを利用して、横方向の解像度を擬似的に上げる技術です。

単純に横方向の画素数を三倍にしてサブピクセルに割り当てるだけではエッジに色が付き、使い物になりません。そのため、 RGB が局所的に均等になるよう分散させるフィルタをかけます(参考: http://www.grc.com/cttech.htm)。このフィルタはローパスフィルタとして働くため、サブピクセルサンプリングの宣伝文句である「解像度が三倍」というのは正しくなかったりします。 cairo, Xft は特許の関係もあり、正しいフィルタリングが行われていません。

ガンマ補正

CRT ディスプレイの画素は入力に比例した明るさで光るわけではありません。具体的には

dst = src ^ 2.2

の関係があります(正確には 0 付近での補正が入ります)。この 2.2 という値は sRGB という規格で標準化されていて、ある程度のグレードのディスプレイは、このカーブを再現するように作られています。アンチエイリアスのかかったフォントのレンダリングはこのガンマカーブを考慮して行われなければなりません。ガンマ補正を考慮した場合としない場合とでは、中間値で 127 ⇔ 186 と大きな違いがあります。

これはもちろんフォント固有の問題ではありません。しかし、普通の図形描画ではこの補正をしなくてもあまり影響はないのに対し、少ないピクセルに複雑な図形が描かれるフォントレンダリングでは視覚的に大きく影響します。

X-Window のフォントが汚い理由

さて本題です。

ヒンティングの問題

上述のようにヒンティングに必要な情報はフォントが持っていますが、特許の関係で freetype はこの情報を利用せず、自分でヒント情報を生成する機構を持っています。この生成機構自体はなかなか優秀で、フォントが持っているヒント情報よりもずっと良い結果を出すことも多いです。しかし現状、 fonts.conf の設定をいじる程度ではこの機構を十分に生かせません。ヒンティングのアルゴリズム自体は優秀のようですが、渡しているオプションが適切でないように思います。後述するように freetype への情報の受け渡し部分にも問題があるのですが。

また、サブピクセルサンプリング有効時はヒンティングもサブピクセル単位で行われるべきですが、現状ピクセル単位で行われています。これではサブピクセルサンプリングの意味がありません。これも問題です。

サブピクセルサンプリングの問題

freetype & cairo, Xft でサブピクセルサンプリングを有効にすると、色づきが激しくて使いものになりません。これは cairo, Xft でのフィルタリング処理が正しくないからです。ここにも特許の問題が絡んでいてややこしいのですが、 freetype-2.3 以降には正しいフィルタが実装されています。ただし cairo, Xft はこれを利用していません。 freetype の開発者の一人、 David Turner 氏が非公式にパッチを出し、 cairo の ML にも投稿していますが、採り入れられた形跡はありません。

ガンマ補正の問題

現状 cairo, Xft ではガンマ補正が行われていません。個人的にフォントが汚く見える一番の原因はこれだと思っているので、ぜひとも実装したいところです。しかし実は、 cairo, Xft のレイヤでガンマ補正を実装することはできません。

ガンマ補正は

fg : 前景色
bg : 背景色
α : フォントのラスタデータ
dst = ( fg^γ * α + bg^γ * (1-α) ) ^ (1/γ)

という式になります。ここで問題なのは、前景色、背景色を知っていないと補正ができないことです。 cairo の Xrender バックエンドは、サーバにラスタデータを転送しておいてサーバ側で合成するため、背景色を知ることができません。原理上 cairo でガンマ補正はできないのです。ガンマ補正を正確に実装するためには X サーバに手を入れる必要があります。誰かやりませんか…。

またガンマ補正を入れる場合、もとの線形な情報は 8-bit では全然足りないという問題もあります。

実装上の問題

ここの記述は間違いです。 FreeType のリファレンスにある通り、 FT_Load_Glyph() と FT_Render_Glyph() に異なる値を指定することで直交した指定が可能です。

freetype, cairo のソースを見れば分かるのですが、なかなかの汚さです(オープンソースプロジェクトは大抵汚い、という話もありますが。暴言? )。特に cairo はそう感じます。 API は綺麗で使いやすいのに…。それはともかく、明らかに開発者の意図通り動いていないところ、開発者自身も理解して書いていないのではないかと思えるところがたくさんあります。コアは優秀なのに正しく使われていないために、残念な結果が出ているように思います。分かり易い例だと、

/usr/*/include/freetype/freetype.h:

typedef enum  FT_Render_Mode_
{
    FT_RENDER_MODE_NORMAL = 0,
    FT_RENDER_MODE_LIGHT,
    FT_RENDER_MODE_MONO,
    FT_RENDER_MODE_LCD,
    FT_RENDER_MODE_LCD_V,

    FT_RENDER_MODE_MAX

} FT_Render_Mode;

NORMAL, LIGHT の指定と LCD, LCD_V の指定は本来直交しているはずです。しかしこの定義では同時に指定することができません。いまさら変更できませんが。さらに問題なのは、開発者がこのことを理解していないと思われるコードがある( FT_RENDER_MODE_NORMAL | FT_RENDER_MODE_LCD )のような指定を行っている部分がある)ことです。ちなみに、 cairo では LIGHT の指定は無条件に無視されてしまいます。

そんなこんなで

土日を潰してコードを追っていたのですが、一番の目的だったガンマ補正を実装できないことに気づいてガッカリです。代わりにヒンティング周りの改善がうまくいったので、パッチをおいておきます。

[[ここにスクリーンショット]]

パッチの内容は

などなど。このうち cairo, Xft パッチの中身はほとんど Turner 氏のパッチです。ただしバグフィックスを含んでいるので、必ず全てのパッチを当ててください。 autohint=true, hintstyle=slight での使用を強く推奨します。

fonts.conf の例:

LCD 向け
fonts-lcd.conf
CRT 向け
fonts-crt.conf

通常は ~/.fonts.conf に置けば読み込まれますが、 Linux のディストリビュージョンによっては fonts.conf がカスタマイズされていて、うまく設定が反映されないかもしれません。

TODO

最後に

あまりフォント周りのことを簡潔にまとめてある文章がないと以前から感じていたので、ちょっと書いてみました。フォントのレンダリングは、普通の人が思っているよりずっと複雑なものです。僕も上に書いた程度の表面的なことしか知りませんが、実際にはベジェ曲線のラスタライズ一つとってみても、多種多様なアルゴリズムが提唱されているはずです。ほとんどの人にとっては、その内容を知ったところで何の得にもなりませんが、「たかがフォント」にも様々な技術が使われていることを知っていただければ幸いです。

参考

http://antigrain.com/research/font_rasterization/index.html|
より細かい話が載っています。この文章を書いた後に見つけたのですが、これがあれば僕の文章は必要なかったんじゃ…。
http://lists.gnu.org/archive/html/freetype/2007-07/msg00007.html
freetype の ML より。ほとんど ML は読んでいないのですが、たまたま目に入ったので紹介。後半、 Turner 氏が「もし時間とやる気があったら…」という感じで改善したい項目を列挙した後、

Alas, I lack the time and motivation to do all this work. (中略) I have provided many patches in the past that are mostly ignored by the corresponding library's maintainers (but are surprisingly included by distribution package managers), and I'm quite tired of this.

あと、このメールに X サーバに手を入れないとガンマ補正は無理だと書いてありますね。ソース追う必要なんてなかった…。
http://developer.apple.com/textfonts/TTRefMan/
TrueType フォントの仕様書。読んでないけどヒンティングが凄まじいことは分かった。

戻る

y.fujii <y-fujii at mimosa-pudica.net>