Fontconfig (9日目) - sans-serif¶
CSS同様にFontconfigもsans-serif、serif、monospaceといった総称ファミリー名を扱えるのはこれまで見てきたとおりです。
ただ、DEのフォント設定画面で選択したフォントは各種GUIツールキットの設定ファイルに保存されてFontconfigには伝わっていないのも見てきました。
今日は fc-match 'sans\-serif'
に対して狙ったフォントを返せるようにしましょう。
総称ファミリー名とは¶
総称ファミリー名とは CSSでは ブラウザのデフォルトフォントやユーザーが設定したフォント、環境ごとのUIフォントを明示的に選択するためのものです。
Fontconfigでは……ただの、存在していないフォントファミリー名というだけです。
ソースコードをgrepしても /usr/bin/fc-match や /usr/lib/x86_64-linux-gnu/libfontconfig.so.1 を strings
コマンドにかけても総称ファミリー名らしきものは出てきません。
何ら特別扱いはされていません。
特定の名前を多数の設定ファイルが色々と取り回して意味のある名前にしているだけなのです。 順番に見ていきましょう。
sans-serifが実在するフォントで置き換えられる理由¶
現在、sans-serifは英語ではNoto Sans、日本語ではNoto Sans CJK JPに置き換えられます。
$ LANG=en_US fc-match 'sans\-serif'
NotoSans-Regular.ttf: "Noto Sans" "Regular"
$ LANG=ja_JP fc-match 'sans\-serif'
NotoSansCJK-Regular.ttc: "Noto Sans CJK JP" "Regular"
ということはどこかでフォントパターンの family
属性にこのような操作を行っている設定ファイルがあるはずです。
KDE neonでは /etc/fonts/conf.d/56-neon-noto.conf がそうです。
<!-- 省略 -->
<alias>
<family>sans-serif</family>
<prefer>
<family>Arimo</family>
<family>Noto Sans</family>
<family>Noto Sans CJK SC</family>
<family>Noto Sans Arabic</family>
<family>Noto Sans Thai</family>
<family>Noto Sans Devanagari</family>
<family>Noto Sans Tamil</family>
<family>Noto Sans Hebrew</family>
<family>Noto Sans Bengali</family>
<family>Noto Sans Telugu</family>
<family>Noto Sans Kannada</family>
<family>Noto Sans Malayalam</family>
<family>Noto Sans Gurmukhi</family>
<family>Noto Sans Gujarati</family>
<family>Noto Sans Oriya</family>
<family>Noto Sans Armenian</family>
<family>Noto Sans Georgian</family>
<family>Noto Sans Khmer</family>
<family>Noto Sans Lao</family>
<family>Noto Sans Ethiopic</family>
<family>Noto Sans Myanmar</family>
<family>Noto Sans Sinhala</family>
<family>Jomolhari</family>
<family>Noto Sans Coptic</family>
<family>Noto Sans Deseret</family>
<family>Noto Sans TaiTham</family>
<family>Noto Sans CanadianAboriginal</family>
<family>Noto Sans Yi</family>
<family>Noto Sans Tifinagh</family>
<family>Noto Sans Adlam</family>
<family>Noto Sans Cherokee</family>
<family>Noto Sans Chakma</family>
<family>Noto Sans Osage</family>
<family>Noto Color Emoji</family>
<family>Noto Sans Symbols</family>
<family>Noto Sans Symbols2</family>
<family>DejaVu Sans</family>
</prefer>
</alias>
<!-- 省略 -->
<match target="pattern">
<test name="lang" compare="contains">
<string>ja</string>
</test>
<test name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="prepend" binding="strong">
<string>Noto Sans CJK JP</string>
</edit>
</match>
<!-- 省略 -->
<alias>¶
<alias>
タグは説明していませんでした。
これは <match>
〜 <test>
〜 <edit>
の省略記法です。
<alias>
<family>XXX</family>
<prefer>
<family>YYY</family>
</prefer>
</alias>
は次と同じです。
<match>
<test name="family">
<string>XXX</string>
</test>
<edit name="family" mode="prepend">
<string>YYY</string>
</edit>
</match>
<prefer>
はprepend、 <accept>
はappend、 <default>
はappend_lastと同じです。
あくまで上から下に実行される命令であって、エイリアス宣言なんかではないです。 従って順番が非常に大切です。
sans-serifだけの状態から最初にXをprependするとX, sans-serifの順番になるので、そこで次にYをprependしてもX, Y, sans-serifとなってXが優先されます。
一番先だった 56-neon-noto.conf はKDE neon固有ですが、他にも 57-dejavu-sans.conf、 58-dejavu-lgc-sans.conf、 60-latin.conf、 61-urw-gothic.conf、 61-urw-nimbus-sans.conf、 65-droid-sans-fallback.conf、 65-fonts-ipafont-gothic.conf、 65-fonts-persian.conf、 65-nonlatin.conf、 69-unifont.conf、 70-fonts-noto-cjk.conf が次々とsans-serifの前後に推しフォントファミリー名を追加しようと手ぐすね引いています。
結果 fc-pattern -c 'sans\-serif'
は膨大なリストとなり fc-match 'sans\-serif'
は何れかの存在するフォント名となるわけです。
sans-serifがパターンに含まれるようになる理由¶
fc-match ''
と fc-match 'sans\-serif'
は同じ結果を返します。
ということはどこかで family
属性にsans-serifを追加している設定ファイルがあるはずです。
/etc/fonts/conf.d/49-sansserif.conf がそうです。
family
属性が持つリストにsans-serif、serif、monospaceのいずれも含まれていない場合sans-serifを追加してくれています。
この設定ファイルが実は困り物で、フォントパターンに実際に存在するフォント名を指定したとしてもsans-serifが追加されてしまうため、次のようにNoto Serifのフォールバック先がNoto Sans CJK JPになったりします。 Noto Serif CJK JPであって欲しいですよね。
$ fc-match -s 'Noto Serif' | head
NotoSerif-Regular.ttf: "Noto Serif" "Regular"
NotoSansCJK-Regular.ttc: "Noto Sans CJK JP" "Regular"
NotoSansArabic-Regular.ttf: "Noto Sans Arabic" "Regular"
NotoSansThai-Regular.ttf: "Noto Sans Thai" "Regular"
NotoSansDevanagari-Regular.ttf: "Noto Sans Devanagari" "Regular"
NotoSansTamil-Regular.ttf: "Noto Sans Tamil" "Regular"
NotoSansHebrew-Regular.ttf: "Noto Sans Hebrew" "Regular"
NotoSansBengali-Regular.ttf: "Noto Sans Bengali" "Regular"
NotoSansTelugu-Regular.ttf: "Noto Sans Telugu" "Regular"
NotoSansKannada-Regular.ttf: "Noto Sans Kannada" "Regular"
family
属性は Noto Serif → Noto Serif, sans-serif → Noto Serif, Noto Sans CJK JP, sans-serif みたいな感じで先頭のNoto Serifを除けば空文字列を指定したのと似たようなものになってしまうわけです。
(正確には49より前の分は反映されませんが。)
この対策は後日行います。
設定¶
では実際にsans-serifを設定してみましょう。
/etc/fonts/conf.d/ にある設定ファイルでsans-serifを置き換えようとするものは56以降ですのでユーザーの設定ファイルで先んずることができます。
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<alias>
<family>sans-serif</family>
<prefer>
<family>Ubuntu</family>
</prefer>
</alias>
</fontconfig>
さて。
$ LANG=en_US fc-match 'sans\-serif'
Ubuntu-R.ttf: "Ubuntu" "Regular"
$ LANG=ja_JP fc-match 'sans\-serif'
NotoSansCJK-Regular.ttc: "Noto Sans CJK JP" "Regular"
strong
として追加されているNoto Sans CJK JPに負けていますね……。
binding
アトリビュートのデフォルトがweakなのが原因です。
same
か strong
にしましょう。
<!-- 省略 -->
<alias binding="same">
<!-- 省略 -->
どうでしょうか。
$ LANG=en_US fc-match 'sans\-serif'
Ubuntu-R.ttf: "Ubuntu" "Regular"
$ LANG=ja_JP fc-match 'sans\-serif'
Ubuntu-R.ttf: "Ubuntu" "Regular"
無事にUbuntuとなりました。
sans-serifの意味¶
KDEの設定画面では本文用のフォントとUI用のフォントが分けられていましたが、それはKDEライブラリを使用しないアプリケーションには伝わりません。 そして多くのアプリケーションは(個別設定等を行わなければ)sans-serifを本文とUIの両方に使用します。
さて私は本文用にDejaVu Sans、UI用にUbuntuを設定しました。 ではsans-serifはどちらにするべきでしょうか?
これは、Ubuntuにせざるを得ないと思います。
つまりsans-serifはUI用フォントとみなすということです。 UI用フォントはsans-serifを通じてしか設定できなくても本文用フォントは別に設定できるアプリケーションもありますし。
CSSでしたらUI用はsystem-uiとして分離されていますのでこんな悩みは無いのですけれどもね。 (system-uiや-apple-systemを本文に使うクソサイトが結構あったり……。)