暗无天日

=============>DarkSun的个人博客

Unix 系统中那些被埋没的配置开关——以 FontConfig 为例

前言

本文灵感来自 Buried Toggles Begging to be Brought to Light(作者:Patrick Louis),文章探讨了一个 Unix 世界里的经典问题:很多系统软件提供了极其强大的配置能力,但这些能力被深埋在复杂的配置文件和晦涩的命令行选项中,几乎没有人知道它们的存在。

Mechanism, Not Policy

Unix 设计哲学中有一条广为人知的信条:*"提供机制,而非策略"(Mechanism, not policy)*。意思是系统应该提供灵活的底层能力,而把"怎么用"的决定权留给用户。

这个理念催生了无数强大的工具,但也带来了一个副作用。Henry Spencer 曾一针见血地指出:

这种态度的缺点是,人们倾向于认为一旦完成了高度可配置和富有表现力的接口,工作就算完成了……即使结果是其他任何人如果不经过漫长研究几乎不可能使用。可配置性的反面是迫切需要良好的默认值和一种简单的方式将一切重置为默认值。表达力的反面是需要指导——无论是在程序中还是在文档中——关于如何入门以及如何实现最常见的需求。

ESR 在 The Art of Unix Programming 中也表达了类似的观点:

当人们说一个用户界面是直观的,他们的意思是它 (a) 可发现、(b) 使用时透明、(c) 遵循最小惊讶原则。

venam 在文章中列举了 Linux 系统中 9 个"被埋没的配置开关"的例子。本文将以 FontConfig 为重点深入探讨,其余领域简要带过。

FontConfig:你可能从未了解过的字体魔法

大多数人只知道 fc-list

如果你问一个 Linux 用户怎么管理字体,大概率会得到这样的回答:

fc-list | head
/usr/share/fonts/100dpi/courB18.pcf.gz: Adobe Courier:style=Bold
/usr/share/fonts/100dpi/helvBO18.pcf.gz: Adobe Helvetica:style=Bold Oblique
/usr/share/fonts/100dpi/UTRG__12.pcf.gz: Adobe Utopia:style=Regular
/usr/share/fonts/gsfonts/D050000L.otf: D050000L:style=Regular
/usr/share/fonts/100dpi/timB12-ISO8859-1.pcf.gz: Adobe Times:style=Bold

这确实是大多数人使用 FontConfig 的方式——列出系统里有哪些字体,然后就没了。

FontConfig 的配置格式

但实际上,FontConfig 拥有一套极其强大的 XML 配置格式,可以对字体进行 匹配和属性编辑 。配置文件位于 /etc/fonts/fonts.conf/etc/fonts/conf.d/ 目录下。

ls /etc/fonts/conf.d/ | head
10-hinting-slight.conf
10-scale-bitmap-fonts.conf
10-yes-antialias.conf
11-lcdfilter-default.conf
20-unhint-small-dejavu-lgc-sans.conf
20-unhint-small-dejavu-lgc-sans-mono.conf
20-unhint-small-dejavu-lgc-serif.conf
20-unhint-small-dejavu-sans.conf
20-unhint-small-dejavu-sans-mono.conf
20-unhint-small-dejavu-serif.conf

每个配置文件都是一个 XML 文档,核心结构是 <match> 元素,包含 =<test>=(匹配条件)和 =<edit>=(编辑操作):

<match target="font">
  <test name="family">
    <string>Monospace</string>
  </test>
  <edit name="hintstyle" mode="assign">
    <const>hintslight</const>
  </edit>
</match>

这段配置的意思是:对所有 Monospace 族字体,将 hintstyle 设置为 hintslight。

预置的优化配置

你的系统上很可能已经有一套预置的字体优化配置,但大多数人从未留意过:

ls /usr/share/fontconfig/conf.avail/
05-reset-dirs-sample.conf
09-autohint-if-no-hinting.conf
10-autohint.conf
10-hinting-full.conf
10-hinting-medium.conf
10-hinting-none.conf
10-hinting-slight.conf
10-no-antialias.conf
10-scale-bitmap-fonts.conf
10-sub-pixel-bgr.conf
10-sub-pixel-none.conf
10-sub-pixel-rgb.conf
10-sub-pixel-vbgr.conf
10-sub-pixel-vrgb.conf
10-unhinted.conf
10-yes-antialias.conf
11-lcdfilter-default.conf
11-lcdfilter-legacy.conf
11-lcdfilter-light.conf
11-lcdfilter-none.conf
...(共 70+ 个预置配置文件)

这些预置配置涵盖了各种渲染优化场景,如 LCD 滤波器设置、像素间距建议、特定语言的字体回退等。

fc-match:查看字体匹配真相

fc-match 是理解 FontConfig 行为的关键工具:

fc-match Sans
wqy-zenhei.ttc: "文泉驿正黑" "Regular"

这会告诉你当你请求 Sans 字体时,系统实际会使用什么字体。加上 -s 参数可以看到完整的替换链:

fc-match -s Sans | head -5
wqy-zenhei.ttc: "文泉驿正黑" "Regular"
DejaVuSans.ttf: "DejaVu Sans" "Book"
DejaVuSans-Bold.ttf: "DejaVu Sans" "Bold"
DejaVuSans-Oblique.ttf: "DejaVu Sans" "Oblique"
DejaVuSans-BoldOblique.ttf: "DejaVu Sans" "Bold Oblique"

fc-pattern:调试字体属性

fc-pattern 可以让你看到字体的完整属性列表:

fc-match --verbose Sans | head -20
Pattern has 45 elts (size 48)
	family: "文泉驿正黑"(w) "WenQuanYi Zen Hei"(w) "文泉驛正黑"(w)
	familylang: "zh-cn"(s) "en"(w) "zh-tw"(w)
	style: "Regular"(s)
	stylelang: "en"(s)
	fullname: "文泉驿正黑"(w) "WenQuanYi Zen Hei"(w) "文泉驛正黑"(w)
	fullnamelang: "zh-cn"(s) "en"(w) "zh-tw"(w)
	slant: 0(i)(s)
	weight: 100(f)(s)
	width: 100(f)(s)
	size: 12(f)(s)
	pixelsize: 12.5(f)(s)
	spacing: 0(i)(w)
	foundry: "WenQ"(s)
	antialias: True(w)
	hintstyle: 0(i)(w)
	hinting: True(w)
	verticallayout: False(s)
	autohint: False(w)
	globaladvance: False(w)

为什么这么强大的能力几乎没人知道?

venam 指出,目前的字体相关界面仅限于显示已安装字体列表和不同尺寸的预览。曾经有一个叫 fontweak 的项目试图让 FontConfig 的配置更加可发现,但功能有限且无法展示全局视图。

FontConfig 的困境是典型的 "mechanism without discoverable policy" : 它提供了强大的匹配-编辑机制,但用户需要一个直观的界面来回答"我的系统当前对字体到底做了什么?"

其他被埋没的配置

venam 在文章中还列举了其他几个类似的例子,这里简要概述。

Audio Restore List

PulseAudio 和 PipeWire 会记住每个设备、每个音频流的最后音量设置。但这个信息是隐藏的——当你插上耳机或打开视频时,你无法预知音量会是多少。可能震耳欲聋,也可能完全静音。

Journald

systemd.journal-fields(7) 定义了一套固定的字段体系,Journald 本质上是一个固定列/字段的线性存储。但大多数人只会随机敲命令直到找到想要的结果,或者死记硬背一两个 --unit <service> --reverse 之类的技巧。

sudo

每个人都知道 sudo ,但很少有人能解释 sudo -ll 的输出,或者完整配置 sudoers 文件。sudoers 文件包含默认全局设置、别名和匹配规则,但唯一的编辑器 visudo 对新手极其不友好。

Polkit

Policy Kit 是一个很棒的桌面安全概念:标准化的动作和服务,通过间接总线触发,由集中的策略执行器决定调用者是否被允许。但它的规则和 JavaScript 格式的配置散落在系统各处,创建新的动作和规则非常困难。

udev

udev 规则是另一个"配置散落"的典型。它有匹配模式和动作执行(甚至支持 goto 和标签跳转,是图灵完备的)。唯一"可视化"这些配置的方法是:

systemd-analyze cat-config udev/rules.d
# /usr/lib/udev/rules.d/01-md-raid-creating.rules
# do not edit this file, it will be overwritten on update
# While mdadm is creating an array, it creates a file
# /run/mdadm/creating-mdXXX.  If that file exists, then
# the array is not "ready" and we should make sure the
# content is ignored.

KERNEL=="md*", TEST=="/run/mdadm/creating-$kernel", ENV{SYSTEMD_READY}="0"

# /usr/lib/udev/rules.d/10-dm.rules
...

为什么这个问题难以解决

venam 在文章结尾提到了人们面对复杂配置的三种态度:

  1. 保持默认 —— 用容器化/不可变系统的方式,一切保持出厂设置
  2. 精雕细琢(RICE) —— 乐在其中地折腾每一项配置
  3. 探索中间地带 —— 尝试让隐藏的能力更容易被发现和使用

FontConfig 的例子很好地说明了核心矛盾:可配置性和可发现性之间存在天然的张力。提供更多配置选项意味着更复杂的界面,而简化界面又意味着放弃一些能力。

但也许问题不在于"要不要简化",而在于"如何让用户知道这些选项的存在"。一个好的界面不一定需要展示所有选项,但它应该让用户在需要时能够 发现 选项的存在。这正是目前大多数 Linux 系统工具所缺失的。

正如 ESR 所说,一个好的界面应该是可发现的、透明的、符合最小惊讶原则的。我们还有很长的路要走。

Linux : Unix哲学 : FontConfig : 配置管理 : mechanism-vs-policy