基于 libXft 的应用程序怎么利用上 fontconfig 的字体替换机制?

fontconfig 提供 “别名”(alias) 字体匹配机制,比如:

<alias>
	<family>serif</family>
	<prefer>
		<family>Font 1</family>
		<family>Font 2</family>
		<family>Font 3</family>
		<family>Font 4</family>
		<family>Font 5</family>
	</prefer>
</alias>

如果应用程序请求字体 serif, 那么 fontconfig 会根据 “prefer” 的顺序(如果是弱绑定,那么先匹配语言 lang )匹配字体。这样可以让显示效果更佳的字体排在前面,而当字体里字符不存在时,再到后面显示效果更次的字体里去寻找。

fontconfig 的这种字体匹配机制似乎并非对上级应用程序透明,或者说:需要上级程序参与到字体匹配之中。因为我发现基于 libXft 的程序好像只匹配一次字体。匹配成功后,如果存在 “缺字” 的情况,并不会自动到后面的字体列表里去寻找。而 Xft 又是基于 fontconfig 的,因此可以断定 fontconfig 的别名字体机制并非对上次应用透明。

我看了一下应用程序,是调用了 libXft 的这个函数:

XftFont* XftFontOpenName( Display *dpy, int screen, unsigned char *fontname )

如果对该函数的第三个参数 fontname 传入诸如 “serif:bold:size=16” 这类的字体 Pattern, 那么可以匹配到一个字体,却无法在 “缺字” 的情况下继续匹配后面的字体。

如果 Xft 本身支持 “缺字替换” 的功能,那么该调用哪个函数?如果不支持,那么该如何直接调用 fontconfig 的函数,让 “缺字替换” 功能得以实现?

我发现 GTK 程序完美支持 fontconfig 的 “缺字替换” 功能,当然 GTK 并不依赖 Xft. 我对字体领域知之甚少,看 libXft 的文档以及源代码已经一头雾水,对于更加庞杂的 fontconfig 和 GTK, 就更难掌握了。如果哪位高人知道该如何做,恳请提供一些思路。

fontconfig 如其名,只是「配置字体」,它不管字体的渲染。GTK 程序使用 Pango 来渲染字体,因此所有使用 Pango 的程序都能完美地支持 fontconfig 的配置。

而跳过了 Pango 的程序,比如 Qt,就容易遇到不兼容的 问题。Chrom* 系浏览器同理。火狐也是自己找字体的,但是它对 fontconfig 支持得比较好(也只是「比较好」而已,比如你修改字体的 charset 属性,或者按大小使用不同的字体,火狐也是不支持的)。

我猜测,Xft 和一些基于 Rust 的程序(如 wezterm、alacritty)一样,仅使用 fontconfig 取得默认字体,但是并没有好好处理 fallback 等等更为进阶的功能。

字体渲染是个挺复杂的事情,参见 Text Rendering Hates You - Faultlore

3赞

fontconfig 确实不应该去负责字体渲染,但它的两个核心任务是配置和匹配,应该能够做到对上层应用透明。应用程序无须关心具体的某个字符来自哪个字体,只需请求诸如 sans, serif 和 monospace 这类通用别名即可。

我简单探索了一下 libXft 的源代码,用 substitute 作为关键词去检索,发现其中有一些地方应该是处理字体 fallback 机制的:

void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern)

另外有些地方还存在这样的注释:

/* Substitute default for non-existant glyphs */

说明它自身在做一些 “缺字” 的处理. Xft 可能有相关的功能,只是我水平太烂,没法看懂(尽管 libXft 是比较简单小巧的库,我依然感觉头疼). Xft 基于 fontconfig, 假如 fontconfig 将字体的匹配机制对上层应用完全透明,将所有的字体匹配集体起来处理好,那么上层应用会轻松得多。

fontconfig 是不可能对字体渲染组件透明的,因为字体渲染组件需要 fontconfig 来告诉它每个字用什么字体(还有一堆字体属性)。