浏览器环境下,网页从加载到其中文本渲染,可分为 HTML 解析->字体匹配->字体渲染3个部分。

一、HTML 解析

HTML 解析分为字节流解码(Byte Stream Decoder)、输入流预处理(Input Stream Preprocessor)、标记分析(Tokenizer)、DOM 树构建(Tree Constructon),详细过程见 W3C HTML 解析WHATWG HTML 解析

字节流解码

因历史原因,不同区域和语言的页面可能使用不同的编码方式,导致浏览器在解析前需先确定编码,W3C 推荐的编码是 UTF-8。

现代浏览器是依据 W3C 规范的一套编码嗅探算法(Encoding Sniffing Algorithm),以确定首次解码文档时要使用的字符编码。当 HTML 解析器解码输入字节流时,它会使用字符编码和置信度。置信度分为暂定、 确定和无关紧要三种。解析过程中会根据所使用的编码以及该编码的置信度(暂定或确定)来决定是否需要更改编码。如果不需要编码,则置信度无关紧要。

输入流预处理

即规范化换行。

标记分析和 DOM 树构建

标记分析即输出各种 DOCTYPE 标记、HTML 5 元素的起始和结束标记、注释、文件结束符。每个标记生成后,立即由 DOM 树构建调度器处理,以生成 DOM 树。如果遇到阻塞脚本样式表或正执行脚本,则标记分析中止,待解除阻塞或脚本执行完后再继续。

DOM 中的文本由字符构成的。相同特定用途的字符组成一个字符集(character set,也称为字符库 repertoire)中。为了明确地指代字符,每个字符都与一个数字相关联,称为代码点(code point)。字符在计算机中以一个或多个字节(bytes)的形式存储。

DOM 中的文本可能是中文、英文,或阿拉伯文等多种语言混和。lang 属性不仅可在 HTTP 头、html 或者 meta ,用于标记整个文档的全局语言,还可能在页面内元素上,比如

简体普通话

为了统一处理这些复杂的情况,需要将文本分为由不同语言组成的小段,在有的文本布局引擎里,这个步骤称为 itemize,分解后的文本段常被称作 text run,但是具体划分的规则可能根据不同的引擎有所区别,比如 HarfBuzzICU 一般是根据要使用的不同排版类来划分 (常称作 shaper),比如英语和法语可能使用同一个 shaper 排版,那么相邻的英语和法语文本就会划分到同一个 run 里,而希伯来文需要另一个 shaper,就划分到它自己的 run 里。

不少浏览器还会在这个划分下面,在确定具体使用的字体之后,根据使用字体的不同划分更细的 run,比如 SimpleTextRun,最后把它们逐一交给 shaper 进行排版得到要绘制的字形。

开发建议

声明内容应完全位于文件开头的前1024个字节内,因此最好将其放在 head 元素开始标记之后。

<head> <meta charset="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

二、字体匹配

字体是字形(glyph)定义的集合,即用于显示字符的形状的定义。

浏览器识别出要处理的字符后,就会在字体中查找可用于显示或打印这些字符的字形。 给定的字体通常只包含一个字符集,如果字体中缺少某个特定字符的字形,一些浏览器会在系统的其他字体中查找缺失的字形(这意味着该字形看起来会与周围的文本不同)。否则,通常会看到一个方框、问号或其他字符来代替。

根据 CSS 确定字体(CSS3 字体匹配算法 ),例如

<p>A <strong>XX</strong> ruler.</p> p { font-family: Helvetica, Arial, sans-serif; } p strong { font-weight: bold; }

表示这个段落里优先使用 Helvetica 这个 family 的字体,如果找不到,就找 Arial,如果还是找不到,就用浏览器设置的默认非衬线字体。稍微复杂的“XX”,它应该继承父元素的 font-family, 也用 Helvetica,但不用默认的 Regular,而用 Bold 版本,假如找不到 Helvetica Bold,就找 Arial Bold,否则就找浏览器默认设置的 Bold 版本,假如都没有呢?就要考虑用人工伪造的方式来显示粗体了。

中文字体按照英文字体一样的规则来判断:逐个字符尝试当前的字体是否提供了针对该字符的字形,如果没有则尝试下一个,要是到了最后都没找到匹配的字体呢?CSS 规范里只简单的说执行“system font fallback”,但这个过程在不同的浏览器下可能很不一样。

具体的字体选择还有一些不太容易注意的细节,也是各个浏览器差异比较大的一点。

开发建议

三、字体渲染

当确定了字体以后,就可以将文本、字体等等参数一起交给具体的排版引擎,生成字形和位置,然后根据不同的平台调用不同的字体 rasterizer 将字形转换成最后显示在屏幕上的图像。每个操作系统都包含一个或多个文本渲染引擎,每个浏览器则控制着使用哪个渲染引擎。比如iOS、Mac OS 下用 Core Text(工作在 Quartz 2D 之上),Linux/X11 下用 FreeType,Windows 下用 GDI/DirectWrite。

不同版本的操作系统和浏览器所使用的渲染引擎可能也存在差异,因此我们不能期望所有浏览器(即使在同一系统)的渲染效果完全相同。

在 Windows 系统中,字体格式对渲染效果有着显著的影响。字体文件包含字体的矢量轮廓,有两种格式:PostScript (轮廓由三次曲线构成) 或 TrueType (轮廓由二次曲线构成)。EOT 和 .ttf 文件总是包含 TrueType 字体,而 .otf 字体通常基于 PostScript。WOFF 可以包含两种字体格式。

字体微调(hinting)可以帮助字体的矢量轮廓与像素网格保持一致,其操作范围很广,从细微的调整到针对特定尺寸、像素级的精确指令都可以实现。