Acid 测试中的场景
在我们之前的 Acid3 帖子中,我提到我们通过的最后一个测试,即测试 79,非常难。该测试涵盖了 SVG 文本布局和字体支持的许多细节。为了通过它,我们不得不修复许多错误。除非你是一个核心标准极客,否则你可能不会觉得所有这些细节令人兴奋,但既然我们已经谈到了一些其他最近的修复,我想我会讲讲这个故事。这个子测试最初由 Cameron McCormack 贡献。
错误 1:对 altGlyph 元素的 DOM 支持
我开始测试 79 是因为一个周末的黑客乐趣。虽然我在 SVG 代码上做了一些修改,但这并非我的主要专业领域,而且我完全没有看过文本布局代码,所以我觉得这将是一次有趣的学习经历。我当时并不知道我们最终会争分夺秒地修复它。
我们在测试 79 中看到的第一个失败是,对 SVG <text>
元素调用 `getNumberOfCharacters` 返回的结果差了一个。测试暗示这可能是由于对 UTF-16 处理不当(SVG 1.1 勘误表中涵盖了这一点)。然而,在进一步研究后,我意识到我们对 UTF-16 的处理是正确的,但 <text>
元素包含了一个我们不识别的 <altGlyph>
元素,因此,根据 SVG 规则,它没有被渲染。对于不熟悉文本布局的人来说,“字形”(glyph)指的是将在屏幕上绘制的特定符号——通常每个字符都有自己的字形,但情况可能比这更复杂。
所以,作为第一步,在 r31240 中,我让引擎完全识别 <altGlyph>
并允许它最初以 <tspan>
元素的形式渲染。这有点像 SVG 中相当于 HTML <span>
的元素,它只渲染其文本内容。
在应用此修复后,我发现测试字符位置的代码在字符 2 的位置上出现了错误。
插曲:修改测试用例
此时,我意识到我们可能会遇到许多错误的位置,一次性看到所有问题会比逐个查看更有帮助。Acid3 只报告给定测试中的第一个失败。所以我保存了一个本地副本,修改它以报告测试 79 中的所有失败,并将其更改为测试字符之间的间距而不是字符位置。测量一个字符的任何错误都会传播到所有后续字符,因此查看位置差异(在排版术语中称为“推进量”)更有用。这立即表明,迄今为止最常见的失败是未能正确支持 SVG 字体中的多字符字形。
错误 2:多字符字形
SVG 有一项功能,允许指定与多个字符匹配的字形。你可以用它来创建像连字这样的东西。例如,比例字体通常会为“fi”组合包含一个特殊字形,因为如果这些字母以特殊方式组合绘制,它们会看起来更美观。
我们有支持此功能的基本代码。然而,问题是我们的代码一次只测量一个字符。字体代码无法决定选择一个匹配多个字符的字形,因为它一次只看到一个字符,而且它也无法报告一个字形匹配了多少个字符。所以,尽管绘图代码某种程度上是正确的,但度量是基于单字符字形的。在 r31310 中我修复了这个问题。
插曲:研究测试用例
修复后,我研究了测试用例,对剩余的失败进行了分类。我意识到可能不止一个错误,如果我把它们记录下来,其他人就可以提供帮助,或者在我没有时间的时候继续处理这些错误。
我发现了四个独立的剩余错误:SVG 字形匹配的优先级不正确(它选择了最长匹配而不是第一个匹配),在字形匹配中未能考虑 xml:lang
属性,未能支持显式字距调整对,以及未能渲染 altGlyph 选择的实际备用字形。
错误 3 和 4:字形选择
在研究了 SVG 文本规范后,我意识到我们用于字形选择的数据结构是错误的。SVG 字体要求你按照指定的顺序选择第一个匹配的字形,除了文本之外还有许多不同的规则(例如,你必须考虑语言和阿拉伯字符的多种形式)。我们只有一个字形的哈希表,通过检查从最长到最短的可能字符序列来查找候选字形。这导致了错误的优先级匹配,并且完全没有正确处理语言。
我决定将 GlyphMap 改为 N 叉字典树(或“前缀树”),每个节点带有一个可能的字形向量,以及一个用于子节点的哈希表。我还根据字形在树中的位置存储了一个“优先级”值。为了进行查找,我将根据可用字符逐个遍历字典树,收集在每个级别找到的所有字形。接下来,字形按优先级排序。最后,扫描这个候选字形向量,以找到第一个匹配其他约束(如阿拉伯语形式)的字形。
Opera 拿出看家本领
制作一个新的精巧数据结构比我之前的两次修复要复杂得多,所以我为此忙了两天。周三早上,我听说 Opera 将宣布 Acid3 得分达到 98/100。一直在研究 Acid3 的 WebKit 开发者意识到我们正在修复大部分剩余问题,并且很可能在当天结束时通过测试,并将其发布出去。
此时,我想表扬 Opera 开发者取得的成就。当他们发布 100/100 截图时,我们并不知道测试 79 中存在一个错误,我们当时最多只希望能成为第一个发布 100/100 公开构建版的人,并且我们两个团队都能获得一些赞誉和积极曝光。直到我们修复代码中的最后一个错误时,我们才发现了测试中的错误,正如您将在下面看到的那样。
无论如何,一旦我们意识到竞争开始了,并且 Opera 比我们预期的进展要快得多,我们决定完成我们的修复。Eric 修复了 XML 中字符集编码错误的处理,Antti 搞定了两个 SMIL 测试修复,我则整理了这个补丁,制作了一个回归测试,并将其提交到了 r31324 和 r31325。(我们有一项政策,每项修复都必须附带一个自动化回归测试,无论我们多么想赶时间——这是为了我们从长远来看能够更快地发展,通过拥有一个全面的回归测试套件来捕捉错误。即使如此,我还是犯了一点提交失误,但团队立即发现了它,因此有了这两个修订版。)
错误 5:altGlyph 渲染
此时,我遇到了一个问题。Niko,最初编写 SVG 字体和文本代码的人,当时正忙于学业,而我刚刚通过修复这些错误学到了所有关于 SVG 文本系统的知识,所以我成了顶级专家,某种程度上肩负了重任。但是这些测试还剩下两个错误,而且都不容易修复。我决定与渲染大师 Dave Hyatt 分担剩余的工作。我请他负责 altGlyph 的修复,而我则处理字距调整。
结果发现这里实际上有两个不同的问题。altGlyph 的查找是按 ID 进行的,但不知何故,字形元素没有进入 ID 映射。在 Darin 和团队其他成员的帮助下,Hyatt 设法弄清楚我们对 SVG <glyph>
元素的“id”属性处理不当。
第二个问题是实际选择备用字形并将其用于度量和渲染。这比看起来要简单,因为它几乎完全可以在 SVG 字体机制中处理。
你可以在 r31338 中看到 Hyatt 修复的详细内容。我帮助他准备了这个测试用例,有一段时间他以为自己的代码坏了,因为我给他发送了一个通过时显示红色方框的测试。孩子们,永远不要在你的浏览器测试中这样做。绿色表示通过,红色表示失败。
错误 6:字距调整
最后一个错误是对字距调整的支持。在 SVG DOM 中为 <hkern>
元素添加支持,并构建字距调整对的向量非常容易。棘手的是存储足够的信息来决定何时应用字距调整对,以及帮助确定适用性的匹配算法。字距调整对在 SVG 规范中可以通过两种方式指定:Unicode 值或字形名称。
当我阅读规范时,Darin 正在旁边看着,因为我希望他在匹配算法的这部分能提供帮助。正是在这一点上,我们意识到测试有一个错误。它有一个 <hkern>
元素,其 u1
属性为“EE”,并期望应用字距调整。但这不是逗号分隔的字符序列,也不是 Unicode 范围,而这正是 SVG 1.1 <hkern>
元素规范所要求的语法。这意味着如果不违反 SVG 规范,你不可能通过这个测试。我立即告知了 Acid3 编辑 Ian Hickson,并与测试 79 的原作者 Cameron 确认了错误。当他们思索如何修复测试时(Ian 对修复这个非常复杂的测试中出现的少数错误反应非常迅速),Darin 和我继续研究最后一个错误。
最后,我们设法传递了字形信息并正确应用了匹配。我们决定处理 SVG 规范的全部复杂性,尽管我们本可以为了通过测试而偷工减料,因为它并没有测试字距调整对的所有可能形式。与此同时,Ian 已经修复了测试中的错误并撰写了博客。我终于第一次在屏幕上看到了 100/100 的得分。每个人都怂恿我提交,但我坚持要让补丁经过审查并创建回归测试,因为在 WebKit,我们就是这样做的。最终,在 r31342 中,我们获得了 100/100 的分数,并向公众开放。剩下的就都是历史了。
作为对比
如你所见,这个测试相当严格,我们付出了很多努力才使其通过。我们修复了许多错误,从基础到晦涩,从简单到复杂。我们没有现成的代码能让我们轻易达到目标。
并非所有 Acid3 上的测试都同样严格。测试 75 和 76 是 SVG 动画测试,由 Erik Dahlström 贡献。我们已经开始实现这项功能,但它相当不完整,我们通过 `ifdefs` 将其禁用,并且在过去的版本中选择了不发布它。我们相信新功能的增量开发,所以我们准备启用它并修复通过测试所需的错误。我们原以为这需要大量额外工作。令我们惊讶的是,结果发现我们在旧代码基础上所做的前几项更改就足以通过这两个 SVG 动画测试。
Antti 检查并提交了这些修复,但我们不认为我们现有不完整的实现真正令人满意,并且在主要的公开版本中原样发布它可能不是一个好主意。我们对这段代码还不满意,但这对于我们的开放开发主干来说是一个很好的进步。
致谢
最后,关于 Acid3 最让我惊讶的是它带来的乐趣。回想当年 Acid2 风靡一时的时候,Hyatt 几乎独自完成了所有的修复工作,所以我们其他人没有太多机会投入其中。但 Acid3,以及围绕它略带傻气的竞争氛围,让 Web 标准变得有趣起来。
与超快的性能、炫酷的新功能,甚至使网站正常运行的基本 Web 兼容性工作相比,Web 标准常常显得无聊。互操作性对于作为一个开放平台的 Web 至关重要,但很难向普通用户解释其重要性。Acid 测试让 Web 标准变得有趣,无论是对浏览器开发者、Web 设计师还是普通用户。无论这些测试的内在价值如何,我认为我们都应该感谢 Ian Hickson 和所有测试贡献者。
我还要感谢 Opera 给了我们真正的竞争,让这场角逐变得激烈。我们非常尊重他们的开发者以及他们在 Web 标准方面所做的一切工作。