Linux字体美化实战(Fontconfig配置)

作者:金步国


版权声明

本文作者是一位开源理念的坚定支持者,所以本文虽然不是软件,但是遵照开源的精神发布。

其他作品

本文作者十分愿意与他人分享劳动成果,如果你对我的其他翻译作品或者技术文章有兴趣,可以在如下位置查看现有的作品集:

联系方式

由于作者水平有限,因此不能保证作品内容准确无误。如果你发现了作品中的错误(哪怕是错别字也好),请来信指出,任何提高作品质量的建议我都将虚心接纳。


序言

【重要】要阅读本文,你必须首先对Fontconfig的工作原理与配置语法有一定的了解,否则建议首先阅读一下我翻译的《fonts.conf 中文手册》。只有理解了Fontconfig的工作原理与配置机制,才能真正掌握Linux字体美化的核心与关键。

本文以作者自己使用的"fonts.conf"文件为实例(建议点开后"查看源代码"),一步一步讲解Linux字体的美化过程,并尽量清晰的讲解每个美化步骤与配置指令的意图和原因,使读者在“知其然”的同时还能“知其所以然”。

选字体

本文的主题是Linux环境下的字体美化,但是首先得要有字体,然后才能谈美化。所以第一件事就是“选字体”。

字体的分类

目前,世界上的字体至少有几万种,并且还在不断增加中。但是基本上都可以归为以下五类:

monospace[等宽]
是指字符宽度相同的字体,用于需要字符严格对齐的场合,例如控制台和源代码以及ASCII艺术。与此相对,字符宽度各不相同的字体称为比例字体(其余四类字体都是)。不过,对于中文字体而言,并不存在等宽与比例的差别,因为所有中文字都是等宽的。我们平常所说的“xx等宽中文”其实只是说该字体的西文部分是等宽的。
sans-serif[无衬线]
是指笔画末端没有修饰(衬线)的字体,通常用于屏幕显示。中文的黑体与圆体就属于此类字体。[注意]很多非专业人士会将粗体与黑体混为一谈,造成概念上的混淆,应注意区分。
serif[有衬线]
是指笔画末端有修饰(衬线)的字体,通常用于打印。中文的宋体与仿宋就属于此类字体。
cursive[书法]
以手写风格呈现的字体,又称为“模拟笔迹”,仅用于需要表达强烈个性风格的场合。例如被很多平面设计师鄙视的"Comic Sans MS",以及中文的隶书、楷书、行楷、行书、草书等都属于此类字体。
calligraphic[艺术]
拥有强烈的艺术风格,同时又不以手写风格呈现的字体,中文通常称为“美术字”,亦仅用于需要表达强烈个性风格的场合。

上述的每一类字体都可以再进一步细分,但本文的目标不是研究字体的分类,所以点到即止。

对于像笔者这样的“普通用户”而言,并不关心如何选择充满个性而又丰富多彩的书法字与艺术字,因为它们的主要使用者是平面设计师这样的专业用户,我更关心的是前三种字体。所以本节主要关注等宽、无衬线、有衬线三种字体的选择。

点阵与矢量

点阵字体的优点在于低像素下依然可以保持清晰锐利的笔画,但其不能缩放的缺点也非常致命。而矢量字体则相反,可以任意缩放,但在低像素下的表现并不突出。传统的做法是将点阵与矢量配合使用:低像素时使用点阵、高像素时使用矢量。不过在笔者看来,这仅仅是早期的一个权宜之策罢了,当下的矢量字体渲染技术已经非常成熟,特别是在LCD已经全面取代CRT的情况下,组合使用次像素渲染、抗锯齿、字体微调三种技术,矢量字体在低像素下的显示效果已经非常优秀,甚至在某种程度上已经超越了点阵字体。就中文字体的实际情况来说,点阵字体最小一般只能到11~12px,而且选择的余地非常小,只有寥寥几款看上去更像黑体的所谓“xx宋体”;而优秀的中文矢量字体,在配置得当的情况下,可以在10px时依然清晰可辨。基于如上原因,本文将仅使用矢量字体,而不使用任何点阵字体。

选中的字体

世上的字体千千万,并不是安装的越多越好。一方面,很多同类字体大同小异,没必要全部安装;另一方面,过多的字体会影响系统的运行效率,而且增加配置的复杂度。所以不能一味贪多,只需要安装几个具有代表性的字体即可。同时在字符覆盖范围上,只需要覆盖东亚文字(中日韩)与西方文字(拉丁/希腊/西里尔)即可,没必要追求覆盖所有字符[反正也看不懂]。下面分门别类的依次说明每类字体的筛选要求、最终胜出的字体的来源,还有为使每种字体达到最佳显示效果,应该使用的渲染参数(最小10px)。

西文等宽字体

筛选要求:

  1. 每个西文字符的宽度正好是中文的一半[spacing=90]
  2. 可以清晰区分"1lI"和"O0o"
  3. 原生粗体
  4. 行高合适[使用"字体查看器"目测即可]

字体来源:最终胜出的是一款来自日本的"Migu 1M"开源字体(migu-1m-20130617.zip),下载后解压,从中提取"migu-1m-bold.ttf"与"migu-1m-regular.ttf"两个文件即可。在实际选择的过程中,笔者发现,能满足后三条的字体有很多,但是能满足第一条的却凤毛麟角,"Ubuntu Mono"可算其中之一,但是美观度嘛,真心不敢恭维。此外,"Migu 1M"字体也是本文所使用的西文字体。

渲染参数:

antialias=true
hinting=true
autohint=false
hintstyle=hintslight
lcdfilter=lcddefault

西文无衬线字体

筛选要求:

  1. 可以清晰区分"1lI"和"O0o"
  2. 字符间距与"Arial"相当(包括粗体)
  3. 原生四体(常规/粗体/斜体/粗斜体)
  4. 行高合适[使用"字体查看器"目测即可]

字体来源:最终胜出的是来自Google的"Note"系列开源字体中的"Noto Sans"字体,下载zip包后解压,从中提取"fonts/individual/hinted/NotoSans-*.ttf"四个文件即可。此外,之所以要求字符间距与"Arial"相当,是因为"Arial"的使用相当广泛,而其字符间距较小,如果我们选取的字体字符间距过大,则有可能会破坏某些网页的布局。在实际选择的过程中,笔者发现,能满足后两条的字体有很多,但是能满足前两条的却不多。特别第一条,大多数字体都无法清晰区分"1lI"和"O0o"。

渲染参数:

antialias=true
hinting=true
autohint=false
hintstyle=hintslight(10px,12px)
hintstyle=hintfull(其他)
lcdfilter=lcddefault

西文有衬线字体

筛选要求:

  1. 可以清晰区分"1lI"和"O0o"
  2. 字符间距与"Times New Roman"相当(包括粗体)
  3. 原生四体(常规/粗体/斜体/粗斜体)
  4. 行高合适[使用"字体查看器"目测即可]

字体来源:最终胜出的是来自Google的"Note"系列开源字体中的"Tinos"字体,下载zip包后解压,从中提取"fonts/individual/hinted/Tinos-*.ttf"四个文件即可。此外,之所以要求字符间距与"Times New Roman"相当,是因为"Times New Roman"的使用相当广泛,而其字符间距较小,如果我们选取的字体字符间距过大,则有可能会破坏某些网页的布局。在实际选择的过程中,笔者发现,能满足后两条的字体有很多,但是能满足前两条的却不多。特别第一条,大多数字体都无法清晰区分"1lI"和"O0o"。就算是最终胜出的字体,也顶多算勉强合格。

渲染参数:

antialias=true
hinting=true
autohint=false
hintstyle=hintslight(Regular/Bold=10px)
hintstyle=hintfull(其他)
lcdfilter=lcddefault

中文黑体(无衬线)

筛选要求:

  1. 汉字笔画符合中国大陆的国家标准
  2. 完整覆盖简体/繁体/粤语/CJK扩展A区/日文字符[使用"字符映射表"即可查看(注意勾选"只显示来自此字体的字形")]
  3. 原生粗体
  4. 行高合适[使用"字体查看器"目测即可]

字体来源:最终胜出的是来自Google的"Note"系列开源字体中的"思源黑体",下载zip包后解压,从中提取"third_party/noto_cjk/NotoSansHans-*.otf"两个文件即可。其实在黑体的选择过程中,我在来自苹果的"冬青黑体"与来自Adobe的"思源黑体"之间摇摆了很久,美观度上,"冬青黑体"某些时候略占微弱优势,但在字符集的覆盖面上,"思源黑体"具有压倒性的优势,尤其是生僻字与各种中日韩符号。加上其开源的特性,最终选择了"思源黑体"。需要提醒的是,不要使用"NotoSansCJK",因为需要解决'locl'问题(如果有谁解决了这个问题请告诉我)。至于被微软吹上天的雅黑,面对它的中文标点符号,我只能“呵呵”了。

渲染参数:

antialias=true
hinting=true
autohint=false
hintstyle=hintslight
lcdfilter=lcddefault

中文宋体(有衬线)、中文仿宋(有衬线)、中文楷体(书法)

筛选要求:

  1. 汉字笔画符合中国大陆的国家标准
  2. 完整覆盖简体/繁体/粤语/CJK扩展A区/日文字符[使用"字符映射表"即可查看(注意勾选"只显示来自此字体的字形")]
  3. 行高合适[使用"字体查看器"目测即可]

字体来源:最终胜出的是来自Adobe CS 6的"Adobe 宋体 Std"、"Adobe 仿宋 Std"、"Adobe 楷体 Std",下载任意一个软件的试用版,即可从中提取如下文件:"AdobeSongStd-Light.otf","AdobeFangsongStd-Regular.otf","AdobeKaitiStd-Regular.otf"。

注意,从 Creative Cloud 开始不再捆绑仿宋与楷体,必须从 Adobe Typekit 订阅(因此只能非官方途径更新)。此外,"Adobe 宋体 Std"还可以从Adobe Reader中提取,在Windows平台安装后,提取"C:\Program Files\Adobe\Reader 11.0\Resource\CIDFont\AdobeSongStd-Light.otf"文件即可。

渲染参数:

antialias=true
hinting=true
autohint=false
hintstyle=hintfull
lcdfilter=lcddefault

韩文字体

字体来源:依然是来自Google的"Note"系列开源字体中的"思源黑体",下载zip包后解压,从中提取"third_party/noto_cjk/NotoSansKR-*.otf"两个文件即可。

渲染参数:

antialias=true
hinting=true
autohint=false
hintstyle=hintslight
lcdfilter=lcddefault

符号字体

字体来源:C:\Windows\Fonts\seguisym.ttf

渲染参数:

antialias=true
hinting=true
autohint=false
hintstyle=hintfull
lcdfilter=lcddefault

常见字体

除了上面选出的字体之外,我们还需要知道常见的中西文字体还包括哪些,后面撰写"fonts.conf"配置文件的时候,需要使用这个信息。下表列出了来自 Windows, MS Office, Mac OS X, iOS, Android, Linux 中常见的中西文字体。

西文常见字体
等宽无衬线有衬线
Monospace
mono
Sans
sans serif
sans-serif
Serif
Andale Mono
Andale Mono WT
Bitstream Vera Sans Mono
Biwidth
Consolas
Courier
Courier New
Cousine
DejaVu Sans Mono
Droid Sans Mono
Fira Mono
Fixed
Fixedsys
FreeMono
Inconsolata
Liberation Mono
Lucida Console
Lucida Sans Typewriter
Luxi Mono
Menlo
Monaco
Nimbus Mono L
Source Code Pro
Terminal
Terminus
Ubuntu Mono
Unibit
Unifont
Akzidenz-Grotesk
Andika
Arev Sans
Arial
Arial Black
Arial Narrow
Arial Unicode MS
Arimo
Avenir
Avenir Next
Avenir Next Condensed
Bitstream Vera Sans
Calibri
Candara
Cantarell
Carlito
Century Gothic
Charcoal
Chicago
Copperplate Gothic
Corbel
DejaVu Sans
DejaVu Sans Condensed
Droid Sans
Droid Sans Fallback
Fira Sans
Franklin Gothic Bold
Franklin Gothic Heavy
Franklin Gothic Medium
FreeSans
Frutiger
Frutiger Linotype
Futura
Gadget
Geneva
Gill Sans
Gill Sans MT
Gotham
Haettenschweiler
Helvetica
Helvetica Neue
Impact
Liberation Sans
Liberation Sans Narrow
Linux Biolinum
Lucida Grande
Lucida Sans
Lucida Sans Unicode
Luxi Sans
Microsoft Sans Serif
MS Sans Serif
Myriad
Myriad Pro
News Gothic MT
Nimbus Sans L
Noto Sans
Noto Sans UI
Open Sans
Optima
Roboto
Roboto Condensed
Segoe
Segoe UI
Source Sans Pro
Tahoma
Thonburi
Trebuchet MS
Tw Cen MT
Ubuntu
Ubuntu Condensed
Univers
Verdana
Baskerville
Baskerville Old Face
Bell MT
Big Caslon
Bitstream Charter
Bitstream CyberBase
Bitstream Cyberbit
Bitstream Vera Serif
Bodoni MT
Book Antiqua
Bookman Old Style
Californian FB
Calisto MT
Cambria
Centaur
Century
Century Schoolbook
Century Schoolbook L
Charis SIL
Code2000
Code2001
Constantia
Cooper Black
DejaVu Serif
DejaVu Serif Condensed
Didot
Droid Serif
Elephant
FreeSerif
Gabriola
Garamond
Gentium
Georgia
Goudy Old Style
High Tower Text
Hoefler Text
Junicode
Liberation Serif
Linux Libertine
Lucida Bright
Lucida Fax
Luxi Serif
MS Serif
New Athena Unicode
New York
Nimbus Roman No9 L
Noto Serif
Old Standard TT
Palatino
Palatino Linotype
Perpetua
Rockwell
Sitka
Sitka Banner
Sitka Display
Sitka Heading
Sitka Small
Sitka Subheading
Sitka Text
Source Serif Pro
Times
Times New Roman
Tinos
Utopia

[说明]下面的“中文常见字体”列表中,字族名前面圆括号里的"等宽"、"无衬"、"有衬"表示的是该字体的西文部分的风格。

中文常见字体
黑体宋体仿宋楷体
(无衬)"Source Han Sans"
(无衬)"Source Han Sans CN"
(无衬)"Source Han Sans SC"
(无衬)"Source Han Sans TC"
(无衬)"Source Han Sans TWHK"
(无衬)"思源黑体"
(无衬)"思源黑體"
(无衬)"思源黑体 CN"
(无衬)"思源黑體 TWHK"
(无衬)"Noto Sans CJK"
(无衬)"Noto Sans CJK Simplified Chinese"
(无衬)"Noto Sans CJK Traditional Chinese"
(无衬)"Noto Sans S Chinese"
(无衬)"Noto Sans T Chinese"
(无衬)"Hiragino Sans GB"
(无衬)"冬青黑體簡體中文"
(无衬)"冬青黑体简体中文"
(无衬)"Adobe Heiti Std"
(无衬)"Adobe 黑体 Std"
(无衬)"Adobe Fan Heiti Std"
(无衬)"Adobe 繁黑體 Std"
(无衬)"Hei"
(无衬)"LiHei Pro"
(无衬)"STHeiti"
(等宽)"SimHei"
(等宽)"黑体"
(无衬)"Heiti TC"
(无衬)"黑體-繁"
(无衬)"黑体-繁"
(无衬)"Heiti SC"
(无衬)"黑體-簡"
(无衬)"黑体-简"
(无衬)"STXihei"
(无衬)"华文细黑"
(无衬)"Lantinghei SC"
(无衬)"蘭亭黑-簡"
(无衬)"兰亭黑-简"
(无衬)"Lantinghei TC"
(无衬)"蘭亭黑-繁"
(无衬)"兰亭黑-繁"
(无衬)"Microsoft JhengHei"
(无衬)"Microsoft JhengHei UI"
(无衬)"微軟正黑體"
(无衬)"Microsoft YaHei"
(无衬)"Microsoft YaHei UI"
(无衬)"微软雅黑"
(无衬)"WenQuanYi Bitmap Song"
(无衬)"WenQuanYi Micro Hei"
(无衬)"文泉驛微米黑"
(无衬)"文泉驿微米黑"
(等宽)"WenQuanYi Micro Hei Mono"
(等宽)"文泉驛等寬微米黑"
(等宽)"文泉驿等宽微米黑"
(无衬)"WenQuanYi Zen Hei"
(无衬)"文泉驛正黑"
(无衬)"文泉驿正黑"
(等宽)"WenQuanYi Zen Hei Mono"
(等宽)"文泉驛等寬正黑"
(等宽)"文泉驿等宽正黑"
(无衬)"WenQuanYi Zen Hei Sharp"
(无衬)"文泉驛點陣正黑"
(无衬)"文泉驿点阵正黑"
(有衬)"Adobe Song Std"
(有衬)"Adobe 宋体 Std"
(有衬)"Adobe Ming Std"
(有衬)"Adobe 明體 Std"
(等宽)"AR PL UMing CN"
(等宽)"AR PL UMing HK"
(等宽)"AR PL UMing TW"
(等宽)"AR PL UMing TW MBE"
(有衬)"Apple LiSung"
(有衬)"GB18030 Bitmap"
(有衬)"LiSong Pro"
(等宽)"Ming(for ISO10646)"
(有衬)"STSong"
(有衬)"华文宋体"
(有衬)"STZhongsong"
(有衬)"华文中宋"
(有衬)"SimSun"
(有衬)"宋体"
(等宽)"NSimSun"
(等宽)"新宋体"
(等宽)"SimSun-ExtB"
(有衬)"SimSun-18030"
(有衬)"宋体-18030"
(等宽)"NSimSun-18030"
(等宽)"新宋体-18030"
(有衬)"Songti SC"
(有衬)"宋體-簡"
(有衬)"宋体-简"
(有衬)"Songti TC"
(有衬)"宋體-繁"
(有衬)"宋体-繁"
(有衬)"AR PLBaosong2GBK Light"
(有衬)"文鼎PL报宋二GBK"
(等宽)"AR PL SungtiL GB"
(等宽)"文鼎PL简报宋"
(有衬)"AR PLMingU20 Light"
(有衬)"文鼎PL明體U20-L"
(等宽)"AR PL Mingti2L Big5"
(等宽)"文鼎PL細上海宋"
(有衬)"AR PL ShanHeiSun Uni"
(有衬)"文鼎PL细上海宋Uni"
(有衬)"文鼎PL細上海宋Uni"
(有衬)"AR PL New Sung"
(有衬)"文鼎PL新宋"
(等宽)"AR PL New Sung Mono"
(等宽)"文鼎PL新宋 Mono"
(等宽)"MingLiU"
(等宽)"細明體"
(等宽)"MingLiU-ExtB"
(等宽)"細明體-ExtB"
(有衬)"PMingLiU"
(有衬)"新細明體"
(有衬)"PMingLiU-ExtB"
(有衬)"新細明體-ExtB"
(等宽)"MingLiU_HKSCS"
(等宽)"細明體_HKSCS"
(等宽)"MingLiU_HKSCS-ExtB"
(等宽)"細明體_HKSCS-ExtB"
(有衬)"Adobe Fangsong Std"
(有衬)"Adobe 仿宋 Std"
(等宽)"FangSong_GB2312"
(等宽)"仿宋_GB2312"
(等宽)"FangSong"
(等宽)"仿宋"
(有衬)"STFangsong"
(有衬)"华文仿宋"
(有衬)"Adobe Kaiti Std"
(有衬)"Adobe 楷体 Std"
(等宽)"AR PL UKai CN"
(等宽)"AR PL UKai HK"
(等宽)"AR PL UKai TW"
(等宽)"AR PL UKai TW MBE"
(有衬)"BiauKai"
(有衬)"Kai"
(等宽)"DFKai-SB"
(等宽)"標楷體"
(有衬)"STKaiti"
(有衬)"华文楷体"
(等宽)"KaiTi"
(等宽)"楷体"
(有衬)"Kaiti SC"
(有衬)"楷體-簡"
(有衬)"楷体-简"
(有衬)"Kaiti TC"
(有衬)"楷體-繁"
(有衬)"楷体-繁"
(等宽)"SimKai"
(等宽)"KaiTi_GB2312"
(等宽)"楷体_GB2312"
(等宽)"AR PL KaitiM GB"
(等宽)"文鼎PL简中楷"
(等宽)"AR PL New Kai"
(等宽)"文鼎PL新中楷"
(等宽)"AR PL KaitiM Big5"
(等宽)"文鼎PL中楷"
(有衬)"AR PL ZenKai Uni"
(有衬)"文鼎PL中楷Uni"

fonts.conf详解

配置文件的结构

整个配置文件由如下几个部分依次拼接而成:

  1. 目录设置(<dir>, <cachedir>, <include>)
  2. 杂项设置(<config>)
  3. 扫描阶段(<match target="scan">)
  4. 匹配阶段(<alias>, <match target="pattern">)
  5. 渲染阶段(<match target="font">)

目录设置

<!-- 字体目录 -->
<dir>/opt/fonts/core</dir>
<!-- <dir>/opt/fonts/ext</dir> -->

由于这些字体都是自己选取的,不受系统的包管理器控制,所以安装到"/opt/fonts"目录中。至于为什么还要分为"core"与"ext"两个子目录,出于如下原因:对某些用户而言,本文使用的字体不足以满足其需求,他们需要安装更多的字体,但是这些字体又不需要放到 xorg.conf 中的 FontPath 里面,所以单独再隔开一个目录,效果会比较好。

<!-- 缓存目录 -->
<cachedir>/var/cache/fontconfig</cachedir>

缓存目录没啥好说的,整个系统范围内的字体信息都缓存到同一个地方而已。

<!-- 额外的配置目录 -->
<!-- <include>/etc/fonts/conf.d</include> -->

对于额外的配置目录,本文没有使用,而是将所有的配置指令都直接放在了 fonts.conf 文件中。这不是官方推荐的做法,因为软件包升级的时候,fonts.conf 会被强制覆盖,官方推荐将自定义的配置放在 local.conf 文件中。但是本文出于两个原因违背了官方的推荐:(1)官方推荐的做法将一个配置文件打散为N多个部分,虽然获得了一定的灵活性,但是不便于理解与维护。而本文只是一个示范,易于理解是首要的。(2)软件包升级之后,再覆盖一下也很方便。

杂项设置

<config>
	<blank><!-- 空白字符 -->
		<int>0x0020</int><int>0x00A0</int><int>0x00AD</int><int>0x034F</int>
		<int>0x0600</int><int>0x0601</int><int>0x0602</int><int>0x0603</int>
		<int>0x06DD</int><int>0x070F</int><int>0x115F</int><int>0x1160</int>
		<int>0x1680</int><int>0x17B4</int><int>0x17B5</int><int>0x180E</int>
		<range><int>0x2000</int><int>0x200F</int></range>
		<range><int>0x2028</int><int>0x202F</int></range>
		<range><int>0x205F</int><int>0x2063</int></range>
		<range><int>0x206A</int><int>0x206F</int></range>
		<int>0x2800</int><int>0x3000</int><int>0x3164</int><int>0xFEFF</int>
		<int>0xFFA0</int><int>0xFFF9</int><int>0xFFFA</int><int>0xFFFB</int>
	</blank>
	<!-- 每小时扫描一次配置文件与字体的变化 -->
	<rescan><int>3600</int></rescan>
</config>

<blank>段指明了哪些Unicode字符应该显示为空白,这是直接从官方默认的配置中抄来的,除了出于缩短配置文件长度的目的,把一系列连续的<int>用<range>进行了取代之外,没有任何修改。关于<blank>的详细解释,请参考《fonts.conf 中文手册》

官方默认的配置是每30秒扫描一次配置文件与字体的变化,这里修改为每小时扫描一次。因为频繁的扫描会增加系统的负载,而配置文件与字体在设定好之后,并不会频繁变动,每小时扫描一次足矣。

扫描阶段

扫描阶段是最早发生的阶段,它的作用是告诉应用程序:系统上安装了哪些字体,这些字体的每个属性的值是什么。通常用来纠正或修改字体的固有属性。

[提示]要想看到<match target="scan">对"foo.ttf"的效果,可以分别使用如下两个命令,对比它们的输出结果就可以了:

fc-query /path/to/foo.ttf
fc-scan /path/to/foo.ttf

西文字体举例

<match target="scan">
	<test name="family"><string>Migu 1M</string></test>
	<edit name="family"><string>Monospace</string></edit>
	<edit name="lang"><langset><string>en</string></langset></edit>
	<edit name="charset">
		<charset>
			<!-- 基本拉丁(ASCII) -->
			<range><int>0x0020</int><int>0x007E</int></range>
			<!-- 此处省略了其他<range>与<int>单元,完整的配置请查看fonts.conf -->
		</charset>
	</edit>
</match>

<edit name="family">的作用是将不直观的字族名称"Migu 1M"修改为通用名称"Monospace",目的在于更清晰的表示这是默认的等宽字体。此外,改为通用名称还有一个好处,那就是可以在字体选择列表中节约一行,因为无论你的系统上是否真的安装了名为"Monospace"的字体,都会显示这么一行,既然如此,那不如将默认的等宽字体直接改为通用名称算了。类似的,另外两个西文字体的重命名("Sans"与"Serif"),也是出于同样的原因。

<edit name="lang">的作用是强制将该字体支持的语言指定为"en",配合后面匹配阶段的指令(<edit name="lang"><string>en</string></edit>),可以确保优先使用西文字体显示西文字符,以实现对中文字体中丑陋的西文部分的替换(因为"lang"属性优先于弱绑定的字族名)。类似的,另外两个西文字体也进行了同样的修改。

<edit name="charset">的作用是明确指定该字体所含字符的Unicode码点,主要用于屏蔽字体中的某些字符。以"Migu 1M"字体来说,因为它是个来自日本的字体,里面不仅包含了我们需要的西文字符,也包含了许多CJK统一表意字符以及全角标点符号等非西文字符,如果我们不加屏蔽的使用,就会出现许多汉字的笔画不符合中国大陆的国家标准(日文写法),以及中文标点符号不美观之类的问题,所以必须加以屏蔽。类似的,另外两个西文字体("Sans"与"Serif"),由于它们都包含了不美观的中文标点符号,以及一些不想使用的非西文字符(拉丁/希腊/西里尔之外的字符),所以也用同样的方法进行了屏蔽。

中文字体举例

<match target="scan">
	<test name="postscriptname"><string>AdobeSongStd-Light</string></test>
	<edit name="family"><string>zhSong</string></edit>
	<edit name="style"><string>Regular</string></edit>
	<edit name="weight"><int>80</int></edit>
	<edit name="lang"><langset><string>zh-cn</string></langset></edit>
</match>

与前述西文字体类似,这里使用<edit name="family">将字族名修改为"zhSong"(不存在的字族名),以确保它不会被网页上指定的字体(强绑定)匹配到,配合后面匹配阶段的指令(<alias>与<edit name="lang"><string>en</string></edit>),可以确保优先使用西文字体显示西文字符,以实现对中文字体中丑陋的西文部分的替换(因为"lang"属性优先于弱绑定的字族名)。类似的,其他三个核心中文字体也进行了同样的修改。

<edit name="style">将字体样式的名称修改为"Regular",以确保所有字体的样式都在显示上保持一致性,否则将会在字体样式中显示"L"而不是"Regular"。同样的,使用<edit name="weight">将字重值修改为"80"也是为了保持和其他字体的一致性。

<edit name="lang">的作用是强制将该字体支持的语言指定为"zh-cn",配合后面匹配阶段的指令(<edit name="lang"><string>en</string></edit>),可以确保优先使用西文字体显示西文字符,以实现对中文字体中丑陋的西文部分的替换(因为"lang"属性优先于弱绑定的字族名)。类似的,其他三个核心中文字体也进行了同样的修改。

其他字体举例

<match target="scan">
	<test name="postscriptname"><string>NotoSansKR-Bold</string></test>
	<edit name="family"><string>zzFailBack</string><string>zzKorean</string></edit>
	<edit name="familylang"><string>en</string><string>en</string></edit>
	<edit name="lang"><langset><string>ko</string></langset></edit>
</match>

对于韩文字体(非中西文字体),将字族名修改为"zzFailBack"+"zzKorean"(也就是有两个字族名),前一个是首选名字,会在字体列表中显示出来,后一个是字体自身的名字,用于在配置文件中区分各个字体。而将所有非中西文字体以及符号字体统一赋予一个首选的"zzFailBack"名称,目的在于将所有非中西文字体在字体列表中归结为同一个项目,以节约列表空间。

调试工具

要充分理解字体匹配过程的细节,需要一个犀利的工具,帮助详细查看Fontconfig的整个工作过程。这个工具就是环境变量"FC_DEBUG"以及"fc-match"。此外,要使用好"fc-match",还必须要熟悉"Font Name"(字体模板的格式)的结构。例如以最详细的调试模式,查看对"宋体,15磅,简体中文文档"的匹配过程,可以使用如下命令:

FC_DEBUG=8191 fc-match -s '宋体-15:lang=zh-cn'

又例如,可以使用如下命令查看火狐浏览器在渲染页面时,与Fontconfig的互动过程:

FC_DEBUG=5 firefox http://www.example.com/foo.html

[提示]输出的调试信息中,前面有很长一部分是显示火狐自身用户界面(例如菜单文字)时的信息,后面才是网页渲染相关的信息。

匹配阶段

"family"列表的修改

匹配阶段最主要的工作就是修改"family"列表。

首先第一步,使用已安装的字体替换所有未安装的常见字体,规则如下:

  1. 保持原有绑定不变
  2. 将常见西文字体替换为对应的字体类
  3. 将常见中文字体替换为对应的"西文字体类+zhXXX"(参见上一节的"中文常见字体"表)
<!-- 第一步,替换所有未安装的常见字体 -->
<alias binding="same"><family>mono</family><prefer><family>Monospace</family></prefer></alias>
<alias binding="same"><family>Candara</family><prefer><family>Sans</family></prefer></alias>
<alias binding="same"><family>文鼎PL中楷</family><prefer><family>Monospace</family><family>zhKai</family></prefer></alias>
<alias binding="same"><family>AR PL ZenKai Uni</family><prefer><family>Serif</family><family>zhKai</family></prefer></alias>
<!-- 这里省略了300多个其他类似的<alias>单元,完整的配置请查看fonts.conf -->

经过上述一系列替换动作之后,每一个能够被识别的常见字体,都找到了替身。

下面是一段仅针对火狐浏览器的配置,作用是删除"family"列表头部不可识别的字体,以确保"family"列表的第一项可以被识别。

<!-- 第二步,剔除列表头部不可识别的字体(仅对firefox),重复5次,确保剔除干净,为后面补全"zhXXX"与设置"isDengKuan"标记打基础 -->
<match>
	<test name="prgname"><string>firefox</string></test>
	<test qual="first" name="family" compare="not_contains"><string>@font-face:</string></test>
	<test qual="first" name="family" compare="not_eq"><string>Monospace</string></test>
	<test qual="first" name="family" compare="not_eq"><string>Sans</string></test>
	<test qual="first" name="family" compare="not_eq"><string>Serif</string></test>
	<test qual="first" name="family" compare="not_eq"><string>zhHei</string></test>
	<test qual="first" name="family" compare="not_eq"><string>zhSong</string></test>
	<test qual="first" name="family" compare="not_eq"><string>zhFangSong</string></test>
	<test qual="first" name="family" compare="not_eq"><string>zhKai</string></test>
	<edit name="family" mode="delete"></edit>
</match>
<!-- 这里省略了其他4个完全相同的配置段 -->

首先,只有类似浏览器这种需要处理外来不可控文档的场合,才会出现不可识别的字体,而对于可控文档(例如文本编辑器),使用的是字体选择器选定的字体,不会出现无法识别的情况。其次,删除不可识别字体是一个破坏性的操作,可能会造成不良后果,必须谨慎为之,所以才将上述配置严格限定于仅作用于火狐浏览器(<test name="prgname">)。

你也许会问,那些不可识别的字体,反正也不会被匹配到,也就是说,它们并不影响字体的匹配结果,为什么还要删除它们呢?原因注释里说的很清楚,是为后面补全"zhXXX"与设置"isDengKuan"标记打基础。为了达到这个目标,必须确保"family"列表的第一项是可以识别的:要么是网页内嵌的字体(以'@font-face:'开头),要么是系统上已经安装的核心字体。至于为什么,读到后面你自然就明白了。

下面是西文字体的补全操作,相当于设置默认的西文字体。指令本身一看就懂,不需要过多的解释。

<!-- 第三步,如果不存在任何西文字体类,那么追加一个默认的西文字体类(弱绑定) -->
<match>
	<test name="family" qual="all" compare="not_eq"><string>Monospace</string></test>
	<test name="family" qual="all" compare="not_eq"><string>Sans</string></test>
	<test name="family" qual="all" compare="not_eq"><string>Serif</string></test>
	<edit name="family" mode="append_last"><string>Sans</string></edit>
</match>

经过上面的西文字体补全操作之后,现在"family"列表中必定存在至少一个西文字体类。这样就可以根据西文字体来进行中文字体的补全操作了(相当于设置默认的中文字体):

<!-- 第四步,如果不存在任何核心中文(zhXXX),那么根据西文字体类补上对应的"zhXXX"(弱绑定) -->
<match>
	<test name="family" qual="all" compare="not_eq"><string>zhHei</string></test>
	<test name="family" qual="all" compare="not_eq"><string>zhSong</string></test>
	<test name="family" qual="all" compare="not_eq"><string>zhFangSong</string></test>
	<test name="family" qual="all" compare="not_eq"><string>zhKai</string></test>
	<test name="family" qual="all" compare="not_eq"><string>Serif</string></test><!-- 没有Serif -->
	<edit name="family" mode="append_last"><string>zhHei</string></edit><!-- 那么仅有Monospace与Sans -->
</match>
<match>
	<test name="family" qual="all" compare="not_eq"><string>zhHei</string></test>
	<test name="family" qual="all" compare="not_eq"><string>zhSong</string></test>
	<test name="family" qual="all" compare="not_eq"><string>zhFangSong</string></test>
	<test name="family" qual="all" compare="not_eq"><string>zhKai</string></test>
	<test name="family" qual="all" compare="not_eq"><string>Monospace</string></test><!-- 没有Monospace -->
	<test name="family" qual="all" compare="not_eq"><string>Sans</string></test><!-- 并且没有Sans -->
	<edit name="family" mode="append_last"><string>zhSong</string></edit><!-- 那么仅有Serif -->
</match>
<!-- 继续第四步,如果"family"列表中依然没有"zhXXX",那就表示出现了Monospace,Sans与Serif混杂的情况。那么根据列表第一项决定对应的"zhXXX"(弱绑定) -->
<match>
	<test name="family" qual="all" compare="not_eq"><string>zhHei</string></test>
	<test name="family" qual="all" compare="not_eq"><string>zhSong</string></test>
	<test name="family" qual="all" compare="not_eq"><string>zhFangSong</string></test>
	<test name="family" qual="all" compare="not_eq"><string>zhKai</string></test>
	<test name="family" qual="first"><string>Serif</string></test><!-- 第一项是Serif -->
	<edit name="family" mode="append_last"><string>zhSong</string></edit>
</match>
<match>
	<test name="family" qual="all" compare="not_eq"><string>zhHei</string></test>
	<test name="family" qual="all" compare="not_eq"><string>zhSong</string></test>
	<test name="family" qual="all" compare="not_eq"><string>zhFangSong</string></test>
	<test name="family" qual="all" compare="not_eq"><string>zhKai</string></test>
	<!-- 如果依然没有"zhXXX",那就表示列表第一项是Monospace,Sans或无法识别(例如"@font-face:*") -->
	<edit name="family" mode="append_last"><string>zhHei</string></edit>
</match>

经过上面的中文字体补全操作之后,现在"family"列表中必定存在至少一个中文字体,并且尽可能保持与西文字体的风格一致。

<!-- 最后一步,在列表结尾补上韩文与符号字体(弱绑定) -->
<match>
	<edit name="family" mode="append_last"><string>zzKorean</string><string>zzSymbol</string></edit>
</match>

其他属性

<match>
	<!-- 设置合理的像素密度,确保pt与px之间能够合理转换 -->
	<edit name="dpi"><double>96</double></edit>
</match>

像素密度(DPI)是一个非常重要的参数,它是pt(磅)与px(像素)之间相互转换的桥梁。"磅"是一个绝对长度单位,具体说来就是:1磅=1/72英寸=0.35278毫米。但是"像素"却是一个相对长度单位。以我的显示屏为例,其分辨率是1280x800像素,物理尺寸是331x207毫米(也就是13.0315x8.1496英寸),那么,水平DPI=1280/13.0315=98.22,垂直DPI=800/8.1496=98.16,也就是说,我的显示器实际DPI大约是"98.2"(像素/英寸)。进一步可知,对于我的显示器而言,1像素=(1/98.2)英寸=0.2586558毫米。如果应用程序要求显示16px大小的文字,那很简单,直接按16px显示就可以了。但是如果应用程序要求显示16pt(=16/72英寸=5.6444毫米)大小的文字,那么就得用(16*98.2)/72也就是21.82个像素来显示(实际会四舍五入为22px)。

现在你知道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的倍数"。这个说法的本意,是为了照顾点阵字体的显示效果,既然本文完全使用矢量字体,而且未来的趋势也将是矢量字体一统天下,你就忘记它吧。

<match>
	<!-- 确保弱绑定西文字体优先于弱绑定中文字体 -->
	<edit name="lang"><string>en</string></edit>
</match>

对于弱绑定"family"来说,其匹配优先级低于"lang",这就会导致当"lang=zh-cn"的时候(例如浏览中文网页),弱绑定中文字体的优先级高于弱绑定西文字体,而这是我们所不愿看到的。因此强制将"lang"属性设为"en"可以确保西文字体优先于中文字体,从而确保对中文字体中丑陋的西文部分的替换。

<match>
	<!-- 设置等宽标记 -->
	<edit name="isDengKuan"><eq><name>family</name><string>Monospace</string></eq></edit>
</match>

这是个无中生有的标记,用于标记首选字体是否为等宽字体。当"family"列表的第一项是"Monospace"的时候,其值为"true",否则,其值为"false"。经过前面针对火狐浏览器的5个连续剔除列表头部不可识别字体的动作之后,至少在99%的情况下,"family"列表头部的不可识别字体都被剔除干净了。这样就可以保证对等宽识别的正确率至少有99%。至于这个标记的用途,继续往后看就知道了。

渲染阶段

渲染阶段的核心目标是:让字体中的每一个字符,都以其最佳效果在屏幕上显示出来。

字体渲染三板斧

改善矢量字体的显示效果,最重要的是下面三种技术:

抗锯齿(antialias)是一个改善字体显示效果的利器。抗锯齿算法不是本文的重点,想了解详细信息可以google之。站在使用者的立场,我们只需要知道,开启它可以大幅度的改善字体显示效果,并且没有什么不良影响。甚至在苹果的 Mac OS X 和 iOS 上,已经抛弃了字体微调技术(hinting),而完全依赖于抗锯齿技术来改善字体的显示效果。

微调(hinting)分为两种:内嵌微调与自动微调。内嵌微调是字体设计者耗费大量的时间和精力、精心制作的、内嵌在字体文件中的额外指令,本质上是人工微调;而自动微调(autohint)是FreeType内嵌的一套微调算法,通用于所有矢量字体,本质上是机器微调。一般说来,内嵌微调比自动微调的显示效果更佳。与微调相关的属性有三个:

hinting
微调功能的总开关,一旦关闭便彻底禁用微调(包括内嵌微调与自动微调)。
autohint
是否优先使用自动微调。设为"true"表示只使用自动微调,不使用内嵌微调;设为"false"表示优先使用内嵌微调,但对于没有内嵌微调的字体仍会使用自动微调。
hintstyle
微调的程度(同时作用于内嵌微调与自动微调):(1)"hintnone"表示禁用微调(包括自动微调与内嵌微调),通常用于字号非常小的场合;(2)"hintslight"表示优先保持字符的形态,但是可能会降低笔画的锐利度,通常更适合于自动微调以及小字号下的内嵌微调;(3)"hintmedium"表示在字符形态与笔画锐利度之间进行折中,但实际效果通常相当于"hintfull";(4)"hintfull"表示优先提高笔画的锐利度,但是可能破坏字符的形态,通常更适合于内嵌微调;[注意]这里对微调风格的解释仅是一个提示,切勿教条化,对于特定的字体、特定的字号,哪种微调风格的效果最佳,最可靠的途径是通过自己的眼睛去亲自查看与对比。此外,还由于每个人的口味不同,风格有所偏向,特别是对于"hintslight"与"hintfull",不同的人可能会有相反的喜好。

亚像素渲染是一个针对LCD显示屏的技术(切勿用于CRT显示屏),与微软的ClearType技术同属一类。其基本原理是,将显示器的R,G,B各个次像素也分别进行控制,让其进行微妙的调整,相当于提升了分辨率,从而显示出更细腻平滑的笔画。与亚像素渲染相关的属性有两个:

rgba
LCD子像素的排列顺序,不同的LCD排列顺序未必相同,可能的取值如下:(1)"unknown"表示未知;(2)横向(水平)"Red Green Blue"顺序,这是最常见的排列顺序;(3)横向(水平)"Blue Green Red"顺序;(4)纵向(垂直)"Red Green Blue"顺序,例如把显示器垂直放置的时候;(5)纵向(垂直)"Blue Green Red"顺序;(6)"none"无子像素(例如传统的CRT显示器),其实就是关闭亚像素渲染。
lcdfilter
LCD filter 的风格:(1)"lcdnone"表示彻底关闭 LCD filter,不推荐,因为它会导致笔画边缘出现彩色边纹;(2)"lcddefault"表示最大限度的消除彩色边纹,但是可能会增加笔画的模糊程度。多数场合这是最佳选择;(3)"lcdlight"表示减轻笔画的模糊程度,但不能最大限度的消除彩色边纹。少数场合也许效果更好;(4)"lcdlegacy"是为了与传统的"libXft color filter"兼容而设置,未来会被删除;

设置默认的渲染参数

<!-- 第一步,设置默认的渲染参数 -->
<match target="font">
	<!-- 修整像素大小(小于10px的调整到10px,否则四舍五入到整数) -->
	<edit name="pixelsize">
		<if>
			<less><name>pixelsize</name><double>10</double></less>
			<int>10</int>
			<round><name>pixelsize</name></round>
		</if>
	</edit>

	<!-- 开启抗锯齿(smooth) -->
	<edit name="antialias"><bool>true</bool></edit>

	<!-- 优先使用内嵌微调,同时默认开足微调 -->
	<edit name="hinting"><bool>true</bool></edit>
	<edit name="autohint"><bool>false</bool></edit>
	<!-- 依个人喜好,你也可能喜欢默认"hintslight"(此时可将下面的"第七步"全部注释掉) -->
	<edit name="hintstyle"><const>hintfull</const></edit>

	<!-- LCD特征设置 -->
	<edit name="rgba"><const>rgb</const></edit>
	<edit name="lcdfilter"><const>lcddefault</const></edit>

	<!-- 禁用内嵌点阵 -->
	<edit name="embeddedbitmap"><bool>false</bool></edit>

	<!-- 禁用合成粗体 -->
	<edit name="embolden"><bool>false</bool></edit>
</match>

修整像素大小的指令很简单,主要是将像素值四舍五入到整数,同时确保最低显示10px大小的字符。因为对于汉字来说,10px基本是极限,再小就几乎没法阅读了。另外,将像素值四舍五入到整数也是为后面进一步修正像素大小做准备。

对于抗锯齿、微调、LCD亚像素渲染,前面"字体渲染三板斧"已有详细解说,这里不再赘述。

禁用内嵌点阵也非常明了。例如Windows中的"宋体"就内嵌了很多点阵,我们的策略既然是使用纯矢量,自然应该关闭它,以保持一致性。

至于禁用合成粗体,其实这个属性的默认值就是"false",这里写出来主要是为了进一步明确这个默认值。

接下来的默认动作是为没有原生斜体/粗体的字体使用合成斜体/粗体:

<!-- 第二步,为没有原生斜体的字体使用合成斜体 -->
<match target="font">
	<test name="slant" compare="eq"><const>roman</const></test>
	<test name="slant" compare="not_eq" target="pattern"><const>roman</const></test>
	<edit name="slant"><const>oblique</const></edit>
	<edit name="matrix">
		<times>
			<name>matrix</name>
			<matrix>
				<double>1</double><double>0.2</double>
				<double>0</double><double>1</double>
			</matrix>
		</times>
	</edit>
</match>
<!-- 第三步,为没有原生粗体的字体使用合成粗体 -->
<match target="font">
	<test name="weight" compare="less"><int>105</int></test>
	<test name="weight" compare="more" target="pattern"><int>105</int></test>
	<edit name="weight"><const>bold</const></edit>
	<edit name="embolden"><bool>true</bool></edit>
</match>

上述配置本身简单易懂,这里想详细说说的是合成粗体的工作原理,因为后面进一步修正像素大小的时候,如果预先知道合成粗体的工作原理,会更容易理解。

所谓"合成粗体",是指在常规体的基础上,通过算法生成的粗体,这是一种模拟出来的粗体,而不是原生的粗体。具体算法如下:(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)而言,偶像素下,"标称大小"与"视觉大小"相同,但在奇像素下,"视觉大小"就要比"标称大小"大一个像素。

<!-- 第四步,标记"视觉大小"(原本的标称值)是否为奇数,为接下来修正等宽条件下的"标称大小"做准备 -->
<match target="font">
	<edit name="isOddPx">
		<eq>
			<round><divide><plus><name>pixelsize</name><double>0.5</double></plus><double>2</double></divide></round>
			<ceil><divide><plus><name>pixelsize</name><double>0.5</double></plus><double>2</double></divide></ceil>
		</eq>
	</edit>
</match>
<!-- 第五步,修正合成粗体的"标称大小",尽力确保其"视觉大小"与原本的标称值一致 -->
<match target="font">
	<test name="embolden"><bool>true</bool></test>
	<!-- 标称大小=视觉大小-trunc((视觉大小+13.5)/25) -->
	<edit name="pixelsize">
		<minus>
			<name>pixelsize</name>
			<trunc><divide><plus><name>pixelsize</name><double>13.5</double></plus><double>25</double></divide></trunc>
		</minus>
	</edit>
</match>
<!-- 第六步,在等宽条件下,为确保中西文对齐,进一步修正"标称大小"(也会影响"视觉大小") -->
<match target="font">
	<test name="isDengKuan"><bool>true</bool></test>
	<!-- 如果"视觉大小"是奇数 -->
	<test name="isOddPx"><bool>true</bool></test>
	<!-- 那么上调为偶像素,因为Monospace在奇像素下总是大一级显示 -->
	<edit name="pixelsize"><plus><name>pixelsize</name><int>1</int></plus></edit>
</match>
<!-- 第六步续,进一步专门处理等宽条件下"标称大小"为11px,12px的合成粗体 -->
<match target="font">
	<test name="isDengKuan"><bool>true</bool></test>
	<test name="embolden"><bool>true</bool></test>
	<test name="pixelsize" compare="more"><double>10.5</double></test>
	<test name="pixelsize" compare="less"><double>12.5</double></test>
	<!-- 统一调整为12px常规体,只有这样才能对齐 -->
	<edit name="pixelsize"><int>12</int></edit>
	<edit name="embolden"><bool>false</bool></edit>
	<edit name="weight"><int>80</int></edit>
</match>

上述配置中的最后一个段落,专门处理等宽条件下标称大小为11px,12px的合成粗体。因为11px,12px是个特例,在粗体条件下无法保持中西文对齐,只能调整为12px常规体,才能做到中西文对齐。

针对单个字体的调整

<!-- 第七步,针对每个字体单独调整渲染参数 -->
<match target="font">
	<test name="family"><string>Monospace</string></test>
	<edit name="hintstyle"><const>hintslight</const></edit>
</match>
<match target="font">
	<test name="family"><string>Sans</string></test>
	<edit name="hintstyle">
		<if>
			<or>
				<eq><name>pixelsize</name><int>10</int></eq>
				<eq><name>pixelsize</name><int>12</int></eq>
			</or>
			<const>hintslight</const>
			<const>hintfull</const>
		</if>
	</edit>
</match>
<match target="font">
	<test name="family"><string>Serif</string></test>
	<test name="pixelsize"><int>10</int></test>
	<test name="slant"><int>0</int></test>
	<edit name="hintstyle"><const>hintslight</const></edit>
</match>
<match target="font">
	<test name="family"><string>zhHei</string></test>
	<edit name="hintstyle"><const>hintslight</const></edit>
</match>
<match target="font">
	<test name="postscriptname" compare="contains"><string>NotoSansKR</string></test>
	<edit name="hintstyle"><const>hintslight</const></edit>
</match>

这里的调整只涉及到了微调风格。至于在不同大小、不同粗细、不同风格下,使用什么样的渲染参数效果最好,就只能靠自己的眼睛了。虽然可以使用FontForge查看字体的属性,获取相关信息,但是笔者依然推荐用自己的眼睛去检查更可靠。

删除多余的标记

<!-- 最后,删除等宽标记与奇偶标记 -->
<match target="font">
	<edit name="isDengKuan" mode="delete"></edit>
	<edit name="isOddPx" mode="delete"></edit>
</match>