在数字化的计算机系统中,文字一般被拆分为两个部分进行处理。其一是文字彼此区分的能力,其二则是文字的具体写法和样式。第一个部分,通过数字编码处理,让不同的字符与不同的数字一一对应,这样人眼中的不同字符在计算机中被映射为不同的数字进行处理(ASCII, Unicode 等就是这样的映射方案),文本的数字化也就成了可能。但问题在于,经过这样的数字化之后,文字的图形属性完全丢失了,变成了纯粹抽象的东西。如果想把编码成数字的文本还原为文字图形,就需要额外的信息——也就是说要告诉计算机和显示设备(或者打印设备)每个字怎么写。包含这些额外信息的文件就是所谓的“字体文件”。
字体文件本质上就是一个图集,图集中的每张图都附带一些说明来指定它和哪个字符(或者说字符的编号)对应。当一段数字化的文本要显示或者打印时,计算机就根据文本中的字符编号去字体文件中寻找对应的图,然后依照图显示或者打印。
字体文件格式
对于日常能接触到的绝大多数计算机设备,对字体文件的使用和管理是其操作系统(或者说“图形化操作系统”)的基本功能。由于不同系统的具体实现和操作千差万别,这里无法一一介绍到。但是,一些系统为了开发和使用的稳定,以及在一定的范围内让字体文件具有可以移植性,规定了一些字体文件格式标准。这里简单介绍几种常见的字体文件格式。
OpenType
早期的字体格式是相对来说比较杂的,目前来说, OpenType 在一定程度上把杂乱的字体格式做了一层封装打包(尽管并不太成功)。在 OpenType 之前,主要有 Adobe 开发的 PostScript Type 1 和 Apple 与 Microsoft 开发的 TrueType 两种字体文件格式。后来 Microsoft 和 Adobe 又联合开发了 OpenType。
OpenType 虽然是一个标准但是包含两种格式风格,即 TrueType 风格的和 PostScript Type 1 风格的。虽然 OpenType 的这两种风格现在大体上能替代各自对应的老字体格式,但是两种风格本身并不能彼此并不具有互换性。也就是说 TrueType-flavored OpenType (一般和 TrueType 一样以 .ttf
为后缀,简称“TTF”)可以看作 TrueType 的升级, PostScript-flavored OpenType (一般用 .otf
为后缀,简称“OTF”)可以看作 PostScript Type 1 的升级,但是这同一框架下的两种风格格式却不能彼此替换。有的程序对 OTF 支持更好,有的则仅仅支持 TTF。关于这几种字体格式之间的关系,可以参考以下资料:
- https://zhuanlan.zhihu.com/p/386035885
- https://www.reddit.com/r/typography/comments/ci4nwk/comment/ev1jy42/
- https://learn.microsoft.com/en-us/typography/opentype/
不过对于一般用户来说,直接选 TTF 字体是最保险的选择,因为其支持比较全面。只是一些需要用到 Adobe 创意套件的用户可能需要注意 OTF 的问题。
另外,TTF 有合集形式的文件格式,后缀为
.ttc
, OTF 也对应有合集,后缀为.otc
。所谓合集其实就是多个字体文件打包成了一个字体文件。
OpenType 中的 TTF 和早期的 TrueType 缩写一样,都是“TTF”,而且文件后缀名也一样都是
.ttf
。但在实际使用中,很少需要区分两者。可以简单把 OpenType 中的 TTF 当作是一个高版本的加了更多功能的 TrueType。
Web Open Font Format
简称“WOFF”,目前有两个版本—— WOFF 1.0 (有时候直接叫“WOFF”)和 WOFF 2.0。 WOFF 2.0 是 WOFF 1.0 的升级,所以没有特殊理由一般选最新的 WOFF 2.0 就行。需要注意的是,这个文件格式两个版本的后缀不同, WOFF 1.0 的后缀是 .woff
, WOFF 2.0 的后缀是 .woff2
。
相比 OpenType 和更老的字体格式来说, WOFF 主要增加了压缩功能,使得字体文件可以更小,因而在网页和浏览器领域应用广泛。但是目前来说,一些其他程序和比较旧的浏览器可能不支持。
字体风格
所谓“字体风格”是一个很模糊的说法,因为字体的特征可以分很多个维度讨论。由于本文仅仅是一个关于字体知识的简单介绍,所以在这个模糊的标题之下笼统做一点简单的介绍。
衬线和无衬线
一般来说,常见的印刷体(或者屏幕显示字体)根据是否加入装饰衬线分为衬线体(serif)和无衬线体(sans-serif,简称“sans”)。衬线字体一般在笔画起止处有装饰线,且笔画粗细变化较多;无衬线体则一般没有装饰线,线条简单,粗细均匀。在平面设计上对两者的使用有很多研究和讲究,这里就不具体展开了。对应到常见的汉字字体来说,明宋体(就是一般说的“宋体”)属于衬线字体,而黑体则是无衬线字体。
这种分类不是简单二分的,在这两种之外还有更像手写体的 script font 和其他各种奇奇怪怪的艺术字。衬线和无衬线之间也不是非此即彼的,有一些字体的设计介于两者之间,所以也不好直接归为哪一类。例如在代码显示之类的场景中,为了增加 “0Oo” 或者 “1lI” 之类易混淆字符的辨识度,即便是无衬线字体也会加一些衬线字体的设计以方便字符区分。
显示代码用的字体一般还有一个特殊要求,就是不同字符宽度要统一(满足这类要求的字体也叫等宽字体, monospaced font)。这对汉字来说很自然,但是对于拉丁字母来说并不自然。例如“i”的宽度就是要比“m”小很多。程序员要求代码中不同字母宽度一样一方面是早期终端机习惯的遗留,另一方面也出于实用考虑:一者,不同的 ASCII 字符,不论怎么写,其所占用的存储空间是一样的,因此完全相同的宽度有助于直观估计字符串或者文本的存储空间;二者,严格对齐的行列有助于矩阵框选等比较复杂的文本操作。
当代码中混有多种语言的字符时,强行把拉丁字符拉到和汉字一样宽不好看,所以此时又会把字符分为全角和半角两类(还有零宽度字符,但是不常见)。拉丁字符是半角字符,汉字是全角字符,两种字符各自等宽,全角宽度和半角宽度之间则维持一个固定的比例(一般是 2:1)。
粗体、斜体、粗斜体和可变字体
在衬线和无衬线这样的大分类下,就是一款款具体的字体。由于文字在经过数字化编码的时候并不保留写法或图像信息,所以对计算机来说,字体做成什么样都是可以的。但对于人来说,字体还是得符合一定的规范,看起来才舒心。例如说,通常的正文字体应当笔画粗细适中,方正竖直。而整体倾斜的斜体字体,笔画统一加粗的粗体字体或者叠加了加粗和倾斜效果的粗斜体等特殊字体则穿插其中用作突出强调或表示其他附加信息。
但就整体的排版风格而言,斜体,粗体,粗斜体等特殊字体又应当和正文字体有一定的统一性。所以,很多字体制作者会制作配套使用的多款字体打包在一起称为一个“font family”。例如西文字体名“Arial”其实就是一个 font family name,涵盖了普通正文用的字体和斜体、粗体等配套使用的变种字体。
当然也有时候字体作者没有把同一个字体微调很多遍的耐心,就会只给出 font family 的一两个变种,剩下的情况由基础字体经过程序自动调整变形得到。说到这里,稍有编程思维的人可能都很容易想到,这类机制其实可以推而广之变成一种参数化设计——字体设计师在设计字体的时候不直接给定具体的尺寸值,而是给出尺寸之间的依赖关系,这样在使用时用户就可以自行调整字体的笔画粗细、倾斜程度、字符间距等值。这就叫做可变字体(variable font)。这是一个非常好的设想,但这种功能需要字体渲染引擎等程序的支持,而比较老旧的系统或者引擎是不支持这种功能的。另外就是字体文件本身得是按可变字体的格式制作的,但此前的旧字体文件自然都不是按这种标准制作的。所以可变字体目前还在普及的过程中。
连字
在手写或者排印中一个字符的写法不一定是一成不变的,有可能根据前后文字有所调整。例如手写体中的连笔牵丝和英文排印中的“fi”合印字符就属于这种现象,英文叫做“ligature”,汉语可翻译为“连字”。
在手写时,无论连字还是合写字都能轻松地灵活处理。但是对于印刷来说就需要特殊处理,在活字印刷时代,可以把需要连写或者压缩空间合写的若干个字符的字形调整后合刻在一个铅字上解决。但如果在字符编码时也使用这种方式,穷举并编号所有可能的合字或连笔字形显然会浪费大量的编码空间。因此 Unicode 的大致处理方式是已经收录的连字或合字不做改动,后续更新的时候尽量不收录连写或者合字造成的新字形(当然存在一些例外,例如合字本身已经逐渐固定成一个专门符号)。而连字效果交给字符渲染程序去处理,让渲染程序根据上下文调整字符字形。
但是和可变字体类似,早期的字体引擎和字体文件格式都是不支持这种特性的。因此,虽然现在有了一些支持连字显示的软件和一些带连字的字体,但总体上连字功能还在普及的过程中。
连字不仅仅能用于模仿手写的字体中。一些编程字体也利用这一特点美化代码显示。例如 ASCII 中没有大于等于号,所以一般用
>=
表示。而如果使用支持连字的字体,就可以把>=
自动替换显示成“≥”(但是编码仍旧是>=
两个字符的编码,只是显示效果改变)。
阿拉伯文字母的连写变形和各种类似的手写字体变形以及连用多个横线符号的连接等其实都应该用连字来实现。但是由于这个功能出现得很迟,所以有的字符连接就一直将错就错现在反而成了一种排版风格。例如中文破折号其实应该是一条连起来的长线,但是很多字体和渲染程序中是断开的两段横线,久而久之很多人反而习惯了这种显示方式。有的则用了其他的处理方式,例如阿拉伯文的字母变形。阿拉伯文字母的原型和连写变形被分别在 Unicode 的不同号段编号,在保存文字时一般仅保存原型的组合,但显示的时候由渲染程序自动映射成变形形式根据变形字体渲染显示。阿拉伯文的这套变形处理机制不依赖于现在的连字功能,所以不支持连字的浏览器和字体也可能能正确显示阿拉伯文。
替换字形
有时候同一个字符会有多种不同字形,而且不同人在不同场景下会对这些字形有不同的取舍。比较旧的字体通过打包不同版本的字体文件来实现个别字体的替换,这是一种相对低效且复杂的办法。现在的一些字体会直接把所提供的各种字形变体和普通字形一起打包在同一个字符文件中,在调用时使用字形参数进行替换(需要应用程序支持这一功能)。
这种通过字形参数选择替换字形的方法参考: https://github.com/tonsky/FiraCode/wiki/How-to-enable-stylistic-sets。
Unicode 覆盖
Unicode 为几乎所有已知的人类文字符号分配了编码,但它本身并不提供字体。而字体的制作者往往是针对某一个具体的语言环境去制作字体,因此只会为 Unicode 中的一部分字符制作字形。目前据我所知还没有完全覆盖 Unicode 全部定义编码的字体。因此,在后续介绍字体时,我们会按照字体的覆盖范围进行大致的分类。
PUA 覆盖
这里的 PUA 不是“pick-up artist”的意思,而是指“private use areas”,也就是 Unicode 中预留出来让用户自己或其他机构约定用法的区域。由于这个区域的用法不是统一约定的,所以不同使用者的使用方式可能是不同的乃至有冲突的。不过对于一个具体的系统内部来说,只要内部的约定统一,是不存在冲突的。例如程序员们约定了几个 PUA 码位用于绘制虚拟终端命令行的特殊符号,而一些语言文字爱好者则用 PUA 来表示一些尚未被 Unicode 收录的非自然语言文字符号。这两套系统的规则是不同的,但是它们各自内部是没有冲突,可以正常使用的。
由于 PUA 区域的用法并不是 Unicode 指定,所以覆盖这一区域的字体并不能简单互换,而要看它们支持的是哪些 PUA 协议。在后续的介绍中, PUA 协议和支持这些协议的字体也会依照大致的使用范围分类介绍。
本文源码采用 MIT 协议开放,托管于: https://github.com/ZhiZe-ZG/ZZToolLibrary
如果觉得本文内容对您有用,希望您能在能力和意愿范围内给我一些资助。我不以此为生,但我也是个普通人。