Sunday, July 17, 2011

Centrally Managed Fonts With Fontconfig

I'm using this font setup well over a year or two. Everything works just fine (nicely centralized) and as I had a reminder to document it once a bit (so I'll not forget it totally), here is it.

If we speak about font settings almost certainly one of the bits we want to get right is to setup "fontconfig" library correctly. Fontconfig is library which can be used by application to select most appropriate font for the job. Application do this by providing pattern to match against and fontconfig will try to find the best available font nearest to provided pattern. Pattern can contain several attributes and some of them are intended for actual font rendering subsystem. All of these attributes as going through fontconfig can be, and usually are modified or new attributes for renderer are added. Fontconfig doesn't provide just centralized font management but also centralized font rendering settings. That's cool!

When application asks for font providing some pattern the result is provided in 2 steps. First fontconfig consults configuration rules which can match the pattern and edit its attributes (match target="pattern"). With this feature normalization of pattern is possible. Once pattern went through all rules and the nearest font is found the result is matched against set of other rules (match target="font") which can modify some font attributes to be provided to renderer at the end. This is the way how we usually ensure that some quality settings like hinting and anti-aliasing are in place. The system wide configuration file resides in "/etc/fonts/fonts.conf". By looking at this core file we can understand more.

where are font files actually stored?
<!-- Font directory list -->
<dir>/usr/share/fonts</dir>
<dir>~/.fonts</dir>
files in "conf.d" directory are loaded in alphabetical order
<!-- Load local system customization file -->
<include ignore_missing="yes">conf.d</include>
fontconfig is caching properties of every font to speed things up
<!-- Font cache directory list -->
<cachedir>/var/cache/fontconfig</cachedir>
<cachedir>~/.fontconfig</cachedir>

When new font is installed to any font directory, we have to run "fc-cache" to update font information cache files.

In "/etc/fonts/" directory there is usually a directory "conf.avail" containing variety of font configuration files. Some of these files are provided by fontconfig library package itself and others are provided by other packages. In "conf.d" directory which is included to configuration as you can see above are symlinks pointing to "conf.avail" files. You can manipulate these symlinks to achieve desired configuration. One of standard "conf.avail" file is "50-user.conf".

/etc/fonts/conf.d/50-user.conf
<?xml version='1.0'?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
    <!-- Load per-user customization file -->
    <include ignore_missing="yes">~/.fonts.conf.d</include>
    <include ignore_missing="yes">~/.fonts.conf</include>
</fontconfig>

So that's how "~/.fonts.conf" file comes to effect and that's where I keep my configuration. The one thing what I want to achieve here is to have all font settings centrally managed in this file.

  • All requests for bitmap fonts are rejected except for Terminus.
  • Application aliases are just alias names for the fonts used by a particular application or group of applications. Changing the alias mapping in this file will trigger the font to be changed in all applications configured with given alias.
  • I find this very nice and powerful as I don't have to remember where the font is actually configured for application. I don't have to go and change it or it's properties like size of weight somewhere in configuration file. By doing this I get centralized management with consistent font settings across applications easily. I've adopted habit to name these aliases with prefix "local_", hence "local_xmobar" for "xmobar" font, "local_dmenu" for "dmenu" font and "local_xmonad_tabbed" for "xmonad" tabbed layout font.

  • As you can see all above mentioned "local_" aliases will be edited to be actually bold "monospace" alias with size of 9 points, hence same font
  • Then I have a two aliases "local_terminal" and "local_terminal_sized". The latter one will eventually point to former one with the addition of size to be set to 22 pixels. The "local_terminal" will be then edited to "Terminus" which will be edited (towards end of the config file) to "Terminus (TTF).
  • There are also default quality settings matching all fonts and some overrides for "Terminus (TTF)".
~/.fonts.conf
<?xml version='1.0'?>
<!-- "~/.fonts.conf" -->
<!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
<fontconfig>

    <!-- reject all bitmap fonts, with the exception of 'Terminus' -->
    <selectfont>
        <acceptfont>
            <pattern>
                <patelt name="family"> <string>Terminus</string> </patelt>
            </pattern>
        </acceptfont>
        <rejectfont>
            <pattern>
                <patelt name="scalable"> <bool>false</bool> </patelt>
            </pattern>
        </rejectfont>
    </selectfont>

   <!-- application aliases -->
    <match target="pattern">
        <test name="family">
            <string>local_xmobar</string>
            <string>local_dmenu</string>
            <string>local_xmonad_tabbed</string>
        </test>
        <edit name="family"> <string>monospace</string> </edit>
        <edit name="weight"> <const>bold</const> </edit>
        <edit name="size"> <double>9</double> </edit>
    </match>

    <match target="pattern">
        <test name="family"> <string>local_terminal_sized</string> </test>
        <edit name="family"> <string>local_terminal</string> </edit>
        <!-- <edit name="size"> <double>11</double> </edit> -->
        <edit name="pixelsize"> <double>22</double> </edit>
    </match>

    <match target="pattern">
        <test name="family"> <string>local_terminal</string> </test>
        <!-- <edit name="family"> <string>monospace</string> </edit> -->
        <edit name="family"> <string>Terminus</string> </edit>
    </match>

    <!-- preferred aliases -->
    <alias>
        <family>serif</family>
        <prefer>
            <family>DejaVu Serif</family>
        </prefer>
    </alias>

    <alias>
        <family>sans-serif</family>
        <prefer>
            <family>Tahoma</family>
            <family>DejaVu Sans</family>
        </prefer>
    </alias>

    <alias>
        <family>monospace</family>
        <prefer>
            <family>DejaVu Sans Mono</family>
        </prefer>
    </alias>

   <!-- default quality settings -->
    <match target="font">
        <edit name="antialias"> <bool>true</bool> </edit>
        <edit name="hinting"> <bool>true</bool> </edit>
        <edit name="hintstyle"> <const>hintslight</const> </edit>
        <edit name="autohint"> <bool>false</bool> </edit>
        <edit name="rgba"> <const>rgb</const> </edit>
        <edit name="lcdfilter"> <const>lcddefault</const> </edit>
    </match>

    <!-- replace 'Terminus' with 'Terminus (TTF)' -->
    <match target="pattern">
        <test name="family"> <string>Terminus</string> </test>
        <edit name="family"> <string>Terminus (TTF)</string> </edit>
    </match>

    <!-- overrides for 'Terminus (TTF)' -->
    <match target="font">
        <test name="family"> <string>Terminus (TTF)</string> </test>
        <edit name="antialias"> <bool>false</bool> </edit>
        <edit name="hinting"> <bool>false</bool> </edit>
        <edit name="hintstyle"> <const>hintnone</const> </edit>
    </match>

</fontconfig>

Example how the alias is used then.

excerpt from ~/.Xresources
! xterm ----------------------------------------------------------------------
XTerm*faceName:         xft:local_terminal_sized

! urxvt ----------------------------------------------------------------------
URxvt.font:             xft:local_terminal_sized

Unfortunately the format "gtkrc" is expecting, doesn't allow you to go with arbitrary "fontconfig" alias. There can be a similar problem with other applications. If application provides GUI font selection dialog don't expect you'll find your aliases there. If you don't want specific font you can usually select one from well-known aliases ("monospace", "sans" and "sans-serif"). Generally, you have no problem if you're able to set the font for application in XFT format like "xft:alias".

~/.gtkrc-2.0-mine
# ~/.gtkrc-2.0.mine

style "user-font"
{
    font_name = "sans 8"
}
widget_class "*" style "user-font"
gtk-font-name = "sans 8"

For changing a font in running terminal below scripts are useful.

~/bin/font
#!/bin/sh

printf '\e]50;%s\a' "$1"
~/bin/font-pixelsize
#!/bin/sh

font "xft:local_terminal:pixelsize=$1"
~/bin/font-reset
#!/bin/sh

font 'xft:local_terminal_sized'
~/bin/font-size
#!/bin/sh

font "xft:local_terminal:size=$1"

If you're debugging issues with your fontconfig setup you will certainly find out a possibility to look under the hood as very handy. You can do it by setting up "FC_DEBUG" environment variable (look in "man fonts.conf" for possible values).

$ export FC_DEBUG=1
$ fc-match terminus