我尝试着去了解字体方面的知识,印象中一共有两次。一次是去年从 macOS 转到 Arch Linux 上的时候,一次是昨天尝试着把一个字体旋转 270 度的时候。

我首先声明我并没有任何制作字体或者是搭配展示字体给其他人看的经历,我所描写的事情说到底不过是我通过自己收集情报、自己尝试得出的一些结论,至于这个结论是否正确,还请读者自己判读。

这篇文章主要说的是第一次尝试了解字体知识时候的经历,彼时正好换电脑系统,在那之前一直使用 mac 系统,可能这个系统默认的字体搭配地比较好,或者我这个人比较愚钝一直没关注电脑上显示的各种字体,最多也就是更换一下写代码时候编辑器的字体,仅此而已。直到我安装上 Arch Linux 之后,发现中文是显示不出来的,也就是出现了所谓的「豆腐块」,为了解决这个问题显然我们需要安装字体,目前 Linux 上应对 CJK 文字的显示主流就是 Adobe 提供的思源宋体、黑体这一套(因为是 Adobe 和 Google 一起开发的,Google 这边叫 Noto Serif/Sans CJK)安装好之后,基本上中日韩的文字就能正确显示了,当然显示地好不好看另说,至少没有豆腐块了。然后我就产生了一个朴素的疑问,我的系统怎么知道我安装了这些字体呢?我的系统又怎么知道用哪一款字体呢?比如我完全可以安装几款不同的西文、中文、日文字体,我上豆瓣网的时候,豆瓣日记里显示字体的时候,它是怎么决定用那一款字体或者哪几款字体的呢?有过编写 CSS 的朋友可能就要说了,因为样式里写了 font-family 的值呀,确实是这样没有错,可是这个值里写的字体名称我都没有安装诶,我的系统是怎么挑出来的呢?于是我就想我的电脑里肯定有一款软件是专门来管这档子事情的,就是你告诉我一个或者一串想要使用的字体,我按照我计算的结果给你一个回应,这样我的浏览器也好、其他软件也好才能正确显示出字体来。经过一番资料查找,这个软件在 Linux 上叫 fontconfig。(Windows 和 macOS 上肯定也有类似的软件)它就像一个中介,在其他软件需要使用字体的时候先来问他一声。再回到上面的问题

  1. 我们安装好了思源黑体之后,为什么系统就能正确显示中文了呢? 因为我安装字体的路径正好是 fontconfig 去寻找字体的地方,想象一下电脑里有个叫作字体仓库的地方,字体默认安装的地点就是那个仓库。这样就解释了为什么安装完字体系统就能识别出这款字体了。(事实上,系统可能不只一个这样的字体仓库,以 fontconfig 为例,系统级别的仓库在 /usr/share/fonts 里,用户级别的在 ~/.fonts 里,而这些地址应该可以通过修改 fontconfig 的配置文件任意定制)
  2. 系统怎么知道用这款字体而不是那一款呢? 肯定有一个优先级的关系,这个就是 fontconfig 的主要功能了,根据用户或者某个程序的查询返回一个带有优先级的字体列表供查询方使用。这个定义具体优先级的规则就有用户来定义了,而为什么用户没有定义也能显示一些默认的字体,那是因为 fontconfig 在安装的时候内置了一些基本的配置(相当于替用户做了一部分决策),保证你安装之后不至于什么字体都展示不了,同样地在安装某些字体安装包的时候甚至也会预制一些这个字体相关的 fontconfig 配置文件。而想要让自己的系统里的各种字体显示得一致又大体符合自己的期望,就需要自己动手写一个属于自己的配置文件,而基本语法在系统搭载的默认配置里都有,那里的配置方法无疑是教科书一样的存在,有很高的学习价值。通过阅读那里的配置(或者其他发行版的配置文件),你大概就可以尝试着定制自己的字体优先级了。

有了以上知识之后,我们可以做些什么呢?显然我们可以按照我们的意志来自定义 fontconfig 返回字体的优先级。在这之前,我们还需要有一点儿基本的字体知识。一般来说我们通常遇到的软件会和三种字体打交道分别是 serif,sans-serif,monospace,分别代表衬线字体,非衬线字体和等宽字体,衬线、非衬线这是西文里的叫法,对应到我们中文或者日文相当于宋体(明朝体),黑体(ゴシック体)。而等宽字体这个概念对于西方字母来说比较明显,而对于汉字来说似乎意义不大,好像都是等宽的。有了这些知识之后,我们明白了一个道理,要想定制自己电脑上字体显示的方式,就是定义字体名字是 serif,sans-serif,monospace 的时候,fontconfig 应该给出怎样的字体匹配列表。然后就会涉及到字体的 fallback 问题,这是什么问题呢?因为一款字体中实现的 glyph(字形,就是某个字到底长什么样子)是有限的(比方说一款英文字体里就没有实现中文字形),我们打了一段话里面可能有英文、中文、还有👌这样的绘文字,就需要多个字体来渲染这段话。如何定义多个字体的渲染优先级就是我们能用 fontconfig 完成的任务了。比如在我这里,拿豆瓣日记来说吧,正文部分它指定了用 sans-serif 字体去渲染,在我的定义里就变成了:Source Sans Pro 你先去试试水,如果你渲染不了了就让 Noto Color Emoji(一款 Google 出的绘文字字体) 去接手,如果你也渲染不了就交给 Sony Mobile UD Gothic (我的索尼手机搭载的默认字体)去渲染,如果它再渲染不了就交给 Noto Sans CJK JP 去渲染,依次往后推,这个过程就叫 fallback。这个长长的备用链,在外部使用者而言,其实非常简单只需要指定个字体名称比如说叫 sans-serif。

但是我们仔细想想,serif 也好,sans-serif 也好还是 monospace,我们电脑上其实都没有这些字体,而我们的系统却能显示出来其实是因为默认的 fontconfig 配置文件里定义了很多这样抽象的字体阵营。也就是说我们通过配置文件来决定了一款字体是属于哪个阵营的。而说到底,这个阵营无非就是个代号,所以你要是愿意完全可以把一款衬线字体(serif)划分到非衬线字体的阵营(sans-serif),这没有任何问题,甚至可以自己命名一个叫 douban 的字体阵营,然后只要定义好字体名是 douban 的时候,真正用于展示的字体优先级是怎样的就行了,这种无中生有的方式是一种灵活的方式。fontconfig 不仅可以无中生有,还能偷梁换柱。什么意思呢,比如某个软件或者某个网站指定了一款字体,但是这款字体我不喜欢,但是我又不想把它从我的电脑上删除,怎么办呢?你可以告诉 fontconfig 让它把那款字体变成任何一款你想要的字体,或者某个字体阵营。比如你可以把 Helvetica、Arial 统统换成 Source Sans Pro(简单一点儿直接换成 sans-serif,如果 sans-serif 你已经精心设置过优先级了的话)。说着似乎有点儿绕(不过这么长的文章看到这里也辛苦了),简单地说明 fontconfig 的灵活性的话,相当于你问主持人要个村上春树,主持人给你扔出个村上信五。(这个例子反而更迷惑了可能)

除此之外,fontconfig 还能对字体进行一些微调,比如西文字体和 CJK 字体都在一段话里的时候,可以适当调整西文字体的大小让整体显得更协调。其他诸多功能还是阅读它的文档比较好。

以上就是第一回字体研究得出的结论了。

(下面附上一些截图)

日文维基百科页面

终端软件只需要指定 Monospace 就能正确显示各种符号