Fontconfig (5日目) - The programmable configuration language “fonts.conf”

見える……見えるぞ……ここまで読んでくださった方々が一斉に戻るボタンを押すのを……。 いやマジで、Fontconfigの設定ファイルは文法がXMLでとても読み辛い書き辛いですけれども命令型言語なんですってば。

本当は「The programming language “fonts.conf”」としたかったのですけれども、チューリング完全ではないものを「programming language(プログラミング言語)」と言っちゃうのは若干憚られ、 Dhall に倣って「programmable configuration language(プログラム可能な設定言語)」ということで。

以後、設定ファイル名としての fonts.conf はイタリックで、言語名としての“fonts.conf”はクォーテーションで書き分けます。 なお言語名のほうは何か呼び名が欲しいので私が勝手に呼んでいるだけです。 Fontconfigのドキュメントでは「font configuration file(s)」としか書かれてないです。

定義

“fonts.conf”はXMLの上に構築された言語です。

詳しい定義は /usr/share/xml/fontconfig/fonts.dtd を参照ください。

できること

変数が使えます。 破壊的更新が可能です。 つまり関数型言語風なDhallやパターンマッチと置換で構成されるXSLT等とは大きく異なり、上から順番に実行される命令形言語です。

リスト操作ができます。 むしろ全ての変数はリストです。

四則演算や集合演算、文字列操作、比較等ができます。

条件分岐ができます。

他のファイルをincludeできます。 ただし同じファイルを2回includeしても無視されます。 つまり再帰はできません。

できないこと

チューリング完全ではありません。 具体的にはループや再帰が書けません。 ですのでうっかり無限ループさせてしまうようなことはないのでご安心ください。

再帰的なデータ構造もありません。 リストを入れ子にしたりはできません。

サブプログラム定義、関数定義、ラムダ式のようなものもありません。 間接参照のようなものもありません。 つまりプログラムの再利用可能な部品化はできません。

外部に影響を及ぼすような副作用は行えません。 能動的にファイルに出力したりログを吐いたりもできません。

Hello, World

それではお約束のHello, World行ってみましょう。

hello.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
 <match>
  <edit name="MESSAGE">
   <string>Hello, World!</string>
  </edit>
 </match>
</fontconfig>

上記の内容を適当なディレクトリに hello.conf という名前で保存してください。

実行結果は fc-pattern -c で得ることができます。

$ FONTCONFIG_PATH=$PWD FONTCONFIG_FILE=hello.conf fc-pattern -c ''
Pattern has 3 elts (size 16)
        lang: "ja"(w)
        prgname: "fc-pattern"(s)
        MESSAGE: "Hello, World!"(w)

はい、“fonts.conf”には出力を行う機能がありませんので実行結果は変数こと属性を通じて確認するしかありません。 Fontconfigが定義している属性はFontconfigの動作に関わりますのでサンプルでは適当なユーザー定義属性を使います。

環境変数を用いて /etc/fonts/conf.d/ を読み込ませず実行していますが langprgname だけはFontconfig自身が追加してきますのでどうやっても付いてきます。 ですので無視してください。 ちなみに環境変数 LANG をunsetしても英語(en)になるだけでした。

代入

変数……変数と言い張るのも疲れましたので属性に戻します……属性の破壊的更新には <edit> を使用します。 <edit><match> の中に置く必要があります。

上の hello.conf は最低限の記述ですので色々とXML属性……この属性はアトリビュートですねややこしいですね……XMLアトリビュートの省略があります。 省略せずに書くとこうなります。

hellofull.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
 <match target="pattern">
  <edit name="MESSAGE" mode="assign" binding="weak">
   <string>Hello, World!</string>
  </edit>
 </match>
</fontconfig>

<match>

<match> のXMLアトリビュート targetpatternfontscan 何れかの値を取ります。

pattern はフォントパターンで指定された属性を判定/変更します。 font は( fc-query で確認できる)実際のフォントの側の属性を判定/変更します。 scan はよくわかりませんがキャッシュに影響を与えるようです。

少なくとも今日はサンプルの実行に fc-pattern -c を使いますので全部 pattern です。 デフォルトは pattern ですので省略して構わないわけです。

<edit>

<edit> のXMLアトリビュート name は変更する属性名、 mode は変更の方法、 binding は優先度です。

mode は色々とありますがリスト操作の際に説明します。 デフォルトは assign で代入です。

binding は昨日見ましたように weakstrongsame とあります。 実行結果のMESSAGEに (w) が付いてますようにデフォルトは weak です。 ファミリー名以外では意味がないのではと思ってます。 (気付いたことがありましたら修正します。)

計算式

計算をしてみましょう。

calc1.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
 <match>
  <edit name="X">
   <plus>
    <int>1</int>
    <int>2</int>
   </plus>
  </edit>
  <edit name="Y">
   <times>
    <name>X</name>
    <double>0.5</double>
   </times>
  </edit>
 </match>
</fontconfig>

実行。

$ FONTCONFIG_PATH=$PWD FONTCONFIG_FILE=calc1.conf fc-pattern -c ''
Pattern has 4 elts (size 16)
        lang: "ja"(w)
        prgname: "fc-pattern"(s)
        X: 3(i)(w)
        Y: 1.5(f)(w)

1+2=3、3×0.5=1.5ですね。

<string>, <int>, <double>, <plus>, <times>, <name>...

これらのXMLタグは値を書く箇所に使えます。

<string><int><double> は値そのものです。 値として定数文字列を用いる <bool><const> 等もあります。

3日目に軽く触れましたがフォントパターン :weight=bold:weight=200 なのと同様に <const>bold</const><int>200</int> と同じです。

<plus><times> 等は計算式です。 比較演算や <if> 等もあります。 <floor><ceil> 等の丸め関数もあります。

<name> で他の属性の値を参照することもできます。

条件分岐

コマンドラインで指定されたパターンの値によって分岐してみましょう。

cond1.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
 <match>
  <test name="weight" compare="less_eq">
   <const>medium</const>
  </test>
  <edit name="MESSAGE">
   <string>Regular</string>
  </edit>
 </match>
 <match>
  <test name="weight" compare="more">
   <const>medium</const>
  </test>
  <edit name="MESSAGE">
   <string>Bold</string>
  </edit>
 </match>
</fontconfig>

この例は weight の値がmedium以下か、mediumを超えているかで分岐します。

$ FONTCONFIG_PATH=$PWD FONTCONFIG_FILE=cond1.conf fc-pattern -c ':weight=regular'
Pattern has 4 elts (size 16)
        weight: 80(f)(s)
        lang: "ja"(w)
        prgname: "fc-pattern"(s)
        MESSAGE: "Regular"(w)
$ FONTCONFIG_PATH=$PWD FONTCONFIG_FILE=cond1.conf fc-pattern -c ':weight=bold'
Pattern has 4 elts (size 16)
        weight: 200(f)(s)
        lang: "ja"(w)
        prgname: "fc-pattern"(s)
        MESSAGE: "Bold"(w)

Fontconfig側で定義されていない属性をコマンドラインで渡そうとするとエラーになりましたので weight を使用しました。 ユーザー定義属性を使って分岐することももちろんできます。

<test>

name アトリビュートで指定した属性の値と <test> 自体の子ノードの値を compare アトリビュートで指定した方法で比較します。

条件が成立すればそのまま進み、不成立であれば <match> を抜けます。

<match> の中に複数の <test><edit> を入れることもできます。 しかし <match> 自体を入れ子にすることはできずelse節にあたる内容を書く場所もないため同じようなことを繰り返し書かないといけない場面も多く不便です。

リスト操作

全ての属性はリストです。 リスト操作は <edit>mode で色々できます。

後ろに追加。

list1.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
 <match>
  <edit name="X">
   <string>1st</string>
   <string>2nd</string>
  </edit>
 </match>
 <match>
  <edit name="X" mode="append">
   <string>appended</string>
  </edit>
 </match>
</fontconfig>

実行結果。

$ FONTCONFIG_PATH=$PWD FONTCONFIG_FILE=list1.conf fc-pattern -c ''
Pattern has 3 elts (size 16)
        lang: "ja"(w)
        prgname: "fc-pattern"(s)
        X: "1st"(w) "2nd"(w) "appended"(w)

前に追加。

list2.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
 <match>
  <edit name="X">
   <string>1st</string>
   <string>2nd</string>
  </edit>
 </match>
 <match>
  <edit name="X" mode="prepend">
   <string>prepended</string>
  </edit>
 </match>
</fontconfig>

実行結果。

$ FONTCONFIG_PATH=$PWD FONTCONFIG_FILE=list2.conf fc-pattern -c ''
Pattern has 3 elts (size 16)
        lang: "ja"(w)
        prgname: "fc-pattern"(s)
        X: "prepended"(w) "1st"(w) "2nd"(w)

リストに対してデフォルトの動作である assign をするとどうなるのでしょう。

list3.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
 <match>
  <edit name="X">
   <string>1st</string>
   <string>2nd</string>
  </edit>
 </match>
 <match>
  <edit name="X">
   <string>overwritten</string>
  </edit>
 </match>
</fontconfig>

実行結果。

$ FONTCONFIG_PATH=$PWD FONTCONFIG_FILE=list3.conf fc-pattern -c ''
Pattern has 3 elts (size 16)
        lang: "ja"(w)
        prgname: "fc-pattern"(s)
        X: "overwritten"(w)

リストの全ての値を破棄しての上書きでした。

条件分岐とリスト操作

条件分岐とリスト操作の組み合わせにはひとつミソがあります。

<test> はデフォルト(アトリビュート qualany)ではリストの中の何れかの値が一致すればそれで成立します。 そして、続く <edit> ではリストの中の一致した箇所が操作対象になります。

例えば append であれば最初に一致した値の次に挿入されます。

list4.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
 <match>
  <edit name="X">
   <int>1</int>
   <int>2</int>
   <int>3</int>
  </edit>
 </match>
 <match>
  <test name="X" compare="more">
   <double>1.5</double>
  </test>
  <edit name="X" mode="append">
   <string>appended</string>
  </edit>
 </match>
</fontconfig>

実行結果。

$ FONTCONFIG_PATH=$PWD FONTCONFIG_FILE=list4.conf fc-pattern -c ''
Pattern has 3 elts (size 16)
        lang: "ja"(w)
        prgname: "fc-pattern"(s)
        X: 1(i)(w) 2(i)(w) "appended"(w) 3(i)(w)

複数一致しても最初に一致した箇所のみが変更されます。

もう一例。 上書きの場合。

list5.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
 <match>
  <edit name="X">
   <int>1</int>
   <int>2</int>
   <int>3</int>
  </edit>
 </match>
 <match>
  <test name="X" compare="more">
   <double>1.5</double>
  </test>
  <edit name="X">
   <string>overwriten</string>
  </edit>
 </match>
</fontconfig>

実行結果。

$ FONTCONFIG_PATH=$PWD FONTCONFIG_FILE=list5.conf fc-pattern -c ''
Pattern has 3 elts (size 16)
        lang: "ja"(w)
        prgname: "fc-pattern"(s)
        X: 1(i)(w) "overwriten"(w) 3(i)(w)

<test>の順番

<test> が複数ある場合はどうなるのでしょう。 これは最初の <test> で一致した位置となります。 つまり <test> の順番も重要です。

list6.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
 <match>
  <edit name="X">
   <int>1</int>
   <int>2</int>
   <int>3</int>
  </edit>
 </match>
 <match>
  <test name="X" compare="more">
   <double>1.5</double>
  </test>
  <test name="X" compare="eq">
   <double>3</double>
  </test>
  <edit name="X">
   <string>overwriten</string>
  </edit>
 </match>
</fontconfig>

実行結果。

% FONTCONFIG_PATH=$PWD FONTCONFIG_FILE=list6.conf fc-pattern -c ''
Pattern has 3 elts (size 16)
        lang: "ja"(w)
        prgname: "fc-pattern"(s)
        X: 1(i)(w) "overwriten"(w) 3(i)(w)

<test> を入れ替えてみます。

list7.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
 <match>
  <edit name="X">
   <int>1</int>
   <int>2</int>
   <int>3</int>
  </edit>
 </match>
 <match>
  <test name="X" compare="eq">
   <double>3</double>
  </test>
  <test name="X" compare="more">
   <double>1.5</double>
  </test>
  <edit name="X">
   <string>overwriten</string>
  </edit>
 </match>
</fontconfig>

実行結果。

$ FONTCONFIG_PATH=$PWD FONTCONFIG_FILE=list7.conf fc-pattern -c ''
Pattern has 3 elts (size 16)
        lang: "ja"(w)
        prgname: "fc-pattern"(s)
        X: 1(i)(w) 2(i)(w) "overwriten"(w)

リスト全体の操作

<test> の後でもリスト全体を操作したいこともあります。

その場合は assignprependappenddelete の代わりに assign_replaceprepend_firstappend_lastdelete_all を使用します。

インクルードファイル

<include> を使用して設定ファイルから他のファイルをincludeできます。

ただし同じファイルを2回以上includeしようとしても無視されます。 ですので再帰には使えません。