浏览器环境下,网页从加载到其中文本渲染,可分为 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 ,用于标记整个文档的全局语言,还可能在页面内元素上,比如

<span lang="zh-cmn-Hans-CN">简体普通话</span>

为了统一处理这些复杂的情况,需要将文本分为由不同语言组成的小段,在有的文本布局引擎里,这个步骤称为 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)可以帮助字体的矢量轮廓与像素网格保持一致,其操作范围很广,从细微的调整到针对特定尺寸、像素级的精确指令都可以实现。