Fontconfig (10日目) - serif

sans-serifとくればserifです。 といいましてもセリフ体/明朝体はUI用に使われることはないでしょうし本文用フォントとして自ら設定しないと見ることすら稀ではないでしょうか。 Fontconfigに対してserifという名前でデフォルトフォントを問い合わせるアプリケーションは非常に少ないと思います。

セリフ体/明朝体を使うWebサイトは無いことはないです。 (Safariのデフォルトがsans-serifではなくserifなのは有名な話です。 またAlabasterテーマのフォントはGeorgiaですのでSphinx製ドキュメントでよく見ます。 このブログではinitialに戻してます。) しかしその場合はブラウザの中で設定したserif用フォントが使われるはずです。

というわけでFontconfigでserifを設定する意味はフォールバックフォントのほうにあります。

昨日も見ましたようにこうしてserif系フォントを指定した際に

$ fc-match -s 'Noto Serif' | head
NotoSerif-Regular.ttf: "Noto Serif" "Regular"
NotoSansCJK-Regular.ttc: "Noto Sans CJK JP" "Regular"
Ubuntu-R.ttf: "Ubuntu" "Regular"
Ubuntu-Th.ttf: "Ubuntu" "Thin"
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"

2番目以降がsans-serifになってしまっているのを直そう、というわけです。 このままですとNoto Serifを表示しているはずなのにNoto Sans CJK JPをはじめとするsans-serifフォントの文字が混じってしまいます。

パターンにserifを追加する

まずは family 属性にserifを追加してみます。

~/.config/fontconfig/conf.d/40-serif.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
 <alias>
  <family>Noto Serif</family>
  <accept>
   <family>serif</family>
  </accept>
 </alias>
</fontconfig>

accept (append)によってリスト上のNoto Serifの直後にserifを挿入します。

/etc/fonts/conf.d/49-sansserif.conf のほうがユーザーの設定よりも先に実行されていますので family 属性は Noto Serif → Noto Serif, sans-serif → Noto Serif, serif, sans-serif となってserifが優先されるはずです。

確かめてみましょう。

$ fc-match -s 'Noto Serif' | head
NotoSerif-Regular.ttf: "Noto Serif" "Regular"
NotoSerifCJK-Regular.ttc: "Noto Serif CJK JP" "Regular"
NotoSansCJK-Regular.ttc: "Noto Sans CJK JP" "Regular"
NotoNaskhArabic-Regular.ttf: "Noto Naskh Arabic" "Regular"
NotoSerifThai-Regular.ttf: "Noto Serif Thai" "Regular"
NotoSerifArmenian-Regular.ttf: "Noto Serif Armenian" "Regular"
NotoSerifGeorgian-Regular.ttf: "Noto Serif Georgian" "Regular"
NotoSerifDevanagari-Regular.ttf: "Noto Serif Devanagari" "Regular"
NotoSerifHebrew-Regular.ttf: "Noto Serif Hebrew" "Regular"
NotoSerifGujarati-Regular.ttf: "Noto Serif Gujarati" "Regular"

serifに反応して /etc/fonts/conf.d/56-neon-noto.conf がNotoファミリーのserif系フォントを沢山追加してくれました。

でもまだNoto Sans CJK JPが3番目に居座ってますね。 strongは強いです。

パターンからsans-serifを取り除く

そもそもserif系フォントなのにリスト上にsans-serifが入ってしまっているのが原因です。 49-sansserif.conf はリスト上にserifがある場合はsans-serifを追加しませんが、ユーザーの設定ファイルよりも早く実行されてしまうので事前に防ぐ手立てはありません。 後から取り除いてしまいましょう。

~/.config/fontconfig/conf.d/40-serif.conf (修正後1)
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
 <alias>
  <family>Noto Serif</family>
  <default> <!-- ここも修正 -->
   <family>serif</family>
  </default>
 </alias>
 <match>
  <test name="family" compare="eq">
   <string>sans-serif</string>
  </test>
  <test name="family" compare="eq">
   <string>Noto Serif</string>
  </test>
  <edit name="family" mode="delete" />
 </match>
</fontconfig>

実行結果。

$ fc-match -s 'Noto Serif' | head
NotoSerif-Regular.ttf: "Noto Serif" "Regular"
NotoSerifCJK-Regular.ttc: "Noto Serif CJK JP" "Regular"
NotoNaskhArabic-Regular.ttf: "Noto Naskh Arabic" "Regular"
NotoSerifThai-Regular.ttf: "Noto Serif Thai" "Regular"
NotoSerifArmenian-Regular.ttf: "Noto Serif Armenian" "Regular"
NotoSerifGeorgian-Regular.ttf: "Noto Serif Georgian" "Regular"
NotoSerifDevanagari-Regular.ttf: "Noto Serif Devanagari" "Regular"
NotoSerifHebrew-Regular.ttf: "Noto Serif Hebrew" "Regular"
NotoSerifGujarati-Regular.ttf: "Noto Serif Gujarati" "Regular"
NotoSerifKannada-Regular.ttf: "Noto Serif Kannada" "Regular"

うまくいきました。

ついでに acceptdefault (append_last)に修正しています。

/etc/fonts/conf.d/ 以下のファイルを見ると総称ファミリー名をリストに追加するときは default が使われていますのでそれに倣いました。 accpet (append)ですと既に複数のファミリー名がリストにあった場合に割り込んでしまうからだと思います。

$ fc-pattern -c 'Noto Serif' | grep 'sans-serif' でもsans-serifが入っていないことを確認できます。

もう少し汎用的に書く

Noto Serif以外にもserif系フォントは複数インストールされています。 他のフォントにも対応していきましょう。

DejaVu SerifやFreeSerifは既に /etc/fonts/conf.d/ 以下で対応されています。 IPA P明朝がまだ対応されていないようです。

$ fc-match -s 'IPA P明朝' | head
ipamp.ttf: "IPA P明朝" "Regular"
NotoSansCJK-Regular.ttc: "Noto Sans CJK JP" "Regular"
Ubuntu-R.ttf: "Ubuntu" "Regular"
Ubuntu-Th.ttf: "Ubuntu" "Thin"
NotoSans-Regular.ttf: "Noto Sans" "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"

対応といっても上のNoto Serif用の記述をコピー&ペーストしてIPA P明朝に書き換えるだけですが……もう少し短くできないでしょうか。

上半分のserifを追加している <alias> はフォント毎に書く必要がありますが、下半分のsans-serifを削除している <match> はまとめることができます。 リストにsans-serifとserifの両方がある場合にsans-serifを削除する、というので良いわけです。

~/.config/fontconfig/conf.d/40-serif.conf (修正後2)
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
 <alias>
  <family>Noto Serif</family>
  <default>
   <family>serif</family>
  </default>
 </alias>
 <alias>
  <family>IPA P明朝</family>
  <default>
   <family>serif</family>
  </default>
 </alias>
 <match>
  <test name="family" compare="eq">
   <string>sans-serif</string>
  </test>
  <test name="family" compare="eq">
   <string>serif</string>
  </test>
  <edit name="family" mode="delete" />
 </match>
</fontconfig>

実行結果。

$ fc-match -s 'IPA P明朝' | head
ipamp.ttf: "IPA P明朝" "Regular"
NotoSerifCJK-Regular.ttc: "Noto Serif CJK JP" "Regular"
NotoSerif-Regular.ttf: "Noto Serif" "Regular"
NotoNaskhArabic-Regular.ttf: "Noto Naskh Arabic" "Regular"
NotoSerifThai-Regular.ttf: "Noto Serif Thai" "Regular"
NotoSerifArmenian-Regular.ttf: "Noto Serif Armenian" "Regular"
NotoSerifGeorgian-Regular.ttf: "Noto Serif Georgian" "Regular"
NotoSerifDevanagari-Regular.ttf: "Noto Serif Devanagari" "Regular"
NotoSerifHebrew-Regular.ttf: "Noto Serif Hebrew" "Regular"
NotoSerifGujarati-Regular.ttf: "Noto Serif Gujarati" "Regular"

個別のフォールバック設定

IPA P明朝からNoto Serif CJK JPにフォールバックしているところを見てみましょう。

IPA P明朝, Noto Serif CJK JP

Notoのほうが縦長で太字ですので目立ちますよね。 IPA系で収録文字数の多い IPAmj明朝 をフォールバック先にしましょう。 (Debian/Ubuntu系であればパッケージfonts-ipamj-minchoです。)

~/.config/fontconfig/conf.d/60-fallback.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
 <alias binding="same">
  <family>IPA P明朝</family>
  <accept>
   <family>IPAmj明朝</family>
  </accept>
 </alias>
</fontconfig>

IPA P明朝に限りIPAmj明朝をフォールバック先として優先します。

$ fc-match -s 'IPA P明朝' | head
ipamp.ttf: "IPA P明朝" "Regular"
ipamjm.ttf: "IPAmj明朝" "Regular"
NotoSerifCJK-Regular.ttc: "Noto Serif CJK JP" "Regular"
NotoSerif-Regular.ttf: "Noto Serif" "Regular"
NotoNaskhArabic-Regular.ttf: "Noto Naskh Arabic" "Regular"
NotoSerifThai-Regular.ttf: "Noto Serif Thai" "Regular"
NotoSerifArmenian-Regular.ttf: "Noto Serif Armenian" "Regular"
NotoSerifGeorgian-Regular.ttf: "Noto Serif Georgian" "Regular"
NotoSerifDevanagari-Regular.ttf: "Noto Serif Devanagari" "Regular"
NotoSerifHebrew-Regular.ttf: "Noto Serif Hebrew" "Regular"

KCharSelectを起動しなおして見てみましょう。

IPA P明朝, IPAmj明朝(hintmedium)

文字の高さが同じぐらいになりました。

右上の例示字形にも注目。 「」の上下が詰まってますよね。 行間の広いNotoと比べてIPAmj明朝とIPA P明朝の行間は同じぐらいです。

ただ、IPAmj明朝のほうが濃くて目立つのは変わらないですね。 これは /etc/fonts/conf.d/65-fonts-ipafont-mincho.conf がIPA P明朝のヒンティングをなしにしているのに対してIPAmj明朝は通常のヒンティングがなされているからです。

IPAmj明朝もhintnoneにしてみました。

IPA P明朝, IPAmj明朝(hintnone)

違和感はなくなりました。

ブラウザのフォント探索順

もうフォールバックフォントを自由に設定できますよね! ということで余談です。

次のようなCSSに対してブラウザは文字を収録しているフォントをどう探すのでしょうか。

font-family: XXX yyy ZZZ serif;

環境にはXXXとZZZがインストールされていてyyyは存在していないとします。

Firefox

XXX → そのフォールバックフォント → ZZZ → そのフォールバックフォント → ブラウザ設定でserifに設定されたフォント → そのフォールバックフォント、のようです。

ですのでXXXに対してフォールバックフォントをFontconfigできちんと設定しておけば、一貫性のある表示が得られます。

Chromium

XXX → ZZZ → ブラウザ設定でserifに設定されたフォント → そのフォールバックフォント、のようです。

ですのでXXXがNotoファミリー、游、IBM Plex、Zenのような縦長のフォント、ZZZがArial/Times系(Liberations/Arimo/Tinos他)、MS系(HG他)、IPA、BIZ UD、ヒラギノのような昔ながらのスタイルのフォントだった場合、不揃いな表示になりがちです。 新しくて綺麗とされるフォントを前に前に付け足していってクソCSSになってるサイトは結構あります……。

↑は行幅しか見てないあまりにもざっくりとした分類です。 游は行間は広いですが文字自体はそんな縦長ではないですし、ヒラギノは行間は狭いですが文字自体は割と縦長です。

それとChromiumは langを渡してこない みたいな話もあります。

謝辞

@eeveeさんの my fonts.conf を参考にさせていただきました。 本当に、本当にありがとうございます。 私が“fonts.conf”言語を理解できたのは@eeveeさんのおかげです。