跨多个平台的文本显示实现
Text Display Implementation Across Multiple Platforms
几周来我一直在网上搜索,试图弄清楚究竟文本(例如您正在阅读的内容)如何显示在屏幕上。
结果少得惊人。
我遇到过光栅化、位图、矢量图形等概念。我不明白的是,底层实现如何在所有系统中如此统一地工作(windows、linux, 等等)以我们人类理解的方式。某处是否定义了规范?实现代码是否开源并可供一般人查看public?
我目前的理解是:
- 使用外部绘图程序创建字体,一次一个字符
- 将这些字符添加到特定语言库可以理解的字体文件中
- 然后 GPU 根据需要从文件中读取这些字符,并按照育儿代码定义的线性方式显示在屏幕上。
此外,如果字符是在字体文件中定义的,例如 'F, C, ..., Z',矢量图形(依赖于一组坐标点)是如何支持的?如果没有坐标点,光栅化似乎是改变尺寸的唯一选择。
我的 assumptions/research 就到此为止了。
如果您对这个话题很熟悉,并且可以提供一个可能对我自己和其他读者有用的详细答案,请酌情回答。我发现有多少我们认为理所当然的代码在幕后非常复杂。
以下提供了一个概述(省略了很多血淋淋的细节):
在 Internet 上显示文本的两个关键组成部分是 (i) 字符编码和 (ii) 字体。
字符编码是一种将字符(例如拉丁文大写字母“A”)分配为字节序列表示形式的方案。过去已经设计了许多不同的字符编码方案。今天,互联网上几乎无处不在的是Unicode。 Unicode 将每个字符分配给一个 code point,这是一个整数值;例如,Unicode 将拉丁文大写字母 A 分配给代码点 65,或十六进制的 41。按照惯例,Unicode 代码点是指使用四到六个十六进制数字和“U+”作为前缀。因此,拉丁文大写字母 A 被分配给 U+0041。
字体提供用于显示文本的图形数据。多年来已经创建了各种字体格式。今天,Internet 上普遍使用的是遵循 OpenType spec(这是 1991 年左右创建的 TrueType 字体格式的扩展)的字体。
您在屏幕上看到的是字形。 OpenType 字体包含字形数据,以及将 Unicode 代码点映射到相应字形的 table。更准确地说,字符到字形的映射(或 'cmap')table 将 Unicode 代码点映射到 字形 ID。代码点由 Unicode 定义;字形 ID 是字体内部实现细节,用于在其他 table 中查找字形描述和相关数据。
OpenType 字体中的字形可以定义为位图,或(更常见)定义为矢量轮廓(贝塞尔曲线)。字形描述有一个假定的坐标网格。然后,矢量轮廓被定义为贝塞尔曲线控制点的有序坐标对列表。显示文本时,矢量轮廓会根据请求的文本大小(例如 10 点)和显示器上的像素大小缩放到显示网格上。 光栅化器 读取字体中的控制点数据,根据显示网格的需要进行缩放,并生成位图并将其放置在屏幕上的适当位置。
关于显示光栅化位图的一个额外细节:大多数操作系统或应用程序将使用某种过滤来为字形提供更平滑和更清晰的外观。例如,灰度抗锯齿过滤器会将字形边缘的显示像素设置为灰度级别,而不是纯黑色或纯白色,以便在缩放轮廓未与物理像素边界完全对齐时使边缘看起来更平滑-大部分时间都是这样。
我提到了“在适当的位置”。字体具有整个字体和每个字形的度量(定位)信息。
全字体指标将包括建议的文本行行距,以及每行中 基线 的位置。这些指标以字体字形设计网格的单位表示;基线对应于网格内的 y=0。要开始一行,(0,0) 设计网格位置对齐到基线与页面布局中文本容器边缘相交的位置,并定位第一个字形。
字体也有字形规格。字形指标之一是每个给定字形的提前宽度。因此,当应用程序正在绘制一行文本时,它在该行的开头有一个起始“笔位置”,如上所述。然后它将第一个字形相应地放置在该行上,并将笔位置按第一个字形的前进宽度的量前进。然后它使用新的笔位置放置第二个字形,并再次前进。依此类推,因为字形沿线放置。
文本行的布局(自然)更复杂。我上面描述的内容足以在基本文本编辑器中显示英文文本。更一般地说,一行文本的显示可能涉及用某些替代字形替换默认字形;这是必需的,例如,当显示阿拉伯文本时,字符看起来是草书连接的。 OpenType 字体包含一个“字形替换”(或 'GSUB')table,它提供了字形替换操作的详细信息。此外,可以根据各种原因调整字形的位置;例如,将变音符号正确定位在字母上。 OpenType 字体包含一个提供位置调整数据的“字形定位”('GPOS') table。今天的操作系统平台和浏览器支持所有这些功能,因此可以使用 OpenType 字体显示许多不同语言的 Unicode 编码文本。
关于字形缩放的附录:
在字体内,每个 em 设置了一定数量的单元格。这是由字体设计者设置的。例如,设计者可能指定 1000 个单位/em,或 2048 个单位/em。字体中的字形和所有度量值——字形前进宽度、默认行距等——都在字体设计网格单元中设置。
em 与作者设置的内容有什么关系?在文字处理应用程序中,您通常以磅为单位设置文本大小。在印刷领域,点是定义明确的长度单位,大约但不完全是 1/72 英寸。在数字排版中,点被定义为精确的 1/72 英寸。现在,在文字处理器中,当您将文本大小设置为 12 磅时,这实际上意味着 12 磅/em.
因此,例如,假设一种字体的设计使用每个 em 1000 个设计单位。并假设一个特定的字形正好是 1 em 宽(例如,一个 em 破折号);就设计网格单元而言,它将正好是 1000 个单元宽。现在,假设文本大小设置为 36 磅。这意味着每个 em 有 36 点,36 点 = 1/2",因此字形将精确打印 1/2" 宽。
当文本被栅格化时,它是针对具有特定像素密度的特定目标设备完成的。桌面显示器的像素(或点)密度可能为 96 dpi;打印机的像素密度可能为 1200 dpi。这些是相对于英寸的,但是从英寸你可以得到点,对于给定的文本大小,你可以得到 em。根据设备和文本大小,您最终得到每个 em 一定数量的像素。因此,光栅化器采用每个 em 的字体设计单位定义的字形轮廓,并根据给定的每个 em 像素数将其放大或缩小。
例如,假设字体设计为每 em 1000 个单位,打印机为 1000 dpi。如果文本设置为 72 磅,即每 em 1",字体设计单位将与打印机点完全匹配。如果文本设置为 12 磅,则光栅化器将按比例缩小,以便每个有 6 个字体设计单位打印机点。
此时,字形轮廓中的细节可能不会与设备网格中的整个单元对齐。光栅器需要决定哪些 pixels/dots 获得墨水,哪些不获得墨水。字体可以包含影响光栅器行为的“提示”。提示可能会确保某些字体细节保持对齐,或者提示可能是根据当前像素/em 将贝塞尔曲线控制点移动一定量的指令。
有关详细信息,请参阅 Apple 的 TrueType 参考手册中的Digitizing Letterform Designs and Font Engine,其中有很多详细信息。
几周来我一直在网上搜索,试图弄清楚究竟文本(例如您正在阅读的内容)如何显示在屏幕上。
结果少得惊人。
我遇到过光栅化、位图、矢量图形等概念。我不明白的是,底层实现如何在所有系统中如此统一地工作(windows、linux, 等等)以我们人类理解的方式。某处是否定义了规范?实现代码是否开源并可供一般人查看public?
我目前的理解是:
- 使用外部绘图程序创建字体,一次一个字符
- 将这些字符添加到特定语言库可以理解的字体文件中
- 然后 GPU 根据需要从文件中读取这些字符,并按照育儿代码定义的线性方式显示在屏幕上。
此外,如果字符是在字体文件中定义的,例如 'F, C, ..., Z',矢量图形(依赖于一组坐标点)是如何支持的?如果没有坐标点,光栅化似乎是改变尺寸的唯一选择。
我的 assumptions/research 就到此为止了。
如果您对这个话题很熟悉,并且可以提供一个可能对我自己和其他读者有用的详细答案,请酌情回答。我发现有多少我们认为理所当然的代码在幕后非常复杂。
以下提供了一个概述(省略了很多血淋淋的细节):
在 Internet 上显示文本的两个关键组成部分是 (i) 字符编码和 (ii) 字体。
字符编码是一种将字符(例如拉丁文大写字母“A”)分配为字节序列表示形式的方案。过去已经设计了许多不同的字符编码方案。今天,互联网上几乎无处不在的是Unicode。 Unicode 将每个字符分配给一个 code point,这是一个整数值;例如,Unicode 将拉丁文大写字母 A 分配给代码点 65,或十六进制的 41。按照惯例,Unicode 代码点是指使用四到六个十六进制数字和“U+”作为前缀。因此,拉丁文大写字母 A 被分配给 U+0041。
字体提供用于显示文本的图形数据。多年来已经创建了各种字体格式。今天,Internet 上普遍使用的是遵循 OpenType spec(这是 1991 年左右创建的 TrueType 字体格式的扩展)的字体。
您在屏幕上看到的是字形。 OpenType 字体包含字形数据,以及将 Unicode 代码点映射到相应字形的 table。更准确地说,字符到字形的映射(或 'cmap')table 将 Unicode 代码点映射到 字形 ID。代码点由 Unicode 定义;字形 ID 是字体内部实现细节,用于在其他 table 中查找字形描述和相关数据。
OpenType 字体中的字形可以定义为位图,或(更常见)定义为矢量轮廓(贝塞尔曲线)。字形描述有一个假定的坐标网格。然后,矢量轮廓被定义为贝塞尔曲线控制点的有序坐标对列表。显示文本时,矢量轮廓会根据请求的文本大小(例如 10 点)和显示器上的像素大小缩放到显示网格上。 光栅化器 读取字体中的控制点数据,根据显示网格的需要进行缩放,并生成位图并将其放置在屏幕上的适当位置。
关于显示光栅化位图的一个额外细节:大多数操作系统或应用程序将使用某种过滤来为字形提供更平滑和更清晰的外观。例如,灰度抗锯齿过滤器会将字形边缘的显示像素设置为灰度级别,而不是纯黑色或纯白色,以便在缩放轮廓未与物理像素边界完全对齐时使边缘看起来更平滑-大部分时间都是这样。
我提到了“在适当的位置”。字体具有整个字体和每个字形的度量(定位)信息。
全字体指标将包括建议的文本行行距,以及每行中 基线 的位置。这些指标以字体字形设计网格的单位表示;基线对应于网格内的 y=0。要开始一行,(0,0) 设计网格位置对齐到基线与页面布局中文本容器边缘相交的位置,并定位第一个字形。
字体也有字形规格。字形指标之一是每个给定字形的提前宽度。因此,当应用程序正在绘制一行文本时,它在该行的开头有一个起始“笔位置”,如上所述。然后它将第一个字形相应地放置在该行上,并将笔位置按第一个字形的前进宽度的量前进。然后它使用新的笔位置放置第二个字形,并再次前进。依此类推,因为字形沿线放置。
文本行的布局(自然)更复杂。我上面描述的内容足以在基本文本编辑器中显示英文文本。更一般地说,一行文本的显示可能涉及用某些替代字形替换默认字形;这是必需的,例如,当显示阿拉伯文本时,字符看起来是草书连接的。 OpenType 字体包含一个“字形替换”(或 'GSUB')table,它提供了字形替换操作的详细信息。此外,可以根据各种原因调整字形的位置;例如,将变音符号正确定位在字母上。 OpenType 字体包含一个提供位置调整数据的“字形定位”('GPOS') table。今天的操作系统平台和浏览器支持所有这些功能,因此可以使用 OpenType 字体显示许多不同语言的 Unicode 编码文本。
关于字形缩放的附录:
在字体内,每个 em 设置了一定数量的单元格。这是由字体设计者设置的。例如,设计者可能指定 1000 个单位/em,或 2048 个单位/em。字体中的字形和所有度量值——字形前进宽度、默认行距等——都在字体设计网格单元中设置。
em 与作者设置的内容有什么关系?在文字处理应用程序中,您通常以磅为单位设置文本大小。在印刷领域,点是定义明确的长度单位,大约但不完全是 1/72 英寸。在数字排版中,点被定义为精确的 1/72 英寸。现在,在文字处理器中,当您将文本大小设置为 12 磅时,这实际上意味着 12 磅/em.
因此,例如,假设一种字体的设计使用每个 em 1000 个设计单位。并假设一个特定的字形正好是 1 em 宽(例如,一个 em 破折号);就设计网格单元而言,它将正好是 1000 个单元宽。现在,假设文本大小设置为 36 磅。这意味着每个 em 有 36 点,36 点 = 1/2",因此字形将精确打印 1/2" 宽。
当文本被栅格化时,它是针对具有特定像素密度的特定目标设备完成的。桌面显示器的像素(或点)密度可能为 96 dpi;打印机的像素密度可能为 1200 dpi。这些是相对于英寸的,但是从英寸你可以得到点,对于给定的文本大小,你可以得到 em。根据设备和文本大小,您最终得到每个 em 一定数量的像素。因此,光栅化器采用每个 em 的字体设计单位定义的字形轮廓,并根据给定的每个 em 像素数将其放大或缩小。
例如,假设字体设计为每 em 1000 个单位,打印机为 1000 dpi。如果文本设置为 72 磅,即每 em 1",字体设计单位将与打印机点完全匹配。如果文本设置为 12 磅,则光栅化器将按比例缩小,以便每个有 6 个字体设计单位打印机点。
此时,字形轮廓中的细节可能不会与设备网格中的整个单元对齐。光栅器需要决定哪些 pixels/dots 获得墨水,哪些不获得墨水。字体可以包含影响光栅器行为的“提示”。提示可能会确保某些字体细节保持对齐,或者提示可能是根据当前像素/em 将贝塞尔曲线控制点移动一定量的指令。
有关详细信息,请参阅 Apple 的 TrueType 参考手册中的Digitizing Letterform Designs and Font Engine,其中有很多详细信息。