本文的主题是Linux环境下的字体美化,但是首先得要有字体,然后才能谈美化。所以第一件事就是“选字体”。
目前,世界上的字体至少有几万种,并且还在不断增加中。但是基本上都可以归为以下五类:
上述的每一类字体都可以再进一步细分,但本文的目标不是研究字体的分类,所以点到即止。
点阵字体的优点在于低像素下依然可以保持清晰锐利的笔画,但其不能缩放的缺点也非常致命。而矢量字体则相反,可以任意缩放,但在低像素下的表现并不突出。传统的做法是将点阵与矢量配合使用:低像素时使用点阵、高像素时使用矢量。不过在笔者看来,这仅仅是早期的一个权宜之策罢了,当下的矢量字体渲染技术已经非常成熟,特别是在LCD已经全面取代CRT的情况下,组合使用次像素渲染、抗锯齿、字体微调三种技术,矢量字体在低像素下的显示效果已经非常优秀,甚至在某种程度上已经超越了点阵字体。就中文字体的实际情况来说,点阵字体最小一般只能到11~12px,而且选择的余地非常小,只有寥寥几款看上去更像黑体的所谓“xx宋体”;而优秀的中文矢量字体,在配置得当的情况下,可以在10px时依然清晰可辨。基于如上原因,本文将仅使用矢量字体,而不使用任何点阵字体。
筛选要求:
渲染参数:
antialias=truehinting=trueautohint=falsehintstyle=hintslightlcdfilter=lcddefault西文无衬线字体筛选要求:
antialias=truehinting=trueautohint=falsehintstyle=hintslight(10px,12px)hintstyle=hintfull(其他)lcdfilter=lcddefault西文有衬线字体筛选要求:
antialias=truehinting=trueautohint=falsehintstyle=hintslight(Regular/Bold=10px)hintstyle=hintfull(其他)lcdfilter=lcddefault中文黑体(无衬线)筛选要求:
antialias=truehinting=trueautohint=falsehintstyle=hintslightlcdfilter=lcddefault中文宋体(有衬线)、中文仿宋(有衬线)、中文楷体(书法)筛选要求:
[说明]下面的“中文常见字体”列表中,字族名前面圆括号里的"等宽"、"无衬"、"有衬"表示的是该字体的西文部分的风格。
整个配置文件由如下几个部分依次拼接而成:
对于额外的配置目录,本文没有使用,而是将所有的配置指令都直接放在了fonts.conf文件中。这不是官方推荐的做法,因为软件包升级的时候,fonts.conf会被强制覆盖,官方推荐将自定义的配置放在local.conf文件中。但是本文出于两个原因违背了官方的推荐:(1)官方推荐的做法将一个配置文件打散为N多个部分,虽然获得了一定的灵活性,但是不便于理解与维护。而本文只是一个示范,易于理解是首要的。(2)软件包升级之后,再覆盖一下也很方便。
官方默认的配置是每30秒扫描一次配置文件与字体的变化,这里修改为每小时扫描一次。因为频繁的扫描会增加系统的负载,而配置文件与字体在设定好之后,并不会频繁变动,每小时扫描一次足矣。
扫描阶段是最早发生的阶段,它的作用是告诉应用程序:系统上安装了哪些字体,这些字体的每个属性的值是什么。通常用来纠正或修改字体的固有属性。
[提示]要想看到
要充分理解字体匹配过程的细节,需要一个犀利的工具,帮助详细查看Fontconfig的整个工作过程。这个工具就是环境变量"FC_DEBUG"以及"fc-match"。此外,要使用好"fc-match",还必须要熟悉"FontName"(字体模板的格式)的结构。例如以最详细的调试模式,查看对"宋体,15磅,简体中文文档"的匹配过程,可以使用如下命令:
FC_DEBUG=8191fc-match-s'宋体-15:lang=zh-cn'又例如,可以使用如下命令查看火狐浏览器在渲染页面时,与Fontconfig的互动过程:
匹配阶段最主要的工作就是修改"family"列表。
首先第一步,使用已安装的字体替换所有未安装的常见字体,规则如下:
下面是一段仅针对火狐浏览器的配置,作用是删除"family"列表头部不可识别的字体,以确保"family"列表的第一项可以被识别。
你也许会问,那些不可识别的字体,反正也不会被匹配到,也就是说,它们并不影响字体的匹配结果,为什么还要删除它们呢?原因注释里说的很清楚,是为后面补全"zhXXX"与设置"isDengKuan"标记打基础。为了达到这个目标,必须确保"family"列表的第一项是可以识别的:要么是网页内嵌的字体(以'@font-face:'开头),要么是系统上已经安装的核心字体。至于为什么,读到后面你自然就明白了。
下面是西文字体的补全操作,相当于设置默认的西文字体。指令本身一看就懂,不需要过多的解释。
现在你知道DPI的意义了。但是,对这个属性值的设置却并非像前面描述的那样直截了当。下面一个一个的来说明。
先说Fontconfig的默认值"75"吧,这是一个非常不合理的值,目前大多数显示器的实际DPI都比这个值要大,所以我们不能采用。
再说说真实值吧,对于我的显示器来说就是"98.2",这是一个理论上最正确的值,因为它能保证100%精确还原字符的真实大小,如果你对精确还原字符的真实大小很介意(例如经常需要打印预览),那么真实值就是你的不二之选。
再说说Windows的默认值"96"吧,这是一个泛滥成灾的设置,也比"75"更接近主流显示器的实际DPI。正是因为它的泛滥成灾,也在某种程度上成为了事实上的标准。如果你希望同磅值下,显示的字体像素数量"和Windows一样"或者"随大流"、"和大家一样",那么可以使用这个值。但是就别指望100%精确还原字符的真实大小了。呵呵,你看到例子中的值,就知道笔者是个"随大流"的人了,因为98.2和96还是很接近的。
再说说那些"144"甚至"192"这样明显偏大的值吧,这些值是为视力不佳的用户准备的。因为对于同一个显示屏而言,磅值相同的情况下,DPI越大,显示的一个字符需要使用到的像素就越多。所以如果你希望刻意放大字符,可以考虑增大DPI的值。
特别需要提醒的是,这里设置的DPI值,必须要和"xorg.conf"的设置保持一致。具体说来就是"DisplaySize"的值不能简单的填写显示器的实际尺寸(除非你使用了DPI的真实值),而要通过如下公式进行设置:"DisplaySize水平像素数x25.4/DPI垂直像素数x25.4/DPI"。
最后再澄清一个流传甚广的说法:"DPI的值必须是6甚至12的倍数"。这个说法的本意,是为了照顾点阵字体的显示效果,既然本文完全使用矢量字体,而且未来的趋势也将是矢量字体一统天下,你就忘记它吧。
渲染阶段的核心目标是:让字体中的每一个字符,都以其最佳效果在屏幕上显示出来。
改善矢量字体的显示效果,最重要的是下面三种技术:
抗锯齿(antialias)是一个改善字体显示效果的利器。抗锯齿算法不是本文的重点,想了解详细信息可以google之。站在使用者的立场,我们只需要知道,开启它可以大幅度的改善字体显示效果,并且没有什么不良影响。甚至在苹果的MacOSX和iOS上,已经抛弃了字体微调技术(hinting),而完全依赖于抗锯齿技术来改善字体的显示效果。
对于抗锯齿、微调、LCD亚像素渲染,前面"字体渲染三板斧"已有详细解说,这里不再赘述。
禁用内嵌点阵也非常明了。例如Windows中的"宋体"就内嵌了很多点阵,我们的策略既然是使用纯矢量,自然应该关闭它,以保持一致性。
至于禁用合成粗体,其实这个属性的默认值就是"false",这里写出来主要是为了进一步明确这个默认值。
接下来的默认动作是为没有原生斜体/粗体的字体使用合成斜体/粗体:
所谓"合成粗体",是指在常规体的基础上,通过算法生成的粗体,这是一种模拟出来的粗体,而不是原生的粗体。具体算法如下:(1)当标称大小为11px或更小时,通过就地加浓笔画来模拟粗体,因此显示出来的字符的视觉大小就是其标称的大小,例如,标称大小为10px的合成粗体就正好占用10px的屏幕空间。(2)当标称大小为12-35px时,通过在水平和垂直方向各平移一像素并重绘一次来模拟粗体,因此显示出来的合成粗体会比其标称大小大一个像素,例如标称大小为16px的合成粗体,其实际的视觉大小是17px。(3)当标称大小为36-59px时,通过在水平和垂直方向各平移2像素并重绘两次来模拟粗体,因此实际大小会比标称大小大2个像素,例如标称大小为52px的合成粗体,其实际的视觉大小是54px。(4)如果字号更大,合成粗体的平移量还会更大。例如:60-83px,平移3像素;84-107px,平移4像素……。换句话说,平移的像素值可以通过如下公式进行计算:平移像素值=trunc((视觉大小+13.5)/25),其中trunc函数的作用是去尾取整。
通过上面对合成粗体的讲解,我引入了两个概念:"标称大小"与"视觉大小",这里进一步明确一下两者的含义。所谓"标称大小"是指"pixelsize"属性的值,而"视觉大小"是指显示在屏幕上实际大小。对于合成粗体与等宽西文来说,两者有可能不等,但除此之外的其他情况,两者总是相等的。对于合成粗体,两者的差异上面已经讲过,这里不再赘述。对于带有原生粗体的等宽西文(Monospace)而言,由于它的每个西文字符宽度都严格等于半个中文字符宽度,所以当"标称大小"为奇像素的时候(例如17px),由于无法显示半像素字符(8.5px),只能略偏大显示(9px)。因此对于等宽西文(Monospace)而言,偶像素下,"标称大小"与"视觉大小"相同,但在奇像素下,"视觉大小"就要比"标称大小"大一个像素。