字体的秘密

2021-12-17 13:50邱明冉
中国信息技术教育 2021年17期
关键词:微调字符字体

邱明冉

编者按:每个操作系统在设计之初,都会考虑如何在终端上显示文字,而且都不约而同地采用了字体这一方式来显示丰富的文字形态。字体对于操作系统的重要性不言而喻,从本期开始,我们将探讨系统中字体的显示和渲染原理,以及对字体的优化和应用。

一位日常使用Linux系统的同学说:“Mac的字体显示对高分屏有优化,Linux的字体显示对所有屏幕都有优化。”作为一个需要整天面对屏幕上密密麻麻文字的学生,听到这句话,笔者心动了。但是笔者装好双系统之后,并没有感受到Linux的字体显示比Windows 10的显示有进步。为了刨根究底,我们需要从计算机是如何显示文字的开始探索……

● 像素——显示的基本单元

在讨论计算机如何显示字体之前,先介绍一下VGA是如何进行显示的。

以640*480@60Hz的VGA显示规格为例,电子枪不断地重复着从左上角扫描到右下角,再回到左上角的过程,为了能够显示640*480像素的有效区域,电子枪的扫描范围相当于800*525像素,多出来的“无效扫描”就是在电子枪扫描无法显示的边框处和返回行首列过程中发生的(如图1)。

于是,为了让连接VGA的显示器能够以640*480@60Hz工作,必须设计一个电路,使得开发板能够在正确的时候(当电子枪扫描到800*525中有效的640*480时)通过VGA传递当前要打印的像素的颜色。这个电路还可以检测当前扫描到的像素坐标。

VGA显示器接收到正确的垂直同步信号和RGB值才能正常工作。既然可以控制每一个像素的颜色,那么也就可以在屏幕上显示文字了(如图2)。

● 从像素到文字

在计算机科学中,抽象是一个重要的理念。如果每次显示文字的时候都要在像素点层次工作,那就太烦琐了。于是计算机科学家们想到,将整个640*480像素的屏幕划分成一个个8像素宽、16像素高的切片,每个切片显示一个ASCII字符。预先定义好每个ASCII字符在这8*16像素内如何显示,要显示哪个字符的时候,就加载定义好的显示方式来显示即可。

这种抽象出来的方法可以应用到多个方面,在游戏场景制作时也可以这样分解。Unity引擎将游戏画面分解成一块块“地砖”,设计师只需要预先设计几种地砖,通过铺地砖就可以快速完成游戏场景设计(如图3)。

用现在的编程思维来讲,就是预先定义好一个一维数组character,每个字符就是数组的一个元素。数组元素的大小为8*16=128位,每一位表示这8*16个像素中某一个像素是黑还是白,那么“A”的显示方式和存储方式就如图4所示。

在电路的具体实现上,开发板将这些信息保存到ROM(只读内存)中,供VGA使用。这种字体,称为点阵字体(Bitmap Font,直译为“位图字体”)。在后来的DOS系统中,显示模式分为图形模式和文本模式,图形模式下通过直接写显存可以对每一个像素的颜色进行控制,而文本模式下通过写显存只能控制哪一个位置输出哪个字符以及字符的颜色。

由于计算机主板上的ROM容量较小又比较宝贵,一般只存储最基本的128个ASCII码字符,在这些ASCII字符之外的文字和符号(如汉字和特殊符号)要想显示出来,就必须采用其他方法,如将点阵信息储存在硬件插卡或磁盘文件中,形成字库。

在中文DOS系统中,不仅要解决字库的加载,还要解决如何在屏幕上显示这些文字。经历过汉卡之后,曾经的UCDOS、CCDOS等优秀的汉字系统,最终采用“直接写屏”(调用BIOS的10H中断在图形模式下直接操作显存地址)技术以图形方式在屏幕上“画”出汉字。在纯粹的西文DOS环境中,要想显示出汉字是一个不小的挑战。字库文件最后成了一致看好的最终解决方案。

点阵字体大小固定,但是支持整数倍放大。然而这样放大后就像位图一样,会有明显的锯齿。所以,为了满足不同大小的需要,一种字体需要提供好几个版本。图5是8*16的字体放大成16*32的大小(左),与本来就是16*32大小字体的比较(右),可以看出,用放大方法得到的字体,有明显的锯齿,并且最初的不对称也被放大。

即使是同一种字体,在不同大小下看起来也像是不同字体一样。例如,当字体较大时,衬线体可以显示出自己的衬线,而字体较小时,因为像素点数量的限制,这种细节只能被舍弃。我们在命令提示符下查看同一点阵字体不同大小时的差别。8*16大小的Terminal字体,可以看到明显的衬线以及字体横细竖粗的特点(如上页图6)。

而6*12大小的Terminal字体,和8*16长宽比相等,但是没有衬脚,笔画粗细均匀,看上去和前者完全不是一种字体(如图7)。

8*18大小的Terminal字体也跟8*16大小的Terminal字体完全不同(如图8)。

● 从文字到像素

随着图形界面的普及,用户对一种字体提供多种字号的需求不断增长。点阵字体的设计效率不如人意,尤其是对非字母语言的文字而言。于是,设计师想到用数学方程来描述笔画,把字符分隔成若干关键点,用光滑的曲线连接,通过计算就可以得出同一种字体在各种不同的字号下应该如何显示。这就是矢量字体(Vector Font,也称轮廓字体)。

Photoshop的钢笔工具可以绘制贝塞尔曲线,这种曲线依赖于数学方程,不受画布像素的限制,显得非常平滑(如图9)。

但是当使用它在画布上绘图并填充时,就会发现绘制区域的图形依然受到画布像素的制约,每一个像素只能有一种颜色,平滑的边缘会变成锯齿状(如图10)。同理,通过数学函数设计的矢量字体,当最终呈现在显示器上的时候,也会受到显示器像素的制约,在字号较小的时候,显示的效果可能会很差。

例如,字母“e”的矢量設计图(如图11左侧),它如何在显示器上显示呢?显示器上的每个像素就是一个小格子,我们可以用初等数学中常用的二值化进行处理:如果一个像素一半以上面积被字体覆盖,那么这个像素就用来显示字体,否则这个像素不显示字体。图11右侧就是转换过程。

当字体较小时,为了避免像素太少引起矢量字体的严重失真,Windows的做法是对小号字体直接使用原始的点阵字体,不进行矢量渲染。不过微软对Windows 10系统默认的微软雅黑字体的处理是例外,全字号使用ClearType亚像素渲染技术,所以有人觉得雅黑字体显示效果较好。

● 抗锯齿技术

上面所说的非黑即白填充法只能渲染出粗糙的字体效果。在Windows XP之前的Windows系统,微软就采用这样的做法来显示矢量字体。要改进显示效果,可以根据理想的字型在此像素所覆盖的面积比例,赋予像素不同的灰度,靠近字体边缘的地方表现为深灰到浅灰不等的颜色(如上页图11),人眼会将它转换为字体的轮廓。这种通过过渡色减轻字体边缘锯齿感的技术,就是抗锯齿技术(如上页图12)。

字体抗锯齿在日常使用中效果如何呢?这里以图形软件开发常用的Qt框架为例来演示。这是窗口中的一个按钮,按钮文字字号是50,图13和图14展示了抗锯齿的效果。

或者我们也可以用14号字,截图后放大观察区别(如图15、图16)。从图15中可以看到,它使用了红、蓝、棕等颜色而不是灰色作过渡色(详见后文“亚像素”部分)。

抗锯齿在字体的日常使用感受中影响还是很大的,尤其是在字号较小的情况下。开启了抗锯齿之后,不但字形得到了更好的还原,显示效果在大部分情况下也有明显提高。目前的系统也都默认开启了字体抗锯齿(如图17)。

● 字体微调

有时候在小字号下,开启了抗锯齿,字体糊成一团,不易辨别,显示效果反而变差了。为了解决抗锯齿导致的字体边缘模糊问题,人们想到通过轻微调整字形,使其尽可能与像素点贴合,以避免大量使用过渡像素导致观感模糊,从而生成清晰易读的文本。这就是字体微调(hinting,直译为“提示”)。

图18中维基百科的图片很好地诠释了“字体微调”。上下两行中上一行没有字体微调,下一行有字体微调。

微软十分重视字体微调,认为显示字体的时候应该针对显示屏做一定的优化,使之适应较低精度的像素分布,获得清晰效果,方便用户阅读。而苹果认为,在显示屏上所显示的字体应该和印刷出来的成品效果接近,即使这样会导致字体看上去有些模糊。二者字体渲染理念的不同也就产生了不同的效果(如图19)。

字体微调使文字清晰易读的同时,一定程度上扭曲了字体,甚至可能导致比较复杂的文字会显示错误的字形。把Windows 10桌面(屏幕缩放100%)下默认大小的字体放大来看,可以看到为了使笔画对比度提升、显示更清晰,字体微调甚至将“真”字里面的三横减去了一横(如图20)。

在关闭字体微调效果的Linux XFCE桌面上同样的文字,笔画没丢,但显示效果比较模糊,读起来眼睛很累(如图21)。

上面两图的比较是在不同系统不同字体上进行的,没有控制变量,不够严谨。我们来看看Linux XFCE桌面下Noto Serif Regular字體(大小为10号)开启微调的效果(如图22、图23),可以看到大写的E、H、R等字母明显更锐利了,小写g下面的圈从3*2的长方形变成了3*3的正方形,这也是字体微调之后与原字形产生的差别。

除了对字形进行微调,有时还会对字距进行微调,以达到更自然的显示效果(如图24)。

● 亚像素渲染

在液晶显示屏上,一个像素是由红、绿、蓝三个长条形亚像素构成的,LCD可以单独控制每一个子像素,这个特性正好可以用来提高字体精度。白色字体只有离得非常近才能观察到白色是由RGB三种颜色混合而成的。

亚像素渲染正是利用了LCD显示屏像素的特点,将字体水平方向的精细度提高到原先的三倍(如图25),在字号较小的情况下尤其有效。

图26形象展示了亚像素渲染是如何提高水平精度的,图左侧把每个像素分解成RGB三个亚像素,实际上肉眼只能看出图右侧的效果。

采用亚像素渲染后,如果仅仅是在计算机屏幕上放大(如使用XFCE桌面的ALT+滚轮进行全局缩放,或者Snipaste、QQ截图等截图工具自带的放大镜),看到的只是字体轮廓花花绿绿的整块整块大小的像素,而且一定是“白底黑字左橙右青,黑底白字左青右橙”。只有用微距相机对准屏幕拍摄,然后放大查看照片,才能发现字体轮廓附近像素的RGB亚像素亮暗不同(如图27)。

亚像素渲染加入了颜色信息,图28左侧轮廓边缘亚像素分别是R(亮)G(一般)B(暗),所以左侧像素整体表现出的颜色是大部分红+小部分绿=偏红的黄色。

除了水平RGB亚像素渲染,Linux还支持其他种类的亚像素渲染(当然也可以关闭亚像素渲染),以适应各种不同的显示设备。字体渲染的高度可定制性,就是同学口中“Linux的字体显示对所有屏幕都有优化”的含义吧。而笔者使用的屏幕完全符合微软“不甚高端”的预期,已经在Windows 10字体渲染策略下取得了较好的显示效果,所以才没有感受到Linux在字体渲染上的优势……

猜你喜欢
微调字符字体
Python实现图片转字符画
乐海乐器“微调轴”研发成功
正则表达式快速入门
图片轻松变身ASCⅡ艺术画
枯燥的Times New Roman字体有了新花样
我国的民族优惠政策应往何处去?
组合字体
视频监视系统中字符叠加技术的应用
字体安装步步通