Fontconfig (7日目) - DPI

一週間経って未だに設定らしい設定をしていないという事実……!! さて、今回も普段であれば設定なんかしないDPIの話です。

DPIはもちろんDots Per Inchで、Dotsの代わりにPixelsでPPIと呼ばれることもあります。 1インチが何ドットに対応するかという比率ですね。

ディスプレイのDPI

ディスプレイ毎の値は xdpyinfo で確認したり xrandr~/.Xresources で設定できたりします。 (複数のディスプレイを接続した環境でディスプレイ毎に異なるDPIを設定するのは難しいらしいです。 やったことはないです。)

高解像度ディスプレイ等ではない普通のディスプレイのDPIは96です。 もちろん物理的なDPIはばらつきますが、大概のディスプレイは問い合わせに対して誤差を無視して96と返してきますのでだいたい96です。

$ xdpyinfo | grep resolution
  resolution:    96x96 dots per inch

xdpyinfo では物理サイズも取得できますので本当の物理的なDPIを計算して設定してやることもできますが、96を仮定しているアプリケーションも多いでしょうから誤差程度であれば96のままにしておいたほうが良いです。

高解像度ディスプレイですとスケーリング設定にもよりますがもっと大きい値になるはずです。

フォントサイズ計算時のDPI

アプリケーションにもよりますがフォントのサイズ設定ではピクセルサイズ(単位px)で直接設定するよりもポイント(単位pt)という単位で設定することが多いです。

ポイントとピクセルサイズの関係は

\[ピクセルサイズ_{px} = \frac{DPIの値_{px/inch} \times ポイントサイズ_{pt}}{72_{pt/inch}}\]

となっています。

この72という定数は昔のMacではDPIが72のディスプレイが標準だったことに由来するそうです。 今ではMacはRetinaで300を超えるのが当たり前ですし、そうでなくても普通のディスプレイのDPIは96です。 それでもMacではフォントサイズを計算するときは今でもDPIは72です。 72ですとポイントサイズとピクセルサイズが一致して何かと楽です。

WindowsやLinux(といいますかX Window)ですと特に設定等しなければフォントサイズを計算するDPIも96です。

DPIが96ですとポイント単位でフォントサイズを設定するとき困るんですよね。 9ptは12px、12ptは16pxになりますが、実際に使われているフォントサイズはその間の10ptや11ptが多いです。 そして切り上げ切り捨てまたは小数のままで計算してくれる等の動作がアプリケーションごとにまちまちだったりして同じサイズを設定しても揃わなかったり。 全角/半角のある日本語ではフォントサイズはなるべく偶数ピクセルにしたいところですが14pxは10.5ptになりますし。 そもそも小数を指定できないアプリケーションもありますから……。

同じフォントを指定しているのに高さが違って見えるぞという時に疑うべき原因のひとつです。 (ここでは述べませんが悲しいことに疑うべき原因は他にもあり簡単には特定できません。)

で、昨日KDEのフォント設定画面で72に設定したDPIは(ディスプレイのDPIではなく)フォントのサイズ計算だけの仮想的なDPIです。 ディスプレイ自体のDPIは96のままです。

この値は ~/.config/kcmfonts に記録されていますのでKDEライブラリを使っていないアプリケーションには反映されない……と思ったら、VLC(QtアプリだがKDEアプリではない)やFirefox(GTK3)にもちゃんと反映されてるっぽいですね……? ま、まあ、どうにかこうにかして上手く値を伝えてくれているのでしょう。

Fontconfigの把握しているDPI

Fontconfigも Ubuntu-12 または Ubuntu:size=12 みたいにフォントパターンにサイズを含めることができます。 これによってサイズによってヒンティングの方法を変えたりもできるわけですが、この時の size 属性の単位はポイントです。 直接ピクセルサイズで指定したい時は pixelsize 属性を指定します。

ですのでFontconfigにもDPIの値を把握してもらわないといけません。 把握してくれてるのでしょうか?

$ fc-match -v ':size=12' | grep 'size\|dpi'
Pattern has 41 elts (size 48)
        size: 12(f)(s)
        pixelsize: 14(i)(w)
        dpi: 75(f)(s)

75……!? しかも 14/12×72=84 ですから計算もおかしいです。

計算がおかしかったのはフォントファミリー名を指定しなかったため LANG からNoto Sans CJK JPが選ばれ、かつ /etc/fonts/conf.d/56-neon-noto.conf でCJKの最小 pixelsize が14とされていたからのようです。 他のフォントファミリー名を指定しますと75で計算された結果がそのまま出てきます。

$ fc-match -v 'Noto Sans CJK JP:size=12' | grep size
Pattern has 41 elts (size 48)
        size: 12(f)(s)
        pixelsize: 14(i)(w)
$ fc-match -v 'Ubuntu:size=12' | grep size
Pattern has 41 elts (size 48)
        size: 12(f)(s)
        pixelsize: 12.5(f)(s)

size ではなく pixelsize を指定した場合はどうでしょうか?

$ fc-match -v ':pixelsize=14' | grep 'size\|dpi'
Pattern has 39 elts (size 48)
        size: 13.44(f)(s)
        pixelsize: 14(i)(w)

今度は dpigrep の結果に出てこなくなりました。 14/13.44×72=75 ですので75で計算されてはいるようです。

ソースコードをgrepしたところ src/fcdefault.c にそれらしい75を見つけました。 この75はFontconfigのデフォルトのようです。

というわけでディスプレイのDPIの96も、フォントサイズ計算時のDPIの72も、Fontconfigには全く伝わっていないのでした。

では困るか……というと、実際困りません。

環境変数 FC_DEBUG=1 を設定して適当なアプリケーションを起動しますとFontconfigに要求されたパターン(変形後)とそれに一致したフォントをトレースできます。 で、大抵のアプリケーションでは size に中途半端な小数の値、 pixelsize の方に整数値が来ているのが確認できます。 つまり大抵のアプリケーションはフォントパターンとして pixelsize を直接指定しているわけです。 Firefoxなんかは丁寧に dpi も指定してきてるようです。

ですのでFontconfigの設定ファイルを書くときも pixelsize だけを相手にしていれば良さそうです。

というわけでDPIで何かすることもなく今日もFontconfigの設定は行わないのでした。

sizeだけ渡された場合に対応する

偏執狂っぽく、それでも size だけ渡してくるアプリケーションに対応したいんだ!という方(私のような人間)のために一応書いてみました。

~/.config/fontconfig/conf.d/10-dpi.conf
<?xml version='1.0'?>
<!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
<fontconfig>
 <match>
  <edit name="dpi">
   <int>72</int>
  </edit>
 </match>
 <match>
  <edit name="PIXELSIZE_EXISTS">
   <bool>false</bool>
  </edit>
 </match>
 <match>
  <test name="pixelsize" compare="more_eq">
   <int>0</int>
  </test>
  <edit name="PIXELSIZE_EXISTS">
   <bool>true</bool>
  </edit>
 </match>
 <match>
  <test name="size" compare="more_eq">
   <int>0</int>
  </test>
  <test name="PIXELSIZE_EXISTS" compare="eq">
   <bool>false</bool>
  </test>
  <edit name="pixelsize">
   <divide>
    <times>
     <name>size</name>
     <name>dpi</name>
    </times>
    <int>72</int>
   </divide>
  </edit>
 </match>
</fontconfig>

まず dpi 属性を、ここでは72にします。

そして size が0以上、かつ pixelsize が0以上ではない(指定されていない)場合に pixelsize を計算してやります。 (属性が存在していない場合は <test> は必ず失敗するようですので PIXELSIZE_EXISTS を導入しています。)

上手く動いているようには見えますが、警告も出てますね。

$ fc-match -v 'ubuntu-30' | grep 'size\|dpi\|PIXELSIZE_EXISTS'
Fontconfig warning: "~/.config/fontconfig/conf.d/10-dpi.conf", line 37: saw range, expected number
Pattern has 42 elts (size 48)
        size: 30(f)(s)
        pixelsize: 30(i)(w)
        dpi: 72(i)(w)
        PIXELSIZE_EXISTS: False(w)

この警告は size の型は本当はDoubleではなくDoubleの範囲で、Doubleが必要なところでは変換(src/fcrange.cFcRangePromote)されているからのようです。 マニュアルには勿論 size はDoubleとしか書いてません。 このやろう。

まあ、どの道こんなことする必要はありませんので……。