每个开发者都应该了解的字体






4.50/5 (2投票s)
如果您要在表单中放置的不仅仅是一行文本,那么细节就开始变得重要起来。
引言
我最初以为使用字体会非常简单。如果您要在表单中放置的不仅仅是一行文本,那么细节就开始变得重要起来。
字体和字形
那么,什么是字体?从根本上说,字体是一系列字形。您所认为的像字母 A 这样的字符就是一个字形。字体就是该字体中所有字母的字形集合。如果使用 Helvetica 字体,那么它的所有字形看起来都是一种方式。如果使用 Times Roman 字体,那么它们看起来又是另一种方式。每种都是该字体中的字形集合。
现在我们需要引入代码页的概念。代码页是将字符编号映射到特定字形。程序最初将每个字符存储为一个字节。然后,对于亚洲字符集,就有了 DBCS 系统(有些字符是 1 字节,有些是 2 字节)。今天的程序大多使用 Unicode,但网页通常使用 UTF-8,这是一种最多可达 4 字节的多字节序列。
为什么提起编码?因为每种字体都有一个编码,其中字符编号 178 可能返回一个非常不同的字形,具体取决于字体使用的代码页。大多数字体文件使用 Unicode,因此您在那里有一个标准,但许多程序仍然使用特定的代码页,其中该代码页被映射到字体。当您显示 ABC,并且字体是 Wingdings 时,就会出现这种情况,您将得到 。所以第一点是,您需要确保您使用的编码与您使用的字体的编码匹配或被映射到该编码。
情况甚至更复杂。值为 E000 - F8FF 的字符是未定义的。每种字体都可以将这些字符设置为任何内容(一种用途是添加克林贡文字)。因此,此范围内的字符值本身就与其用于显示该字体的字体文件相关联。大多数符号字体就是这样工作的。
好的,所以您正在使用 Unicode,您的字体文件也使用 Unicode,您传递了一个字符串……但字符串显示为空白。怎么回事?好吧,字体文件没有义务为任何给定字符提供字形。符号字体没有 ABC。欧洲和美洲使用的大多数字体都没有中文、日文或韩文字形。使用字体不包含的字形不是错误,但它不会显示为空白,而是什么都没有(即宽度为 0)。
如果您想显示代码页中不存在的字形,并且您使用的是旧代码页之一,那么您也可能遇到类似的问题。在这种情况下,您需要映射到另一个代码页,至少对于该字符(Word 过去就是这样处理这种情况的)。
字体系列
字体分为几个不同的类别。首先是比例字体与等宽字体。在等宽字体中,所有字符的宽度完全相同。高度也是一致的,所有小写字母的高度相同,所有大写字母的高度也相同。尽可能避免使用等宽字体,因为它们更难阅读。亚洲字体几乎都是等宽的,因为汉字具有相同的宽度和高度,所以比例字体没有意义。另一方面,希伯来语和阿拉伯语几乎必须是比例字体。
接下来是字体类型,可以是衬线字体,其笔画末端带有装饰;无衬线字体,末端没有任何额外的装饰;装饰字体,远超正常;以及符号字体,可以包含任何随机内容,包括与映射到字形的字符代码的 ASCII 数字匹配的条形码。这仅仅是西欧字母。
字体度量
现在我们开始测量字体,而这里的字体,大部分(并非全部)是测量字形。用于字体的标准测量单位是磅 (point),虽然关于磅最初的含义有很多历史,但在计算机世界中,它是 72 磅 == 1 英寸。您有时也会看到 twip,它是 Point 的二十分之一,所以 1440 twips == 1 英寸。现在我们有了 EMU,其中 914400 EMUs == 1 英寸(更多内容请参阅此处)。如果您使用磅,则需要使用浮点变量。Twips 通常可以作为整数,EMUs 绝对可以。
然后是字体磅值。这是一个完全任意的数字。将其想象成旧的 CRT 显示器的对角线尺寸,实际尺寸接近您的预期,但从未达到该数字。磅值决定了渲染字形的大小,但它在页面上没有特定的测量值。
现在,事情开始变得有趣了,那就是字体度量。首先,一切都必须从基线开始测量。从字体的任何其他部分开始工作都会导致问题——您会遇到重大问题。所以从那里开始。基线上方绘制的最高部分是上升 (ascent),基线下方的最低部分是下降 (descent),两者都从基线测量。
然后是两行文本之间的间距。这是一个字体设置,因为字体设计者决定了该字体的适当间距。这可以以不同的方式返回,Windows 将其视为您放在下一行上方的间距,返回基线到基线的测量值,而 Java 则将其视为下一行之前的间距,并仅返回该值。行距 (Leading) 是您在类似单倍行距文本的行之间放置的间距。如果间距大于单倍行距,则会增加此值。
您通常需要获取这些字体的高度,而不是您显示的字符串中的字形串的高度。为什么?因为如果一行是“we were wrox”——没有升部或降部,那么这一行将比段落中的其他行更靠近,这看起来会很奇怪。您还需要查看所有字体和磅值,因为如果某些文本更大,您必须使用更大的上升/下降/行距值。但仅适用于具有较大文本的行,而不是整个段落。而且,所有这些都是从基线测量的,这是处理混合字体/大小的唯一方法。
好的,高度需要一些工作,但它相当直接,但宽度——这变得非常有趣。有趣的意思是,您必须将所有内容都做得恰到好处。从根本上说,除了固定宽度字体外,将每个字形的宽度相加不会等于这些字形一起渲染的总宽度。几乎永远不会。为什么?有几个原因。
- 字距调整 (Kerning) 是指字母根据相邻的字母进行放置。这就是为什么 AB 保持清晰,而 tt 则重叠很多。
- 拉丁字母中的某些字符组合会被合并,例如 ae 变成 æ,在德语中,ss 变成 ß。
- 希伯来语和阿拉伯语字形对于同一字符是不同的,具体取决于它在一个词的开头、中间还是结尾。尤其是在阿拉伯语中,放在末尾的字形往往比中间的字形更宽。因此,ﺺ 的宽度取决于它在字符串中的位置。
- 双向字体有额外的下述问题。
- 复杂的脚本,如印度语(印度)会在一个位置改变字形,从几个字符构建而成。所以一个三个字符的字符串可以有 1 到 3 个字形的宽度。
非常简单地说,您需要将一个完整、完全格式化的字符串馈送到您正在运行的平台的字体度量 API,以获取字符串的长度。这是一个昂贵的调用,因为字符串将被渲染到内存中以确定长度,但没有更准确的替代方法。并且您必须在测量时使用与渲染时完全相同的设置。任何时候这些不匹配,我们都发现差异大到肉眼可以辨别。测试代码中此问题的最佳方法是查看右对齐文本,因为您通常需要在渲染时获取字符串左端的基线位置,所以如果您计算错误长度——它就会显示出来。
双向文本
最后,我们遇到了双向文本(阿拉伯语和希伯来语)的问题。双向文本是从右到左的,除了数字,而拉丁语单词是从左到右的。所以它是从右到左读取,然后遇到数字或拉丁文序列时,您会跳到最左边的点,从左到右读取直到完成上一段希伯来语/阿拉伯语的位置,然后跳到拉丁文/数字部分的开头,再从右到左。
关于这些切换应该何时发生,已经进行了大量的研究。有些字符具有强方向性,有些字符具有弱方向性,有些字符没有方向偏好。您没有可能正确地实现这些规则。没有。但并非一切都完了。几乎所有平台,包括 Java 和 Windows,都有一个 API,您可以在其中按照读取顺序提供字符字符串,它会根据规则正确渲染它们。它们还有一个 API,可以告诉您每个字符的位置以及如果您想将光标向前或向后移动 1 个字符应该移动到哪个字符。
您可以将此 API 用于所有字体渲染和光标移动,而不管文本是什么,它都会正常工作——即使是复杂脚本。如果您不针对双向或复杂脚本,开始使用它会有点麻烦,但如果您最终需要,最好从一开始就使用它,这样您就不必重构代码。相信我,您真的、真的不想重构(我曾经不得不这样做——太痛苦了!)。
警告
不要将 Windows 字体复制到 Linux 或其他操作系统。字体度量往往不准确,文本看起来也会有问题。我不知道为什么,因为 TrueType 应该是可移植的,但在实践中,就像 Java 是“一次编写,到处调试”一样,字体往往是“设计一次,到处调整”。从已经为您的平台进行了优化的供应商那里获取字体。
资源
- 每个开发人员都应该了解的字体知识(附带屏幕截图)
- 维基百科 - 字体类型
- 诺曼·沃尔什的字体 FAQ
- 如果您需要一些 Lorem Ipsum