增量字体传输

W3C 候选推荐标准草案

关于本文档的更多详细信息
此版本:
https://www.w3.org/TR/2025/CRD-IFT-20251118/
最新发布版本:
https://www.w3.org/TR/IFT/
编辑草案:
https://w3c.github.io/IFT/Overview.html
历史记录:
https://www.w3.org/standards/history/IFT/
反馈:
public-webfonts-wg@w3.org 主题行请使用“[IFT] … 消息主题 …”(存档
GitHub
实现报告:
https://wpt.fyi/results/IFT
编辑:
Chris LilleyW3C
Google Inc.
Adobe Inc.

摘要

本规范定义了一种将字体从服务器增量传输到客户端的方法。 客户端仅加载其实际需要的字体部分,从而显著减少数据传输量。可以对同一字体进行多次增量 添加,例如,用户代理可在用户浏览多个页面时更新字体。增量传输通过避免破坏布局 (字距调整、连字等)规则,对 unicode-range 进行了改进, 这意味着它可以高效支持对拉丁文字以及印度文字或阿拉伯文字等复杂文字系统进行细粒度增量 传输。

本文档状态

本节描述本文档发布时的状态。 当前 W3C 出版物列表以及本技术报告的最新修订版 可在 W3C 标准和草案索引中找到。

本文档由 Web 字体工作组采用推荐标准 路径作为候选推荐标准草案发布。

本文档是草案,随时可能由其他文档更新、替代或废止。除作为正在进行的工作外,不宜引用本文档。

本文档受 2025 年 8 月 18 日 W3C 流程文档管辖。

目前尚无实现报告。测试套件正在开发中。

本文档由一个依照 W3C 专利政策运作的小组编制。W3C 维护着一份与该小组交付成果相关的所有专利 披露的公开列表;该页面还包含专利披露说明。实际知晓某项专利,并认为该专利 包含必要权利要求的个人, 必须依照 W3C 专利政策第 6 节披露相关信息。

作为候选推荐标准发布并不意味着获得 W3C 及其成员的认可。候选 推荐标准草案整合了工作组打算纳入后续候选推荐标准快照的、相对于上一候选推荐标准所作的更改。

1. 介绍

本节为非规范性内容。

增量字体传输(IFT)是一种用于改善 Web 上远程字体(或称“Web 字体”)延迟的技术。 如果没有这项技术,浏览器必须下载字体的每一个字节,才能使用该字体渲染 任何字符。IFT 允许浏览器仅下载文件中的部分字节, 从而 缩短从浏览器意识到需要某种字体,到能够使用该字体渲染 所需文本之间的感知延迟。与传统字体子集化方法不同,增量 字体传输 会保留各片段之间布局规则的编码(渐进式字体扩充:评估报告 § fail-subset)。

WebFonts 的成功应用并不均衡。本规范使 WebFonts 能够用于 目前因网络缓慢、字体非常庞大或子集化要求复杂而无法使用的场景。例如,即使使用 WOFF 2 [WOFF2],中日韩语言的字体仍可能大到不切实际。

1.1. 技术动机:评估报告

有关促成本规范形成的研究,请参阅《渐进式字体扩充:评估报告》[PFE-report]

1.2. 概述

增量字体是 一种常规的 OpenType 字体,它经过重新格式化以包含增量功能, 其中部分功能由两个额外的提供。使用 这些新表,可以通过加载补丁并将其应用到字体来扩充字体 (例如覆盖更多码位)。

IFT 技术由四个主要部分组成:

从高层次来看,增量 字体的使用方式如下:

  1. 客户端下载初始字体文件,其中包含完整字体中某些初始数据子集, 以及描述可用于扩展 字体的补丁集合的嵌入数据

  2. 客户端根据要渲染的内容选择、下载并 应用补丁,以扩展 字体,使其覆盖更多字符、布局特性和/或变体空间。每次出现新 内容时都会重复此步骤。

1.3. 创建增量字体

预计生成增量字体最常见的方式,是将现有字体转换为使用本规范定义的 增量编码。从高层次来看,将现有字体转换为增量字体的过程 如下:

  1. 选择初始子集的内容,该内容将包含在客户端加载的 初始字体文件中, 通常包括原始字体中预计始终需要的任何数据。

  2. 选择一种或多种补丁类型。不同补丁类型具有不同特性,因此不同使用 场景可能需要不同的 补丁类型,某些情况下也可能混合使用两种类型。

  3. 选择字体的分段方式。客户端将使用补丁把各个片段添加到基础子集中。 选择 合适的分段方式,是生成高效编码过程中最重要的部分之一。

  4. 根据这些选择生成一组补丁,其中每个补丁都相对于 初始字体文件或之前的补丁, 添加特定片段的数据。

  5. 生成包含初始子集和补丁映射的初始字体文件。该映射 列出所有可用补丁文件、它们所在的 URL,以及补丁将向字体 添加哪些数据的信息。

注:这是对 创建增量字体过程的高度简化描述;有关生成编码以及 编码要求的更深入讨论,请参阅 § 7 编码一节。

1.4. 性能注意事项与增量字体传输的使用

是否适合使用增量传输,取决于字体、 网络和被渲染内容的特性,因此它并非始终有益。本节提供非规范性指导,帮助确定 何时应使用增量传输。

增量字体通常会触发并行加载多个补丁。因此,为最大限度 提升性能,在 提供增量字体时,建议使用支持多路复用的 HTTP 服务器(例如 [rfc9113][rfc9114])。

与加载整个字体相比,增量加载字体存在根本性的性能权衡。 简单来说,增量传输可能传输更少的字节,但潜在代价是 增加所发出的网络请求总数和/或增加请求处理 延迟。通常,只有当减少传输字节所降低的延迟 超过增量传输方法引入的额外延迟时,增量字体传输才有益。

首先需要考虑的是被渲染内容的语言。评估报告 包含对三类语言进行增量字体传输模拟的结果 (渐进式字体扩充:评估报告 § langtype)。有关 增量字体传输在各语言类别中的预期性能,请参阅其结论部分 渐进式字体扩充:评估报告 § conclusions

其次,预计需要字体中的多少内容?如果预计渲染内容时需要 字体的大部分内容,那么增量字体传输不太可能带来收益。但在许多情况下, 预计只需要字体的一部分。例如:

增量传输的一种替代方案,是将字体拆分为不同子集(通常按文字系统拆分), 并使用 @font-face 的 unicode range 特性仅加载需要的子集。但是,如果不同 子集中的字符之间存在布局规则,这可能改变某些内容的渲染结果,参见 渐进式字体 扩充:评估报告 § fail-subset。增量字体传输不会遇到此问题,因为它可以涵盖 原始字体及其全部布局规则。

1.4.1. 减少网络请求数量

如上一节所述,最基本的增量字体传输实现 与传统字体加载相比,往往会增加请求总数。由于每次扩充 通常至少需要一个往返时间,如果发出过多 请求,性能可能会受到负面影响。根据可用的补丁类型以及补丁映射中提供的信息量,客户端可以预先请求当前 尚不需要、但预计未来会需要的码位补丁。实现 对此特性的智能使用可帮助减少请求总数。评估报告通过 测试一种基于基本字符频率的码位预测方案来研究这一点,并发现其 改善了整体性能。

2. 选择启用机制

Web 页面可通过在 ''@font-face'' 块中使用 CSS 字体技术 关键字(CSS 字体 4 § 11.1 字体技术),选择为某种字体启用增量传输。关键字 incremental 用于表明 所引用字体包含 IFT 数据,并且 仅应由支持增量字体传输的用户代理加载。

@font-face {
    font-family: "MyCoolWebFont";
    src: url("MyCoolWebFont-Incremental.otf") tech(incremental);
}
@font-face {
    font-family: "MyCoolWebFont";
    src: url("MyCoolWebFont-Incremental.otf") tech(incremental);
    unicode-range: U+0000-00FF;
}

如第二个示例所示,unicode-range 可与 IFT 字体结合使用。 Unicode 范围应设置为与完全扩展字体的覆盖范围相匹配。这样,当字体不支持 任何所需码位时,客户端便可 避免尝试加载 IFT 字体。

另一种方法是使用 CSS supports 机制,它可以根据 字体技术进行选择:

@when font-tech(incremental) {
  @font-face {
    font-family: "MyCoolWebFont";
    src: url("MyCoolWebFont-Incremental.otf");
  }
}
@else {
  @font-face {
    font-family: "MyCoolWebFont";
    src: url("MyCoolWebFont.otf");
  }
}

注:每个单独的 @font-face 块可以 选择启用或不启用 IFT。这是因为 字体在 Web 页面上的使用方式多种多样。作者可以控制希望对哪些字体使用 此技术,以及不对哪些字体使用。

注:IFT 技术关键字可以与其他 字体技术说明符结合使用,以执行 字体特性选择。例如,@font-face 可以包含两个 URL,一个使用 tech(incremental, color-COLRv1),另一个使用 tech(incremental, color-COLRv0)

2.1. 离线使用

在某些情况下,用户代理可能希望保存 Web 页面以供离线使用。保存的页面可能在没有 网络连接时查看, 因而无法请求增量字体引用的任何额外补丁。由于内容发生变化时(例如由于 JavaScript 执行) 无法扩展 增量字体,因此页面保存机制应通过调用 完全扩展字体子集来 完全扩展增量字体,并将对增量字体的引用替换为完全扩展后的字体。

3. 定义

3.1. 字体子集

字体子集是字体文件 [iso14496-22] 的修改版本,其中仅包含 渲染以下内容子集所需的数据:

这些内容均由原始字体支持。当使用正确构造的字体子集,通过任意 组合的子集码位、布局 特性设计变体 空间渲染文本时,其渲染结果与原始字体完全相同。这包括使用渲染器可能选择使用的原始字体中的任何 可选排版 特性进行渲染,例如提示指令。设计 变体空间使用用户轴标度指定(OpenType 规范 § otvaroverview#coordinate-scales-and-normalization)。

字体子集 定义描述字体 子集所支持的最少数据(码位、布局特性、 变体轴空间)。

注:为方便起见,本文档其余部分 链接到 [open-type] 规范,它是 [iso14496-22] 的副本。

3.2. 字体补丁

字体补丁是一个 对要应用于 IFT 编码字体的更改进行编码的文件。补丁用于扩展 现有字体子集并 提供更广的覆盖范围。

补丁格式是 相对于字体子集应用的更改的指定编码。按照该格式编码的一组 更改就是字体补丁。每种补丁格式都有一个关联的补丁应用算法, 该算法以字体子集和一个使用补丁格式编码的字体补丁作为输入, 并输出扩展后的字体子集

3.3. 补丁映射

补丁映射是一个OpenType 表,它对一组 从字体子集定义到承载补丁的 URL 的映射进行编码,这些补丁用于 扩展增量 字体补丁映射表编码 一组补丁映射 条目,其中每个 条目都有键和值。条目的键定义条目的覆盖范围,即关于 哪些子集定义将与其匹配的信息。值指定在条目 匹配时应应用哪些补丁的信息。补丁映射 条目摘要:

  • 字体子集定义:定义 基础覆盖范围。

  • 对零个或多个子补丁映射条目的引用:
    这些子条目 用于扩展覆盖范围。

  • 子条目匹配模式,可以是合取模式或析取模式。

有关补丁映射格式的更多详细信息,请参阅 § 5 字体格式 扩展

3.4. 数据类型说明

本规范其余部分中的编码数据结构,使用 OpenType 规范 § otff#data-types 中定义的数据类型描述。与 OpenType 的其余部分一样,所有字段均使用“大端序”字节 顺序。

4. 扩展字体子集

本节定义客户端用于扩展增量字体子集,以覆盖更多 码位、布局特性和/或设计空间的算法。该算法是一个迭代算法,会重复执行以下操作:

此过程会重复,直到不再有相关补丁。由于应用补丁可能改变 嵌入字体文件中的补丁 映射,因此在每次迭代中,都会重新解析字体子集当前版本中的补丁映射,以查看还有哪些补丁 可用。因此,在每次迭代中,字体子集都是可用补丁的事实来源,并且 完整封装了扩充过程的当前 状态。

4.1. 补丁失效

嵌入字体子集中的补丁映射为每个补丁编码一种失效模式。补丁的失效 模式 标记应用该补丁后,哪些其他补丁将不再有效。扩展算法使用这种 失效模式来确定哪些补丁兼容,并影响选择顺序。补丁 应用期间的有效性由 § 5.3 补丁映射 表中的兼容性 ID 强制保证。每个补丁内部都编码一个兼容性 ID, 该 ID 必须与列出该补丁的§ 5.3 补丁映射表中的兼容性 ID 相匹配。

共有三种失效模式:

特定补丁的失效模式编码在其格式编号中,该编号可在 § 6.1 格式摘要中找到。

4.2. 默认布局特性

大多数文本塑形器都有一组始终启用、因此在 增量加载字体中始终必需的布局特性附录 A:默认特性标签汇总了一组 截至本文撰写时,已知在常见塑形器实现中默认必需的特性。 当形成一个字体子集定义作为扩展算法的输入时,客户端 通常应在子集定义中包含附录 A:默认特性 标签中的所有特性。但是,在某些情况下,客户端可能 知道将使用的特定塑形器不会使用附录 A:默认特性标签中的某些特性,并可 选择将这些未使用的特性排除在子集定义之外。

4.3. 增量 字体扩展算法

客户端使用以下算法扩展增量字体子集,使其覆盖更多 码位、布局特性和/或设计空间。该算法每次增量选择并应用一个补丁, 并按需加载 补丁。作为减少网络往返次数的重要优化,它允许为最终 需要的所有补丁启动加载。这些补丁有两种形式:

扩展增量字体子集

该算法的输入为:

该算法输出:

算法如下:

  1. extended font subset 设置为 font subset

  2. extended font subset 中加载 'IFT ' 和 'IFTX'(如果存在)映射。这两个 表都采用§ 5.3 补丁映射表格式。检查它们是否符合 § 5.3 补丁映射表中的要求。如果任一表无效,则调用处理 错误。如果 extended font subset 没有 'IFT ' 表,则它 不是增量 字体且无法扩展,返回 extended font subset

  3. 如果 'IFT ' 中的兼容性 ID 等于 'IFTX' 中的兼容性 ID,则这是一个错误,调用 处理错误

  4. 对于 'IFT ' 和 'IFTX'(如果存在)中的每一个:通过调用解释格式 1 补丁映射解释格式 2 补丁映射, 将表转换为条目列表。 将返回的条目列表连接成一个列表 entry list

  5. 对于 entry list 中的每个 entry,以 entrytarget subset definition 作为输入,调用检查条目交集;如果返回 false,则从 entry list 中移除 entry。此外,还要从 entry list 中移除补丁 URL 字符串已在先前算法执行期间加载并应用的 任何条目。

  6. 如果 entry list 为空,则扩展操作完成,返回 extended font subset

  7. 根据每个条目的补丁格式的失效模式,将 entry list 中的条目分为 3 个列表: full invalidation entry listpartial invalidation entry listno invalidation entry list

  8. 按照以下过程选择一个 entry

    • 如果 full invalidation entry list 不为空,则恰好选择其中一个 条目。按照 § 4.4 选择失效补丁中的条件选择该单个条目。

    • 否则,如果 partial invalidation entry list 不为空,则恰好选择其中一个 条目。 按照 § 4.4 选择失效 补丁中的条件选择该单个条目。

    • 否则,选择 no invalidation entry list 中在 补丁映射内最先列出的条目。对于 § 5.3.1 补丁映射表:格式 1,这是条目索引 最小的条目。对于 § 5.3.2 补丁映射表:格式 2,这是在entries数组中最先出现的条目。 如果 IFT 和 IFTX 表中的条目都是候选项,则认为 IFT 中的所有条目都排在 IFTX 中的条目之前。

  9. 通过以 initial font URL 作为初始字体 URL、以 entry 中第一个补丁 URL 字符串作为 补丁 URL 字符串,调用加载补丁文件,开始加载 patch file(如果此前尚未启动)。如果 entry 中还有其他 URL 字符串, 则使用相同过程启动对它们的加载。这些 URL 可能在本算法后续迭代中使用。 此外,客户端还可以选择使用相同过程,为 entry list 中任何不会被 entry使之失效的条目启动加载。 客户端在本算法单次执行期间可以加载并应用的补丁总数限制为:

    这些补丁可在本算法单次调用期间加载并应用。如果任一计数 超过限制,则这是一个错误,调用处理 错误

  10. patch file 加载完成后,使用 § 6 字体补丁格式中适当的应用 算法(与 entry 中的补丁格式相匹配),使用补丁 URL 字符串和 entry 中的兼容性 id,将 patch file 应用于 extended font subset。如果加载 patch file 时出错,则按照处理错误进行处理。

  11. 转到步骤 2。

注:在步骤 9 中,客户端可以选择并行启动加载 不会被当前所选条目 使之失效的补丁。这类补丁几乎总会在后续迭代中需要(因为它们不会 失效),并且并行加载 可通过消除往返显著提高性能。以这种 方式启动加载时,请求发起顺序需遵循步骤 8 中指定的顺序。

注:如果剩余相交条目全部是 不失效条目,则无需在后续迭代中重复步骤 5 的交集检查(这是因为不失效补丁在应用时不会改变 可用补丁列表, 只会移除其自身条目)。此外,作为一种优化,客户端可能 希望将剩余相交的不失效条目作为单个批处理操作来应用补丁。 由于以字形为键的补丁的性质,可以直接在一次处理中构造 依次应用多个补丁的最终结果。这样可避免针对每个补丁反复重新合成新的 glyf/loca/CFF/CFF2 表,并可显著减少所需计算总量。

扩展算法的 执行示例可在附录 B:扩展 算法执行示例中找到。

检查条目交集

该算法的输入为:

该算法输出:

算法如下:

  1. 对于 subset definition 中的每个集合(码位、特性标签、 设计空间),检查该集合是否与 mapping entry 的子集定义中的对应集合相交。集合 在以下情况下相交:

    子集定义集合为空 子集定义集合不为空
    映射条目集合为空 true true
    映射条目集合不为空 false 如果两个集合相交,则为 true

    检查设计空间集合是否相交时,只要至少有一对 相交片段 (标签相等且范围相交),它们就相交。

  2. 如果步骤 1 中检查的一个或多个集合不相交,则为 intersects 返回 false。

  3. 如果 mapping entry 中没有子条目,则为 intersects 返回 true。

  4. 对于 mapping entry 中引用的每个 子条目,以 subset definition 为输入调用检查条目交集。如果 子条目匹配模式为合取模式,则仅当所有调用都返回 true 时,才为 intersects 返回 true。 否则,仅当至少一个调用返回 true 时才返回 true。

下表表示一个补丁映射,并展示对 各种条目进行交集检查的预期结果。在这些示例中, 未指定集合(即码位、特性标签、设计空间、子条目)时,假定它为空集合。

索引 映射条目 子集定义 是否相交?
0
subset definition {
  code points: {1, 2, 3},
},
match mode: disjunctive,
code points: {2},
true
1
subset definition {
  code points: {4, 5, 6},
},
match mode: disjunctive,
code points: {2},
false
2
subset definition {
  code points: {1, 2, 3},
},
match mode: disjunctive,
code points: {2},
feature tags: {smcp},
true
3
subset definition: {
  code points: {1, 2, 3},
},
match mode: disjunctive,
feature tags: {smcp},
false
4
subset definition: {
  code points: {1, 2, 3},
},
child entry indices: {1},
match mode: disjunctive,
code points: {5},
false
5
subset definition: {
},
child entry indices: {0, 1},
match mode: conjunctive,
code points: {2},
false
6
subset definition: {
},
child entry indices: {0, 1},
match mode: conjunctive,
code points: {2, 6},
true

加载 补丁文件

该算法的输入为:

该算法输出:

算法如下:

  1. 使用 URL 标准 § url-parsing,将 patch URL string 解析为URL 记录initial font URL基础 URL,编码为 UTF-8。将结果 存储在 target URL 中。如果 URL 解析失败,则返回错误。

  2. 使用实现用户代理的获取能力检索 target URL 的内容。 对于 Web 浏览器,应使用 [FETCH]。使用 [FETCH] 时,请求配置如下:

    • method设置为 GET。

    • url设置为 target URL

    • client设置为用于获取 initial font URL 的请求对象中的客户端。

    • initiator type设置为 "font"。

    • destination设置为 "font"。

    • referrer设置为 initial font URL

    • mode设置为 "cors"。

    获取请求, 其中processResponseConsumeBody为本 算法的步骤 3。

    对于不使用[FETCH]的 实现,请使用可用的获取能力向 target URL 发出请求。 在存在等效概念时,复制上文指定的基于 [FETCH] 的 设置。使用该获取操作的结果继续执行步骤 3。

  3. processResponseConsumeBody 接收响应 以及 null、failure 或字节序列。如果收到字节序列, 则将这些字节作为 patch file 返回。如果收到 null,则将空字节数组 作为 patch file 返回。 否则返回错误。

注:这些获取设置旨在与 CSS 字体加载所使用的设置相匹配,参见 CSS 字体 4 § 4.8.2 字体获取要求,但存在两处差异: 第一,referrer 设置为初始字体的 URL;第二,URL 解析使用初始字体作为基础, 而不是样式表。

处理 错误

如果扩展字体子集的过程因错误而失败,则字体内的一些数据可能 未完全加载,因此, 渲染依赖缺失数据的内容可能产生错误结果。客户端 可以选择继续使用 该字体,但只能将其用于渲染根据 § 4.6 确定字体能够渲染的内容已完全 加载的码位、特性和设计空间。 所有其他内容的渲染应按照客户端正常的回退逻辑,回退到其他字体。

如果错误发生在加载补丁文件期间,则客户端可以继续尝试 扩展字体子集。在扩展增量字体子集的步骤 8 中, 在每个失效分组内,任何加载失败的补丁都会被排除在选择范围之外。 如果某个失效组非空并正在从中选择,但其中只包含失败的补丁,则 扩展已经失败且无法 继续。

对于所有其他错误,客户端 不得尝试进一步扩展字体子集。

4.4. 选择失效补丁

在执行扩展增量字体子集 算法期间,某些情况下需要从候选条目列表中选择一个失效(完全或部分) 补丁条目。所使用的选择条件会直接影响 执行扩展所需的往返总次数。往返代价高昂,因此,为获得最佳性能,应以 尽量减少所需往返总次数的方式选择补丁。

以下选择条件 可尽量减少往返次数,并且客户端在 扩展增量字体 子集的步骤 8 中选择单个部分失效或完全失效补丁时必须使用

  1. 如果一个或多个候选条目的关联补丁文件此前已由 扩展增量字体 子集的步骤 9 加载, 则在以下步骤中,仅考虑补丁文件已加载或当前正在加载的候选条目。否则考虑所有 候选条目。

  2. 对于每个候选条目:将该条目的子集 定义与通过所引用子条目图可达的所有子条目的子集定义求并集,计算出总子集定义。

  3. 对于每个候选条目:计算总子集定义与 目标子集定义之间的集合交集。

  4. 找到一个条目,使其在步骤 2 中得到的交集不是任何其他交集的真子集。

  5. 查找与步骤 3 中找到的条目位于同一补丁映射中,并且具有相同交集的任何其他条目。从这组 条目(包括步骤 3 选中的条目)中,最终选择在补丁映射中最先列出的条目。对于 § 5.3.1 补丁映射表:格式 1,这是 条目索引最小的条目。对于 § 5.3.2 补丁映射表:格式 2,这是在entries数组中最先出现的条目。

注:寻找满足 步骤 3 条件的条目的一个快速高效方法,是先按码位 集合交集的大小、再按特性标签集合交集的大小,最后按设计空间 交集的大小对条目进行降序排序。该排序中的第一个条目 保证不是任何其他条目的真子集,因为任何真超集至少都必须多一个 项目。这种方法还有一个 额外好处,即会选择能够添加客户端当前所请求最多数据的补丁。

4.5. 目标子集定义

扩展增量字体子集 算法将基于客户端希望渲染的某些内容形成的目标子集定义作为输入。 客户端可以选择为整体内容形成一个子集定义,并 运行一次扩展算法。或者,客户端也可以将内容拆分为较小的文本段, 为每个文本段形成一个子集 定义,并针对每个较小的子集定义运行 扩展算法。只要满足以下条件,两种方法最终都会生成 能等效渲染整体内容的字体:

4.6. 确定字体能够渲染的内容

给定某个增量字体(无论是初始字体还是已部分扩展的字体),客户端可能 希望知道该字体在当前状态下 能够渲染哪些内容。在客户端试图确定文本中的哪些部分 应使用回退字体时,这一点尤为重要。

在回退处理期间,客户端通常会检查字体的cmap表,以确定 支持哪些码位;但是,在 IFT 字体中,由于§ 6.3 以字形为键补丁的工作方式, cmap表可能包含 尚未加载相应字形数据的码位映射。因此,客户端不应 仅依赖cmap表 来确定码位是否存在。相反,客户端可使用以下过程,检查 增量字体能够渲染某些内容中的哪些部分:

客户端还可能希望以比塑形单元更细的粒度了解字体能够渲染哪些内容。 以下伪代码 展示了一种可能的方法,可将未通过上述检查的塑形单元拆分为 可使用 增量字体渲染的文本段:

# 返回 shaping_unit 中受 ift_font 支持且可安全使用其渲染的文本段列表,
# 每个文本段为包含端点的 [start, end]。
#
# shaping_unit 是一个数组,其中每个项目都包含一个码位、关联的布局特性列表,
# 以及渲染该码位时所使用的设计空间点。
def supported_spans(shaping_unit, ift_font):
  current_start = current_end = current_subset_def = None
  supported_spans = []

  i = 0
  while i < shaping_unit.length():
    if current_subset_def is None:
      current_subset_def = SubsetDefinition()
      current_start = i

    current_end = i
    current_subset_def.add(shaping_unit.codepoint_at(i),
                           shaping_unit.features_at(i),
                           shaping_unit.design_space_point_at(i))

    if supports_subset_def(ift_font, current_subset_def):
      i += 1
      continue

    if current_end > current_start:
      supported_spans.append(Span(current_start, current_end - 1))
      # 不递增 i,以便在下一次迭代中单独检查当前码位。
      #
    else:
      i += 1

    current_start = current_end = current_subset_def = None

  return supported_spans


# 如果 ift_font 支持渲染 subset_def 所覆盖的内容,则返回 true。
def supports_subset_def(ift_font, subset_def):
  # 仅当以下两项检查均为 true 时才返回 true:
  # - subset_def 中的每个码位都由 ift_font 的 cmap 表映射到非 '0' 的字形 id。
  # - 使用 subset_def 对 ift_font 执行“扩展增量字体子集”算法,并在步骤 6 停止后,
  #   条目列表为空。

来自塑形单元、 且未被返回文本段之一覆盖的任何文本,都不受增量字体支持,应 使用回退字体渲染。每个文本段都应单独进行塑形(即每个文本段成为新的 塑形单元)。 由于此方法会拆分塑形单元,原始字体的某些特性(例如多码位 替换)可能无法 保留。如果客户端正确遵循扩展增量字体子集 算法,并使用按照§ 4.5 目标子集定义形成的子集定义, 则缺失的数据将被加载,这种情况只会在相关补丁正在 加载期间暂时发生。缺失补丁到达并应用后,受影响码位的渲染结果 可能因替换而发生变化。

注:上面的 "supported_spans(...)" 检查并非 用于驱动增量字体扩展。有关为执行扩展增量字体子集形成目标子集定义的指导, 请参阅 § 4.5 目标子集定义

4.7. 完全扩展 字体

本节定义一种算法,可用于将增量字体转换为完全扩展的 非增量字体。此 过程会加载增量字体提供的所有可用数据,并生成一个不再有任何 补丁可应用的静态字体文件。

完全扩展字体子集

该算法的输入为:

该算法输出:

算法如下:

  1. 使用 font subset 调用扩展增量字体 子集。输入的目标子集定义是一个特殊定义, 它 在检查条目交集步骤中被视为与所有条目相交。将 得到的字体子集作为 expanded font 返回。

4.8. 缓存已扩展的增量字体

已扩展的增量字体包含按照本节过程执行未来任何扩展 操作所需的全部状态。因此,如果客户端需要存储或缓存增量字体以供将来使用, 只需 存储最近一次应用扩展算法所生成的字体二进制文件。 无需保留初始 字体或之前扩展生成的任何版本。

5. 字体格式扩展

增量字体 遵循现有的 OpenType 格式,但包含两个由 4 字节标签 'IFT ' 和 'IFTX' 标识的新。 这两个新表都是补丁映射。所有增量字体至少包含一个 'IFT ' 表。'IFTX' 表是可选的。当两个表都 存在时,字体整体的映射是两个表映射的并集。这两个新 表仅在本 规范中使用,不会添加到 OpenType 规范中。

注:允许将映射拆分到两个 不同的表中,使增量字体更容易使用多种 补丁类型。例如,一种类型的所有补丁可在 'IFT ' 表中指定,第二种类型的所有补丁可在 'IFTX' 表中指定。这些补丁可以只更新其中一个映射表,从而避免产生冲突 更新。

5.1. 增量字体传输与字体压缩格式

在 Web 上使用字体时,通常会使用 [WOFF][WOFF2] 等压缩格式对其进行压缩。 只要满足以下条件,这类格式就可用于压缩增量字体传输编码中使用的初始字体文件:

  1. 通过压缩格式对字体进行编码后再解码的过程,不会修改每个 的字节。

  2. 由于增量字体传输扩展算法(§ 4 扩展字体子集)专门作用于未压缩字体 文件,因此在尝试扩展压缩字体之前,需要先将其解码。

对于 [WOFF2],需要特别 注意。如果增量字体将使用 WOFF2 编码以进行传输:

  1. 如果 WOFF2 编码将包含经过转换的 glyf 和 loca 表(WOFF 2.0 § 5.1 转换后的 glyf 表格式),那么增量 字体不应包含会修改 glyf 或 loca 表的§ 6.2 以表为键补丁。WOFF2 格式不 保证解码经过转换的 glyf 和 loca 表后得到的具体字节。§ 6.3 以字形为键补丁可 与经过转换的 glyf 和 loca 表结合使用。

  2. WOFF2 编码器可以按照 WOFF 2.0 § 5 压缩数据格式中定义的 标准过程处理 'IFT ' 和 'IFTX' 表,并使用 brotli 编码。

注:鉴于即使禁用 glyf/loca 转换,WOFF2 的压缩效果仍优于 WOFF, 通常建议 使用 WOFF2 压缩 IFT 字体;当编码使用 以表为键的补丁更新这些表时,应禁用 glyf/loca 转换。

5.2. CFF 和 CFF2 增量字体

对于字形轮廓存储在 CFFCFF2 表中的增量字体,还存在一些 额外限制:

这三项要求可显著简化 CFF 和 CFF2 表上以字形为键的补丁应用实现。 它们允许将字形数据插入 CFF/CFF2 表,而无需更改 CFF/CFF2 表中 编码的任何其他数据元素的偏移。 存储的 CFF/CFF2 偏移使客户端无需解析 CFF/CFF2 表的任何其他部分, 即可找到 CharStrings INDEX。

注:在某些情况下,CFF 或 CFF2 表可能会由 以表为键的补丁添加或更改。在这些情况下,补丁 还需要添加或更新 charstrings 偏移,以反映新增或更改后的内容。

CFF 或 CFF2 子例程化与增量字体传输兼容。但是,简单的 子例程化往往会 减小补丁大小,却增加初始文件大小。此外,WOFF2 通常被证明可以比其子例程化版本 更有效地压缩 未子例程化的字体(代价是未压缩文件显著 更大)。因此,建议避免使用子例程化、适度使用子例程化,或根据 IFT 的特定需求 对其进行定制。

5.3. 补丁映射表

补丁映射采用以下两种 格式之一进行编码:

每种格式都定义一种算法,用于解释使用该格式编码的字节,以生成它所表示的 条目列表。扩展增量字体子集 算法调用这些解释算法,并对得到的条目列表进行操作。 编码字节始终是补丁映射的事实来源。子集扩展期间应用补丁 会改变补丁映射的编码 字节,因此从编码字节派生出的条目列表也会改变。扩展 算法在每次迭代开始时都会重新解释 编码字节,以获取上一次迭代中所做的任何更改。

5.3.1. 补丁映射表:格式 1

格式 1 补丁映射 编码:

类型 名称 描述
uint8 format 设为 1,标识其为格式 1。
uint24 reserved 未使用,设为 0。
uint8 flags 用于指示可选字段是否存在的标志。 如果设置了位 0(最低有效位),则cffCharStringsOffset将 存在。 如果设置了位 1,则cff2CharStringsOffset将 存在。
uint32 compatibilityId[4] 用于标识与此字体兼容的补丁的唯一 ID(参见§ 4.1 补丁失效)。编码器选择该 值。编码器应将其设置为一个此前在 编码 IFT 字体时从未使用过的随机值。
uint16 maxEntryIndex 此表中编码的最大条目索引。
uint16 maxGlyphMapEntryIndex 此表中编码的最大字形映射条目索引。 必须小于或等于 maxEntryIndex。
uint24 glyphCount 提供映射的字形数量。 必须与字体文件中的字形数量相匹配。

注:字体中的字形数量 编码在字体文件中。截至本文撰写时,该值列在maxp表中; 但是,未来的字体格式扩展可能使用其他表来编码 字形 数量。

Offset32 glyphMapOffset 指向字形映射子 表的偏移。偏移相对于此表的起始位置。
Offset32 featureMapOffset 指向特性映射 子表的偏移。偏移相对于此表的起始位置。可以为 null(0)。
uint8 appliedEntriesBitMap[(maxEntryIndex + 8)/8] 一个位图,用于跟踪哪些条目已应用。如果位 i 被设置,则 表明条目 i 的补丁已应用于此字体。位 0 是 appliedEntriesBitMap[0] 的最低有效位,而位 7 是 最高有效位。位 8 是 appliedEntriesBitMap[1] 的最低有效位,依此 类推。
uint16 urlTemplateLength urlTemplate 字节数组的长度。
uint8 urlTemplate[urlTemplateLength] 包含URL 模板的字节数组,该模板用于生成 与每个条目关联的 URL 字符串。
uint8 patchFormat 指定由 urlTemplate 链接的补丁格式。必须设置为 § 6.1 格式 摘要表中的一个格式编号。
uint32 cffCharStringsOffset 仅当flags的位 0(最低有效位)被设置时存在。 给出从 CFF 表起始位置到 CFF 表的 CharStrings INDEX 数据结构的偏移。
uint32 cff2CharStringsOffset 仅当flags的位 1 被设置时存在。 给出从 CFF2 表起始位置到 CFF2 表的 CharStrings INDEX 数据结构的偏移。

注:glyphCount的 设计旨在与允许超过 65,535 个字形的拟议未来字体格式 扩展兼容。

字形映射编码:

字形映射表将字体中的每个字形索引与一个条目索引关联。

类型 名称 描述
uint16 firstMappedGlyph 所有小于 firstMappedGlyph 的字形索引都隐式映射到条目索引 0。
uint8/uint16 entryIndex[glyphCount - firstMappedGlyph] 字形 i 的条目索引存储在 entryIndex[i - firstMappedGlyph] 中。如果 maxEntryIndex 小于 256,则数组成员 为 uint8,否则为 uint16。

特性映射编码:

特性映射表将特性标签和 字形的组合与条目索引关联。

类型 名称 描述
uint16 featureCount featureRecords 的数量。
FeatureRecord featureRecords[featureCount] 为特定特性 标签提供映射。featureRecordsfeatureTag升序排序,且任何特性标签 最多出现一次。排序时,标签值解释为 4 字节大端无符号整数,并按整数值排序。
EntryMapRecord entryMapRecords[variable] 为每个特性映射提供键(条目索引)。entryMapRecords 数组包含的条目数量, 等于 featureRecords数组中各entryMapCount字段之和,其中 entryMapRecords[0] 对应 featureRecords[0] 的第一个条目,entryMapRecords[featureRecord[0].entryMapCount] 对应 featureRecords[1] 的第一个条目,entryMapRecords[featureRecords[0].entryMapCount + featureRecord[1].entryMapCount]] 对应 featureRecords[2] 的第一个条目,依此类推。

FeatureRecord 编码:

类型 名称 描述
Tag featureTag 此映射所对应的特性 标签
uint8/uint16 firstNewEntryIndex 如果maxEntryIndex小于 256,则为 uint8,否则 为 uint16。 此记录映射到的第一个条目索引。
uint8/uint16 entryMapCount 如果maxEntryIndex小于 256,则为 uint8,否则 为 uint16。 与此特性关联的EntryMapRecord数量。

EntryMapRecord 编码:

类型 名称 描述
uint8/uint16 firstEntryIndex 如果maxEntryIndex小于 256,则为 uint8,否则 为 uint16。firstEntryIndex 和 lastEntryIndex 指定 一组字形映射 条目,这些条目构成此映射所创建条目的子集定义。
uint8/uint16 lastEntryIndex 如果maxEntryIndex小于 256,则为 uint8,否则 为 uint16。

条目映射记录与任何大于或等于 firstEntryIndex 且小于 或等于 lastEntryIndex 的条目索引匹配。

5.3.1.1. 解释 格式 1

此算法用于将格式 1 补丁映射转换为补丁映射条目列表。

解释格式 1 补丁映射

该算法的输入为:

该算法输出:

算法如下:

  1. 检查 patch map 数据是否完整且未被截断,format是否等于 1, 并且是否符合 § 5.3.1 补丁映射表:格式 1中的要求 (要求使用“必须”标记)。如果不符合,则返回错误。

  2. 对于entryIndex中的每个唯一 entry index

    • 如果 entry index 为 0,则这是一个特殊条目,用于标记已 存在于初始字体中的字形。跳过此 索引,不为其构建条目。

    • 如果 entry index 大于maxGlyphMapEntryIndex,则此 条目无效,跳过此 entry index

    • 如果appliedEntriesBitMapentry index 对应的位被设为 1,则跳过此 entry index

    • 收集映射到 entry index 的字形索引集合。

    • 使用 font subsetcmap表中的码位到 字形映射,将字形索引集合转换为 Unicode 码位集合。忽略任何未被 cmap映射的字形索引。 多个码位可以映射到同一字形 id。应包含与某个字形 关联的所有码位。

    • 通过以urlTemplateentry index 作为输入调用扩展 URL 模板,将 entry index 转换为 URL 字符串。 如果模板扩展产生错误,则返回 错误。

    • 如果 Unicode 码位集合为空,则跳过此 entry index

    • entry list 添加一个条目,其子集 定义仅包含 Unicode 码位 集合,并映射到生成的 URL 字符串、patchFormat指定的补丁格式,以及compatibilityId

  3. 如果featureMapOffset不为 null,则对于 featureRecordsentryMapRecords中的每个FeatureRecord及其 关联的EntryMapRecord

  4. 返回 entry list

注:虽然编码不要求包含 [0, maxEntryIndex] 中所有条目索引的条目,但 为获得最大紧凑性,建议这样做。

5.3.1.2. 从格式 1 中移除条目

此算法用于从格式 1 补丁映射中移除条目。此移除操作会修改 补丁映射的字节,但不会 改变字节数量。

从格式 1 补丁映射中移除条目

该算法的输入为:

算法如下:

  1. 检查 patch mapformat是否等于 1,并且是否符合 § 5.3.1 补丁映射表:格式 1中的要求。如果不符合, 则返回错误。

  2. 对于 patch mapentryIndex中的每个唯一 entry index

    • 如果appliedEntriesBitMapentry index 对应的位被设为 1,则跳过此 entry index

    • 通过以urlTemplateentry index 作为输入调用扩展 URL 模板,将 entry index 转换为 URL 字符串。 如果模板扩展产生错误,则返回 错误。

    • 如果生成的 URL 字符串等于 patch URL string,则将 appliedEntriesBitMapentry index 对应的位设为 1。

5.3.2. 补丁映射表:格式 2

格式 2 补丁映射 编码:

类型 名称 描述
uint8 format 设为 2,标识其为格式 2。
uint24 reserved 未使用,设为 0。
uint8 flags 用于指示可选字段是否存在的标志。 如果设置了位 0(最低有效位),则cffCharStringsOffset将 存在。 如果设置了位 1,则cff2CharStringsOffset将 存在。
uint32 compatibilityId[4] 用于标识与此字体兼容的补丁的唯一 ID(参见§ 4.1 补丁失效)。编码器选择该 值。编码器应将其设置为一个此前在 编码 IFT 字体时从未使用过的随机值。
uint8 defaultPatchFormat 指定由 urlTemplate 链接的补丁格式(除非被条目覆盖)。 必须设置为 § 6.1 格式 摘要表中的一个格式编号。
uint24 entryCount 此表中编码的条目数量。
Offset32 entries 指向映射 条目子表的偏移。偏移相对于此表的起始位置。
Offset32 entryIdStringData 指向包含所有条目 ID 字符串串接结果的数据块的偏移。 可以为 null(0)。偏移相对于此表的起始位置。
uint16 urlTemplateLength urlTemplate 字节数组的长度。
uint8 urlTemplate[urlTemplateLength] 包含URL 模板的字节数组,该模板用于生成 与每个条目关联的 URL 字符串。
uint32 cffCharStringsOffset 仅当flags的位 0(最低有效位)被设置时存在。 给出从 CFF 表起始位置到 CFF 表的 CharStrings INDEX 数据结构的偏移。
uint32 cff2CharStringsOffset 仅当flags的位 1 被设置时存在。 给出从 CFF2 表起始位置到 CFF2 表的 CharStrings INDEX 数据结构的偏移。

映射条目 编码:

类型 名称 描述
uint8 entries[variable] 包含entryCount映射条目编码字节的字节数组。每个条目具有可变 长度,其长度按照解释格式 2 补丁映射 条目确定。

映射条目编码:

类型 名称 描述
uint8 formatFlags 位字段。位 0(最低有效位)到位 5 表示是否存在可选 字段。如果设置了位 6,则忽略此条目。 位 7 保留供未来使用,并设为 0。
uint8 featureCount featureTags 列表中的特性标签数量。仅当formatFlags的位 0 被设置时存在。
Tag featureTags[featureCount] 条目字体子集定义中的特性 标签列表。仅当formatFlags的位 0 被设置时存在。
uint16 designSpaceCount 设计空间列表中的元素数量。仅当formatFlags 的位 0 被设置时存在。
设计 空间片段 designSpaceSegments[designSpaceCount] 条目字体子集 定义中的设计空间片段列表。仅当formatFlags的位 0 被设置时存在。
uint8 childEntryMatchModeAndCount 此字段和下一个字段用于向此 条目的交集检查添加条件,这些条件取决于之前的条目是否相交。 最高有效位用于表示匹配模式。如果设置该位,则条件 为合取模式,否则为析取模式。 剩余 7 位解释为无符号整数,表示 childEntryIndices 列表中的条目索引数量。仅当formatFlags的位 1 被设置时存在。
uint24 childEntryIndices[0b01111111 & childEntryMatchModeAndCount] 每个值都是entries数组中某个条目的索引,将检查该条目的交集 以确定此条目的交集。 只能引用在entries中位于此映射条目之前的条目。 仅当formatFlags的位 1 被设置时存在。
int24 entryIdDelta[variable] 用于计算此条目的 URL 字符串 ID 的有符号增量列表。如果存在, 则至少包含一个增量。如果某个增量的最低 有效位被设置,则后面还有一个增量。此过程会重复,直到遇到 最低有效位被清除的增量。每个增量对应的条目 id 等于上次计算的条目 id + 1 + floor(entryIdDelta / 2)。仅当formatFlags 的位 2 被设置且entryIdStringData为 null(0)时存在。 如果不存在且entryIdStringData为 null(0),则 此条目有一个 id,并假定 增量为 0。
uint24 entryIdStringLength[variable] 此条目的每个 id 字符串在entryIdStringData数据块中占用的字节数。 如果某个长度值的最高有效位被设置,则后面还有一个长度值。此过程会重复, 直到遇到最高有效位被清除的长度值。实际长度值存储在 每个值的最低 23 位中。仅当formatFlags 的位 2 被设置且entryIdStringData不为 null(0)时存在。如果 存在,则至少有一个长度值。如果不存在 且entryIdStringData不为 null(0), 则有一个 id 字符串,其值设为上一个条目的最后一个 id 字符串;对于第一个条目,则设为空字符串。
uint8 patchFormat 指定此条目链接到的补丁格式。使用§ 6.1 格式摘要表中的 ID 编号。 覆盖defaultPatchFormat。 仅当formatFlags的位 3 被设置时存在。
uint16/uint24 bias 添加到码位集合中所有码位值的偏置值。 如果格式位 4 为 0 且位 5 为 1,则此字段存在且为 uint16。 如果格式位 4 为 1 且位 5 为 1,则此字段存在且为 uint24。 否则不存在。
uint8 codePoints[variable] 此映射的码位集合。编码为稀疏位集合。 仅当formatFlags的位 4 和/或位 5 被设置时存在。长度 按照§ 5.3.2.3 稀疏位集合中的解码过程确定。

如果编码器生成的补丁将存储在文件系统中并随后提供服务,则建议 仅使用数字条目 ID (通过entryIdDelta),因为它们通常会产生最小的 格式 2 补丁映射编码。字符串 ID 适用于补丁未预先存储,并且可以使用 ID 字符串编码有关所请求补丁 的信息的场景。

设计空间 片段编码:

类型 名称 描述
Tag tag 轴标签值。
Fixed start 片段的起点(包含)。该值使用用户轴标度:OpenType 规范 § otvaroverview#coordinate-scales-and-normalization
Fixed end 片段的终点(包含)。必须大于或等于 start。该值使用用户轴标度:OpenType 规范 § otvaroverview#coordinate-scales-and-normalization
5.3.2.1. 解释 格式 2

此算法用于将格式 2 补丁映射转换为补丁映射条目列表。

解释格式 2 补丁映射

该算法的输入为:

该算法输出:

算法如下:

  1. 检查 patch mapformat是否等于 2,并且是否符合 § 5.3.2 补丁映射表:格式 2中的要求(要求 使用“必须”标记)。如果不符合,则返回错误。

  2. 如果entryIdStringData偏移为 0,则 将 last entry id 初始化为 0。否则将 其初始化为空字节字符串。将 current byte 设为 0,将 current id string byte 设为 0。

  3. entry listprior entry list 初始化为空列表。

  4. 调用解释格式 2 补丁映射 条目 entryCount 次。对于每次调用:

    • 传入 prior entry listpatch map 中从entries[current byte] 开始到 patch map 末尾的字节; 如果entryIdStringData非零,则传入 patch map 中从entryIdStringData[current id string byte] 开始 到 patch map 末尾的字节; 还传入 last entry iddefaultPatchFormaturlTemplate

    • last entry id 设置为返回的条目 id。

    • 将返回的已消耗字节数加到 current byte

    • 将返回的已消耗 id 字符串字节数加到 current id string byte

    • 将返回的条目添加到 prior entry list

    • 如果返回的 ignored 值为 false,则将返回条目的兼容性 ID 设置为compatibilityId,并将该条目添加到 entry list

  5. 返回 entry list

解释格式 2 补丁映射条目

该算法的输入为:

该算法输出:

算法如下:

  1. 对于所有步骤,每当从 entry bytes 加载数据时,就将读取的 字节数增加到 consumed bytes

  2. 设置 entry id = last entry idconsumed id string bytes = 0。

  3. entry 的补丁格式设置为 default patch format

  4. entry 添加一个字体子集定义,并将所有 集合初始化为空。

  5. entry bytes 读取formatFlags

  6. 如果formatFlags的位 0 被设置,则存在特性标签和 设计空间列表:

  7. 如果formatFlags的位 1 被设置,则存在复制索引 列表:

    • 如果childEntryMatchModeAndCount 的最高有效位被设置,则将 entry 的匹配模式设为合取模式, 否则设为析取模式。

    • entry bytes 读取由childEntryMatchModeAndCountchildEntryIndices指定的子条目索引列表。

    • 子条目索引引用此前加载的条目。0 是 prior entry list 中的第一个映射条目, 1 是第二个, 依此类推。对于childEntryIndices中的每个值, 在 prior entry list 中找到索引匹配的条目。 向 entry 添加对该条目的引用。如果某个childEntryIndices大于或 等于 prior entry list 的长度,则此编码无效,返回错误。

  8. 如果formatFlags的位 2 被设置,则存在 id 增量或 id 字符串长度:

    • 如果不存在 id string bytes,则:

      • 读取下一个entryIdDelta值。

      • entry id 设置为 entry id + 1 + floor(delta value / 2)。 如果 entry id 为负数或大于 4,294,967,295,则此编码 无效,返回错误。

      • 通过以 url templateentry id 作为输入调用扩展 URL 模板,将 entry id 转换为 URL 字符串。如果模板扩展 产生错误,则返回错误。

      • 将生成的补丁 URL 字符串添加到 entry

      • 如果读取的增量值的最低有效位被设置,则重复步骤 8。

    • 否则,如果存在 id string bytes,则:

      • 读取下一个entryIdStringLength值。

      • 将最低 23 位解释为无符号整数,并从 id string bytes 中读取相应数量的字节。 将 entry id 设置为结果。将读取的字节数增加到 consumed id string bytes

      • 通过以 url templateentry id 作为输入调用扩展 URL 模板,将 entry id 转换为 URL 字符串。如果模板扩展 产生错误,则返回错误。

      • 将生成的补丁 URL 字符串添加到 entry

      • 如果读取的长度值的最高有效位被设置,则重复步骤 8。

  9. 如果formatFlags的位 2 未被设置:

    • 如果不存在 id string bytes,则:

      • entry id 设置为 entry id + 1。

      • 通过以 url templateentry id 作为输入调用扩展 URL 模板,将 entry id 转换为 URL 字符串。如果模板扩展 产生错误,则返回错误。

      • 将生成的补丁 URL 字符串添加到 entry

    • 否则,如果存在 id string bytes,则:

      • 通过以 url templateentry id 作为输入调用扩展 URL 模板,将 entry id 转换为 URL 字符串。如果模板扩展 产生错误,则返回错误。

      • 将生成的补丁 URL 字符串添加到 entry

  10. 如果formatFlags的位 3 被设置,则存在补丁格式。 从 entry bytes 读取patchFormat指定的格式,并将 entry 的补丁格式设置为读取值。 如果patchFormat不是§ 6.1 格式摘要中的值之一,则此编码无效, 返回错误。

  11. 如果formatFlags的位 4 和位 5 中一个或两个被设置,则存在码位 列表:

    • 如果formatFlags的位 4 为 0 且位 5 为 1,则 从 entry bytes 中读取 2 字节(uint16)的bias值。

    • 如果formatFlags的位 4 为 1 且位 5 为 1,则 从 entry bytes 中读取 3 字节(uint24)的bias值。

    • 否则,bias 为 0。

    • 按照§ 5.3.2.3 稀疏位集合, 从 entry bytes 中读取带 bias 的稀疏位集合codePoints。 将得到的码位集合添加到 entry 中第一个字体子集 定义中。如果稀疏位集合解码 失败,则此编码无效,返回错误。

  12. 如果formatFlags的位 6 被设置,则将 ignored 设为 true。否则 ignored 为 false。

  13. 返回 entry identryconsumed bytesconsumed id string bytesignored

5.3.2.2. 从格式 2 中移除条目

此算法用于从格式 2 补丁映射中移除条目。此移除操作会修改 补丁映射的字节,但不会 改变字节数量。

从格式 2 补丁映射中移除条目

该算法的输入为:

此算法是解释格式 2 补丁映射的修改版本。 使用 patch map 作为输入调用解释格式 2 补丁映射,但进行以下更改:

5.3.2.3. 稀疏位 集合

稀疏位集合是一种紧凑存储一组不同无符号整数的数据结构。该集合 表示为一棵树,其中 每个节点具有固定数量的子节点,这些子节点递归地将一个区间细分为相等分区。高度为 H、 分支因子为 B 的树,可以存储区间 [0 到 BH-1](包含端点)中各整数是否属于集合。该树 被编码为字节数组以便传输。

格式 2 补丁映射的上下文中,稀疏位集合用于存储一组Unicode码位。因此, 存储在稀疏位集合中的整数 值仅限于 0 到 0x10FFFF 范围内的Unicode码位值。

稀疏位集合编码:

类型 名称 描述
uint8 header 位 0(最低有效位)和位 1 通过分支因子编码对树的分支因子 B 进行编码。位 2 到位 6 是一个 5 位无符号整数,用于编码 H 的值。位 7 设为 0,保留供 未来使用。
uint8 treeData[variable] 树的二进制编码。

treeData 的确切长度起初未知,其长度通过执行 解码算法确定。使用 分支因子 2 或 4 时,最后一个节点可能只使用一个字节中的部分位。在这种情况下,所有 剩余位都未使用并被 忽略。

分支因子 编码

位 1 位 0 分支因子(B) 最大高度(H)
0 0 2 31
0 1 4 16
1 0 8 11
1 1 32 7

编码高度(H)大于上表中相应编码 分支因子(B)最大高度的稀疏位集合 无效。

解码稀疏位集合 treeData

该算法的输入为:

该算法输出:

该算法使用一个 FIFO(先进先出)队列 Q

  1. treeData 中移除第一个字节。该字节为头部字节。按照稀疏位集合确定树高度 H 和 分支因子 B

  2. 如果 H 大于分支因子编码 表中 B 所在行的“最大高度”, 则编码无效,返回错误。

  3. 如果 H 等于 0,则这是空集合,并且不会继续消耗 treeData 的任何字节。 返回空集合。

  4. 将元组 (0, 1) 插入 Q

  5. S 初始化为空集合。

  6. treeData 被解释为位字符串,其中 treeData[0] 的最低有效位是 字符串中的第一个位,treeData[0] 的最高有效位是第 8 位,依此类推。

  7. 如果在以下步骤中添加到 S 的值大于最大 Unicode 码位值(0x10FFFF),则 忽略该值,不将其添加到 S

  8. 如果 Q 为空,则返回 S

  9. Q 中取出下一个元组 tt 中的第一个值 是 start,第二个值是 depth

  10. treeData 位字符串中移除接下来的 B 位。第一个移除的位是 v1, 第二个是 v2,依此类推,最后一个移除的位是 vB。如果移除前 treeData 中剩余位少于 B,则 treeData 格式错误, 返回错误。

  11. 如果 v1vB 的所有位都为 0,则将 区间 [start + bias, start + bias + BH - depth + 1) 中的所有整数插入 S。转到步骤 5。

  12. 对于 v1vB 中每个等于 1 的 vi: 如果 depth 等于 H,则将 整数 start + bias + i - 1 添加到 S。否则,将元组 (start + (i - 1) * BH - depth, depth + 1) 插入 Q

  13. 转到步骤 8。

注:编码稀疏位集合时,编码器可以使用 任何可用分支因子,但建议 使用 4,因为已有研究表明, 对于通常遇到的大多数 Unicode 码位集合,它可以产生最小的编码。

在分支因子为 8 的树中,集合 {2, 33, 323} 可编码为以下位字符串:
位字符串:
|-- 头部 --|- 0 级 |---- 1 级 ----|------- 2 级 -----------|
| B=8 H=3  |   n0   |   n1       n2   |   n3       n4       n5    |
[ 01  11000 0 10000100 10001000 10000000 00100000 01000000 00010000 ]

随后变为以下字节字符串:
[
  0b00001110,
  0b00100001,
  0b00010001,
  0b00000001,
  0b00000100,
  0b00000010,
  0b00001000
]
在分支因子为 2 的树中,空集合 编码为以下位字符串:
位字符串:
|-- 头部 -- |
| B=2 H=0   |
[ 00  00000 0 ]

随后变为以下字节字符串:
[
  0b00000000,
]
集合 {0, 1, 2, ..., 17} 可使用 分支因子 4 编码如下:
位字符串:
|-- 头部 --| 0 级 |- 1 级 -| 2 级 |
| B=4 H=3  | n0 | n1 | n2 | n3  |
[ 10  11000 0 1100 0000 1000 1100 ]

字节字符串:
[
  0b00001101,
  0b00000011,
  0b00110001
]

5.3.3. URL 模板

URL 模板用于压缩与每个补丁映射条目关联的 URL 字符串。每个补丁映射 表 提供一个 URL 模板,随后通过使用每个条目的 ID 作为输入扩展该公共模板, 生成每个条目的 URL 字符串。 模板扩展的输出是使用 [UTF-8] 编码的字符串。

URL 模板编码为一系列单字节操作码,这些操作码可插入字面值, 或根据输入条目 ID 插入值。

下面给出解码和扩展 URL 模板的算法。

扩展 URL 模板

该算法的输入为:

该算法输出:

算法如下:

  1. URL string 初始化为空字节数组。在其余步骤中,将 url template bytes 用作 队列。 读取操作从队列前端移除字节(从索引 0 处的字节开始)。

  2. url template bytes 的下一个字节读取到 op code 中。如果没有 剩余字节,则 扩展完成,返回 URL string

  3. 如果 op code 的最高有效位未被设置,则:

    • op code 的最低 7 位解释为无符号整数 number of literals

    • 如果 number of literals 为 0,则返回错误。

    • url template bytes 中读取接下来的 number of literals 个字节到 literal bytes。 如果 url template bytes 中剩余字节不足,则返回 错误。

    • 按照 UTF-8,ISO 10646 的一种转换格式 § section-4,检查 literal bytes 是否为有效的 UTF-8 编码字符串。 如果不是,则返回错误。

    • literal bytes 追加到 URL string 末尾。

    • 转到步骤 2。

  4. 如果 op code 的最高有效位被设置,则:

    • 在后面的操作码参考表中查找 op code。执行 “插入操作”列中指定的操作。如果表中找不到 op code,则 返回错误。

    • 转到步骤 2。

操作码参考表

名称 操作码值 插入操作
插入 id32 128 (0b10000000) 将后面的扩展变量表中 id32 变量的值追加到 URL string
插入 d1 129 (0b10000001) 将后面的扩展变量表中 d1 变量的值追加到 URL string
插入 d2 130 (0b10000010) 将后面的扩展变量表中 d2 变量的值追加到 URL string
插入 d3 131 (0b10000011) 将后面的扩展变量表中 d3 变量的值追加到 URL string
插入 d4 132 (0b10000100) 将后面的扩展变量表中 d4 变量的值追加到 URL string
插入 id64 133 (0b10000101) 将后面的扩展变量表中 id64 变量的值追加到 URL string

扩展变量

变量
id32 输入的 entry ID 编码为base32hex字符串(使用 数字 0-9、A-V),并省略填充。entry ID 是无符号整数时,必须先将其转换为大端 32 位 无符号整数, 但随后要在编码前移除所有等于 0 的前导字节。(例如,当 整数小于 256 时,只编码一个字节。)如果 entry ID 为 0,则编码一个零 字节。当 entry ID 为字符串时,原始字节使用 base32hex 编码。
d1 id32 变量中字符串的最后一个字符。 如果 id32 变量为空,则值为字符 _(U+005F)。
d2 id32 变量中字符串的倒数第二个字符。 如果 id32 变量少于 2 个字符,则值为字符 _ (U+005F)。
d3 id32 变量中字符串的倒数第三个字符。 如果 id32 变量少于 3 个字符,则值为字符 _ (U+005F)。
d4 id32 变量中字符串的倒数第四个字符。 如果 id32 变量少于 4 个字符,则值为字符 _ (U+005F)。
id64 输入的 entry ID 编码为base64url字符串(使用 字符 A-Z、a-z、0-9、- (减号)和 _(下划线)),并包含填充。由于填充字符为 '=',必须 将其 URL 编码为 '%3D'。entry ID 是无符号整数时,必须 先将其转换为大 端 32 位无符号整数,但随后要在编码前移除所有等于 0 的前导 字节。(例如,当整数小于 256 时,只编码一个字节。) 如果 entry ID 为 0,则编码一个 零字节。当 entry ID 为字符串时,其原始字节编码为base64url

以下是一些示例输入及其对应的扩展结果。“模板字节”列中的值使用 C 风格字节数组表示。

模板字节 输入 ID ID 类型 扩展结果
[16, 'h', 't', 't', 'p', 's', ':', '/', '/', 'f', 'o', 'o', '.', 'b', 'a', 'r', '/', 128] 123 整数 https://foo.bar/FC
[8, 'f', 'o', 'o', '?', 'b', 'a', 'r', '=', 128] 123 整数 foo?bar=FC
[10, '/', '/', 'f', 'o', 'o', '.', 'b', 'a', 'r', '/', 128] 0 整数 //foo.bar/00
[5, '/', 'f', 'o', 'o', '/', 129, 1, '/', 130, 1, '/', 128] 478 整数 /foo/0/F/07F0
[5, '/', 'f', 'o', 'o', '/', 129, 1, '/', 130, 1, '/', 131, 1, '/', 128] 123 整数 /foo/C/F/_/FC
[4, 'f', 'o', 'o', '/', 129, 1, '/', 130, 1, '/', 131, 1, '/', 128] ['b', 'a', 'z'] 字符串 foo/K/N/G/C9GNK
[4, 'f', 'o', 'o', '/', 129, 1, '/', 130, 1, '/', 131, 1, '/', 128] ['z'] 字符串 foo/8/F/_/F8
[10, '/', '/', 'f', 'o', 'o', '.', 'b', 'a', 'r', '/', 133] 14,000,000 整数 //foo.bar/1Z-A
[10, '/', '/', 'f', 'o', 'o', '.', 'b', 'a', 'r', '/', 133] 0 整数 //foo.bar/AA%3D%3D
[10, '/', '/', 'f', 'o', 'o', '.', 'b', 'a', 'r', '/', 133] 17,000,000 整数 //foo.bar/AQNmQA%3D%3D
[10, '/', '/', 'f', 'o', 'o', '.', 'b', 'a', 'r', '/', 133] [0xc3, 0xa0, 0x62, 0x63] 字符串 //foo.bar/w6BiYw%3D%3D

在以下示例中,扩展预计会失败并返回错误:

模板字节 输入 ID ID 类型 失败原因
[4, 'f', 'o', 'o', '/', 150] 123 整数 操作码 150 无效
[4, 'f', 'o', 'o', '/', 0, 128] 123 整数 操作码 0 无效
[10, 'f', 'o', 'o', '/', 128] 123 整数 模板中没有足够字节供字面值复制操作码 10 使用。
[4, 'f', 'o', 'o', 0x85, 128] 123 整数 字面值字节不是有效的 UTF-8。

6. 字体补丁格式

在增量字体传输中,通过应用补丁扩展字体 子集。 本规范定义两种补丁格式,每种格式分别适用于一组不同的扩充场景。单个 编码可以使用多种补丁格式。

6.1. 格式摘要

本规范定义以下补丁格式:

以下各节中给出了每种算法的更详细描述。

以下格式编号用于在§ 5.3 补丁映射表中标识补丁格式和失效模式:

格式编号 名称 失效模式
1 § 6.2 以表为键 完全 失效
2 § 6.2 以表为键 部分 失效
3 § 6.3 以字形为键 不失效

6.2. 以表为键

以表为键的补丁包含一组应用于输入字体文件中各个字体表 的补丁。每个表补丁使用brotli 压缩进行编码,并使用输入字体文件中的对应表作为共享 LZ77 字典。以表为键的编码补丁由一个短头部及其后的 一个或多个 brotli 编码补丁组成。除了修补表之外,补丁还可以替换(不使用现有 表数据) 或移除字体 子集中的表。

以表为键的补丁 编码:

类型 名称 描述
Tag format 标识 格式为以表为键,必须设为 'iftk'
uint32 reserved 保留供未来使用,设为 0。
uint32 compatibilityId[4] 可应用此补丁的字体 子集的 id。参见§ 4.1 补丁失效
uint16 patchesCount patches 数组中的条目数量。
Offset32 patches[patchesCount+1] 每个条目都是从此表起始位置到某个TablePatch的偏移。偏移必须 按升序排序。

patches数组中两个连续偏移之差,给出 对应TablePatch的 大小。

TablePatch 编码:

类型 名称 描述
Tag tag 标识此补丁所应用的字体 表的标签。
uint8 flags 位字段。如果设置位 0(最低有效位),则此补丁替换现有表。如果 设置位 1,则移除此表。
uint32 maxUncompressedLength brotliStream的最大未压缩长度。
uint8 brotliStream[variable] Brotli 编码的字节流。

6.2.1. 应用 以表为键的补丁

补丁应用算法用于应用以表为键的 补丁,以扩展字体子集, 使其覆盖更多码位、 特性和/或设计变体空间。

应用以表为键的补丁

该算法的输入为:

该算法输出:

算法如下:

  1. extended font subset 初始化为不含任何表的空字体。

  2. 检查 patch 是否符合§ 6.2 以表为键中的要求(要求使用 “必须”标记),并检查所有TablePatch 是否都包含在 patch 内。否则返回错误。

  3. 检查 patch 中的compatibilityId字段是否 等于 compatibility id。 如果不匹配,或 base font subset 没有 'IFT ' 或 'IFTX' 表, 则补丁应用 失败,返回错误。

  4. 在以下步骤中,向 extended font subset 添加表,是指将 表的数据添加到字体中,并 按照 OpenType 规范的要求,向表 目录插入一个新条目。该条目包含表数据的校验和。复制未修改的现有表时,客户端 可以重新使用源字体条目中的校验和。否则需要 计算新的校验和。

  5. 对于patches中的每个条目(索引为 i):

  6. 对于 base font subset 中每个标签未在步骤 5 处理的任何 条目中出现的, 将该表的副本添加到 extended font subset

6.3. 以字形为键

以字形为键的补丁包含一组数据块,每个数据块都与一个字形索引和一个字体表关联。 编码数据会替换所引用中 该字形索引的任何现有数据。以字形为键的补丁可以对glyf/locagvarCFFCFF2 表的数据进行编码。

以字形为键的补丁 编码:

类型 名称 描述
Tag format 标识格式为以字形为键, 必须设为 'ifgk'
uint32 reserved 保留供未来使用,设为 0。
uint8 flags 位字段。如果设置了位 0(最低有效位),则glyphIds使用 uint24, 否则使用 uint16。
uint32 compatibilityId[4] 可应用此补丁的字体子集的兼容性 id。参见§ 4.1 补丁失效
uint32 maxUncompressedLength brotliStream的最大未压缩长度。
uint8 brotliStream[variable] Brotli 编码的GlyphPatches表。

GlyphPatches 编码:

类型 名称 描述
uint32 glyphCount 补丁中编码的字形数量。
uint8 tableCount 补丁包含数据的数量。
uint16/uint24 glyphIds[glyphCount] 补丁中包含的字形索引数组。如果flags的位 0(最低 有效位)被设置,则元素为 uint24,否则元素为 uint16。 必须按 升序排列,并且不得 包含任何重复值。
Tag tables[tableCount] 补丁中包含的 (按标签)的数组。必须按升序 排列,并且不得包含任何重复值。排序时,标签值 解释为 4 字节大端 无符号整数,并按整数值排序。
Offset32 glyphDataOffsets[glyphCount * tableCount + 1] 每个表中各字形数据偏移的数组。前glyphCount个偏移 对应tables[0],接下来的glyphCount个偏移 (如果存在)对应tables[1],依此类推。所有偏移都相对于 GlyphPatches 表的起始位置。偏移必须按 升序排序。
uint8 glyphData[variable] 由偏移选出的实际字形数据。

glyphDataOffsets 数组中两个连续偏移之差,给出 对应字形数据的大小。

6.3.1. 应用 以字形为键的补丁

补丁应用算法用于应用以字形为键的 补丁,以扩展字体子集, 使其覆盖更多码位、 特性和/或设计变体空间。

应用以字形为键的补丁

该算法的输入为:

该算法输出:

算法如下:

  1. 检查 patch 是否符合§ 6.3 以字形为键中的要求(要求使用 “必须”标记)。否则返回错误。

  2. 检查 patch 中的compatibilityId字段是否 等于 compatibility id。 如果不匹配,或 base font subset 没有 'IFT ' 或 'IFTX' 表, 则补丁应用 失败,返回错误。

  3. 按照Brotli 压缩数据 格式 § 10 解码算法,解码brotliStream中的 brotli 编码数据。解码后的 数据是一个GlyphPatches表。如果解码后的数据大于maxUncompressedLength,则返回 错误。

  4. 对于tables中列出的每个字体 表(索引为 i):

    • 使用 base font subset 中的对应表合成 一个新表,其中每个字形的数据,如果glyphData中存在该字形索引对应的数据, 就用该数据替换;否则,从 base font subset 中的对应表复制 该字形索引的数据。

    • 通过寻找等于该字形索引的glyphIds[j], 定位某个字形索引的补丁字形数据。关联字形数据的 偏移为glyphDataOffsets[i * glyphCount + j]。 关联字形数据的长度 等于glyphDataOffsets[i * glyphCount + j + 1] 减去glyphDataOffsets[i * glyphCount + j]

    • 合成新表的具体过程取决于指定的 格式。 任何与字形无关的数据都应从 base font subset 中的表复制。支持 glyfgvarCFFCFF2 类型的表。 必须忽略 任何其他类型表的条目。更新glyf时, 还必须更新loca 表。此步骤不得导致修改字体中的任何其他表。 特别是,这意味着补丁不能添加索引超出maxp中所指定 numGlyphs 的字形。

      CFFCFF2gvar 对每个字形数据使用 可变大小的偏移。如果 合成新表时有必要,可以按需增加偏移大小。对于glyf/loca,偏移 大小不能更改,因为偏移大小在head表中指定。 如果合成期间生成的偏移 超过最大可能偏移值(在可能的情况下增加偏移大小之后),则补丁应用 失败。返回错误。

    • 如果 base font subset 没有匹配的表,则返回错误。

    • 将合成的表插入 extended font subset

  5. 找到与 compatibility id 具有相同compatibilityId§ 5.3 补丁映射表。 如果它是 格式 1 补丁映射,则以补丁映射表和 patch URL string 作为 输入,调用从格式 1 补丁映射中移除条目。否则,如果它是格式 2 补丁映射,则以补丁映射表和 patch URL string 作为输入,调用从格式 2 补丁映射中移除条目。将修改后的补丁映射表复制到 extended font subset 中。

  6. 对于 base font subset 中每个标签未在步骤 4 或 5 处理的任何 条目中出现的, 将该表的副本添加到 extended font subset

  7. 如果在前述步骤中修改了任何字体 表的内容,则 对每个修改后的表:更新字体 表目录中的校验和,使其与表的 新内容匹配。

7. 编码

编码器是一种生成增量字体及一组关联补丁的工具。“编码”是指使用编码器的 过程,包括编码器要求或允许影响特定 情况下结果的任何参数。 合规编码器生成的增量字体 和关联补丁:

  1. 必须满足 § 5 字体格式扩展§ 6 字体补丁格式中的所有要求。

  2. 必须保持一致,即: 对于任何可能的字体子集定义,使用该子集定义和增量字体调用扩展增量字体 子集所得的结果必须始终相同, 无论在扩展增量字体 子集的步骤 8 中选择了何种具体的补丁选择顺序。

  3. 必须 遵守补丁失效条件。作为 IFT 编码一部分的任何补丁, 在应用到兼容的字体子集时,只能对补丁映射兼容性 ID 进行符合§ 4.1 补丁 失效条件的更改, 这些条件由关联补丁映射条目所声明的失效模式决定。

  4. 当编码器用于将现有字体转换为增量字体时,关联的完全扩展字体应与现有字体 等效。等效的完全扩展字体 应具有与现有字体相同的所有 (不包括增量 IFT/IFTX 表),且每个表都应在功能上等效于现有字体中的对应表。注:完全 扩展后的字体不一定始终与现有字体的二进制内容完全匹配。

  5. 应在整个扩充过程中保留完全扩展字体的功能,即: 给定从增量 字体派生的完全扩展字体以及任意内容,则使用增量字体和覆盖该内容的最小子集定义调用扩展增量字体 子集生成的字体子集,对于该内容应与完全扩展字体的 渲染结果相同。

当编码器用于将现有字体文件转换为增量字体,并且客户端按照 本文档其他各节实现时,IFT 规范的目标是使字体在客户端中的外观和行为 与整个文件都传输到客户端时相同。IFT 规范的主要目标之一,是使 IFT 格式和协议可以像 WOFF2 一样,充当字体传输的中立媒介。如果编码器从源字体生成 满足上述全部要求(1 到 5)的编码,则该编码将保留原始字体的全部 功能。上面的要求 4 确保原始字体中的所有功能都可达。该要求与要求 5 配合使用,后者要求 IFT 字体的部分版本对于属于生成该部分字体所用子集定义的内容, 具有与完整版本(此处为原始字体)等效的功能。

这对于字体铸造厂或字体的其他权利所有者可能很重要,他们希望确信 使用 IFT 对该 字体进行编码和传输不会改变其行为,从而不会改变字体创作者的意图。许可证或 合同随后可能包含 有关 IFT 一致性的要求;而在某些情况下,由于 WOFF2 的内容中立性, 将字体重新编码为 WOFF2 格式事实上是允许的,这些情况也可能允许对该字体进行 IFT 编码。

但是,这些编码一致性要求并不意味着排除或弃用 不保留源字体全部功能的 编码的可能性及其实际用途。任何满足最低 要求(上述 1、2 和 3)的编码都是有效的,并可能有适当用途。在某些情况下,可能希望编码后的字体 在其所有补丁文件中省略对某些 功能/数据的支持,即使原始字体文件包含这些内容。在 其他情况下,字体可能直接从字体创作源文件编码为 IFT 格式。如果编码器选择不 满足上述要求 4, 仍强烈建议满足要求 5,因为它可以确保字体在整个 扩充过程中的行为保持一致。

7.1. 编码注意事项

本节为非规范性内容。

编码过程的细节可能因编码器而异,超出本文档的范围。但是, 本节提供 编码器实现可能需要考虑的指导,并且在生成现有字体文件的增量版本时,这些指导对于重现 该字体文件的外观和行为可能很重要。本节提供的指导 基于在制定本规范期间构建编码器实现的经验。 它代表了截至本文撰写时,对如何生成高性能编码的最佳理解, 该编码满足 § 7 编码的要求 1 到 4,因而保留被编码原始字体的所有 功能/行为。

关于§ 6.2 以表为键补丁

§ 6.2 以表为键补丁可以更改某些字体表的内容,而不 更改其他表。每个被修补的表通常需要 相对于特定表内容,但其他表可以具有不同内容。因此,只要 § 6.2 以表为键补丁不改变包含字形数据的表,它就可以 与§ 6.3 以字形为键补丁兼容,因此仅为部分 失效(即它会使其他§ 6.2 以表为键补丁失效, 但不会使§ 6.3 以字形为键补丁失效)。此外,如果两组§ 6.2 以表为键补丁不 修改任何相同的表,则它们可以彼此独立。例如,可以将§ 6.2 以表为键 补丁用于字形表之外的所有 内容,然后为这些表使用另一组§ 6.2 以表为键 补丁,而不是§ 6.3 以字形为键补丁;理论上,这两组都可以是部分失效的——使它们 各自内部相互依赖,但彼此独立。

应用§ 6.2 以表为键补丁通常会修改列出它的 IFT 或 IFTX 表,以添加一组新的 补丁,进一步扩展字体。这意味着全部§ 6.2 以表为键补丁形成一张图, 其中分段中的每个字体子集都是节点,每个补丁都是边。这也意味着此类 补丁通常会串行下载和应用,这对该补丁 类型相对于延迟的性能有影响。

关于§ 6.3 以字形为键补丁

§ 6.3 以字形为键补丁与其他补丁类型有很大不同。第一, § 6.3 以字形为键补丁只能修改 包含字形轮廓数据的表,因此仅使用增量字体中的§ 6.3 以字形 为键补丁时,必须在初始字体文件中包含 所有其他字体表数据。第二,§ 6.3 以字形为键补丁不会使其他补丁 失效, 因此可以独立下载和应用。这种独立性意味着可以并行 下载多个补丁, 与失效补丁类型相比,可显著减少所需的往返次数。

为编码选择补丁格式

所有编码都必须选择一种或多种补丁类型。§ 6.2 以表为键补丁 允许修补字体中的 所有类型数据,但由于此类型至少是部分失效的, 所需补丁总数会随片段数量呈指数增长,而不是线性增长。 § 6.3 以字形为键补丁 只能更新轮廓和变体增量数据,但所需数量随片段数量线性增长。

除了补丁数量之外,编码器还应考虑获取典型内容所需补丁时 需要的网络往返次数。对于失效补丁类型,必须串行发出补丁请求。 这意味着,如果某些 内容需要多个片段,则可能需要多次网络往返。另一方面,以字形为键的补丁不会 使其他补丁失效,并且可以并行获取,只需一次往返。

在这两种类型的极端情况下,§ 6.2 以表为键补丁最适合 非轮廓数据量很大但只需要 少量补丁的字体。§ 6.3 以字形为键补丁最适合 绝大多数数据由字形 轮廓组成的字体,许多现有 CJK 字体都属于这种情况。

对于介于二者之间的字体,或希望对字形数据进行细粒度分段、但 其他表中的数据 仍需分段的情况,可以采用以下方式混合§ 6.2 以表为键§ 6.3 以字形为键补丁类型:

  1. 将所有以表为键的补丁条目保留在一个映射表中,将所有以字形为键的条目保留在另一个 映射表中。

  2. 使用以表为键的补丁更新除以字形为键的补丁会涉及的表之外的所有表 (轮廓、 变体增量和以字形为键的补丁映射表)。这些补丁应使用少量 大片段,以保持 补丁数量合理。

  3. 由于以字形为键的补丁引用被更新的特定字形 ID,以表为键的 补丁不得更改 原始字体中使用的字形到字形 ID 的分配;否则,以字形为键的补丁中列出的字形 ID 可能会 变得不正确。在字体子集化工具中,这通常作为“保留字形 ID”选项提供。

  4. 最后,使用以字形为键的补丁更新剩余的表;此时可使用更小、更细粒度的 片段,而不会 需要过多补丁。

预计混合补丁类型编码将是一种常用方法,因为许多字体都会位于这两个 极端之间。

使用失效补丁减少往返次数

对于使用某种失效补丁类型的分段,减少所需往返次数的一种方法,是提供 可一次添加多个片段的 补丁(除单片段补丁之外)。例如,考虑一个具有 4 个片段的字体: A、B、C 和 D。补丁表可以列出添加以下内容的补丁:A、B、C、D、A + B、A + C、A + D、B + C、B + D 和 C + D。这样 可以在一次往返中添加任意两个片段。该方法的缺点是会进一步 增加所需唯一 补丁的数量。

编码补丁映射中的条目顺序

§ 4.4 选择失效补丁中,客户端使用补丁映射中 条目的顺序,在选择加载和应用哪个补丁时打破平局。 客户端的目标是减少总传输大小,因此当多个条目的交集大小相同时, 通常选择总传输大小最小的补丁最符合客户端利益。因此,编码器应按字节大小 从小到大排列补丁映射中的条目。这样可以确保客户端遵循§ 4.4 选择失效补丁时,偏向较小补丁并最大化性能。

管理补丁数量

§ 6.2 以表为键补丁与大量片段结合使用,可能导致 需要的补丁数量非常庞大,这可能产生两个 负面影响。第一,存储所有预生成补丁所需的空间可能大得不可接受。 第二,更多 补丁通常意味着更低的 CDN 缓存性能,因为更多补丁代表 从给定子集到给定子集的更多路径,而不同用户会根据其访问的 内容采用不同路径。 可以使用一些技术减少预生成补丁总数:

  1. 为补丁图设置最大深度;达到该限制后,修补字体以添加完整 原始字体的所有剩余部分。这会导致在添加一定数量的 片段后加载整个剩余字体。限制 图的深度可减少较深层级的补丁组合爆炸。

  2. 或者,在较深层级,编码器可以开始将多个片段合并为单个 补丁,以减少每一层的分支数。

选择分段方式

编码器需要做出的最重要、最复杂的决定之一,是如何对 编码字体中的数据进行分段。上面的讨论 主要关注片段数量,但增量字体的性能更多取决于 片段内数据的 分组方式。为最大化效率,编码器需要将通常一起使用的数据(例如码位) 分组到同一 片段中。这样可减少客户端扩展字体时加载的不必要数据量。编码器 还必须确定片段大小。较小片段会产生更多补丁,从而因需要更多 网络请求而带来更多开销,但与较大片段相比, 通常每个片段中包含的不必要数据更少。对码位进行分段时,码位使用 频率数据有助于指导分段。

某些码位可能具有显而易见的分段方式,或者至少对于应将哪些 码位分组在一起几乎没有疑问。 例如,拉丁字母表中的大写和小写字母构成一个自然分组。其他情况可能更加 复杂。例如, 中文、日文和韩文共享一些码位,但在日文中高频的码位 可能在中文中频率较低。 在某些情况下,可以选择针对单一语言优化编码。另一种方法是生成折中 编码。例如,在分段时,编码器可以把日文、 中文和韩文中都高频的码位放入一个 片段,然后把仅在日文和中文中高频的码位放入另一个片段,依此类推。之后, 仅在一种语言中高频的码位可以按通常方式处理。这样会导致片段 大小不那么均匀,但意味着 为任一种语言加载高频补丁时,不会同时加载低频字形。

包含默认布局特性

附录 A:默认特性标签汇总了一组通常默认使用的布局特性。 由于此列表中的特性 通常始终会被塑形器使用,因此为获得最佳性能,编码器通常不应在字体编码中 将这些特性设为可选。

保持功能等效

§ 7 编码中所述,编码器应保留 原始字体的功能。字体非常复杂, 并且码位之间经常存在交互,因此使用字体的部分副本保持功能等效可能很棘手。 接下来的两个小节讨论如何使用不同补丁类型保持功能等效。

以表为键的补丁

在准备§ 6.2 以表为键补丁时,实现功能 等效的一种方法,是利用 现有字体子集化工具实现生成保留原始字体功能的字体子集。随后可从这些子集 派生 IFT 补丁。

字体子集化工具根据所需的字体子集定义,从输入字体生成字体 子集。可靠地 对字体进行子集化的实践已得到充分理解,并存在多个开源实现(完整的形式化描述 超出本文档范围)。它通常涉及可达性分析,其中相对于字体子集定义检查表中 的数据, 以确定哪些部分可由子集定义覆盖的任何可能内容访问。 任何可达数据都会保留在生成的字体子集中,而任何不可达数据都可以移除。

在以下示例伪代码中,字体子集化工具用于生成仅使用 以表为键补丁的 IFT 编码字体:

# 将字体(full_font)编码为一个从 base_subset_def 开始的增量字体,
# 并且可以增量添加 subset_definitions 中的任意定义。返回 IFT 编码字体
# 以及一组关联补丁。
encode_as_ift(full_font, base_subset_def, subset_definitions):
  base_font = subset(full_font, base_subset_def)
  base_font, patches  = encode_node(full_font, base_font, base_subset_def, subset_definitions)
  return base_font, patches

# 更新 base_font,添加所有 IFT 补丁映射,以到达
# subset_definitions 中的任意定义,并生成关联补丁。
encode_node(full_font, base_font, cur_def, subset_definitions):
  patches = []
  next_fonts = []
  
  for each subset_def in subset_definitions not fully covered by cur_def:
    next_def = subset_def union cur_def
    next_font = subset(full_font, next_def)
    let patch_url be a new unique url

    add a mapping from, (subset_def - cur_def) to patch_url, into base_font
    next_font, patches += encode_node(full_font, next_font, next_def, subset_definitions)

    next_fonts += (next_font, next_def, patch_url)

  for each (next_font, next_def, patch_url) in next_fonts:
    patch = table_keyed_patch_diff(base_font, next_font)
    patches += (patch, patch_url)
  
  return base_font, patches

在此示例实现中,如果输入的基础子集定义与子集 定义列表的并集完全覆盖输入的 完整字体,并且所使用的子集化工具实现正确保留所有功能,则上述 实现应满足 § 7 编码中作为中立编码的要求。此基本编码器 实现仅用于演示,不代表 所有可能的编码器实现。特别是,它没有使用或 展示以字形为键补丁的用法。大多数编码器可能会更复杂,并需要考虑更多因素,其中一些将在 后续 各节中讨论。

§ 6.3 以字形为键补丁

正因为它们以码位和特性标签为参数,但又可以彼此独立 应用,§ 6.3 以字形为键补丁有额外要求,不能 直接通过使用子集化工具实现来派生。但是, 这类实现有助于澄清编码器在生成这种补丁时,为保持功能等效需要执行什么操作。 考虑相对于给定字体子集定义生成字体子集的结果。我们可以 将该字体 子集定义字形闭包 定义为子集中包含的完整字形集合,子集化工具已确定这些字形 是渲染所描述码位和布局特性的任意组合所必需的。

使用该定义,完整§ 6.3 以字形为键补丁集合的字形闭包要求为:

假设子集化工具准确完成其工作,则字形闭包要求是等效 行为要求的结果:假设存在一个字体子集定义,子集化工具在其子集中包含字形 *i*,但生成§ 6.3 以字形为键补丁的编码器从与该定义对应的补丁集合中 省略了字形 *i*。如果子集化工具是正确的, 则渲染该定义中码位和特性的某种组合时,必须存在该字形才能保持等效行为, 这意味着增量字体在渲染该 组合时不会具有等效行为。

因此,在生成使用以字形为键补丁的编码时,编码器必须确定如何 在所有补丁之间分配字形, 以满足字形闭包要求。这主要涉及查看分配给 某个片段的码位,并确定该片段对应的补丁中还必须包含哪些其他字形,例如当 某个字形变体可 替换片段中按码位包含的字形时。在某些情况下,只有加载多个片段时才需要某个字形, 此时该字形可以添加到这些片段中任意一个所对应的补丁。 (连字或预组合重音字符可能属于这种情况。)最后,在完成初始片段分析后,同一个 字形可能在 加载两个或多个片段的补丁时都需要。处理这种情况主要有五种策略:

  1. 可以将两个或多个片段合并到单个补丁中。这样可避免重复 公共字形,但会 增大片段大小。

  2. 可以将公共字形放入单独的补丁,然后设置映射条目,使加载任何 需要该公共补丁的片段时 同时触发加载该公共补丁。例如,如果 'c' 是片段 'a' 和 'b' 都需要的公共片段,则可以 通过格式 2 映射表设置以下映射条目:

    • 子集定义 a → 片段 a

    • 子集定义 b → 片段 b

    • 子集定义 a 与子集定义 b 的并集 → 片段 c

  3. 在某些情况下,例如使用Unicode 变体选择符时,会有一个 修饰符码位,在与许多其他码位配对时触发字形替换。 由于替代 字形数量庞大,最好将它们保留在独立补丁中,仅当修饰符 码位与相应基础 码位同时存在时才加载。可以通过使用格式 2 补丁映射,并通过childEntryIndices实现多条目匹配。 有关设置方法的示例,请参阅示例 2:带子条目的以字形为键的补丁

  4. 或者,可以将该字形包含在两个或多个对应片段的补丁中, 代价是将字形数据复制到 多个补丁。

  5. 最后,可以将公共字形移入初始字体。这样可避免增加片段大小和 重复字形数据, 但会增大初始字体大小。它还意味着无论是否需要,字形数据都会始终 加载。这 对许多片段都需要或以其他方式使分段复杂化的字形很有用。

在初始字体中预加载数据

在某些情况下,可能希望避免对初始文件应用补丁的开销。例如, 可以 希望在公司主页上加载的字体已经能够渲染该页面上的内容。 这类文件的主要优点 是降低渲染延迟:内容可以在一次往返后渲染。

在下载的字体文件中包含数据有两种方法。一种是直接将增量字体整体编码, 使数据位于初始文件中。任何此类数据都将始终可用于字体的任何补丁版本。 当相同数据原本需要出现在多个不同片段中时,这种方法可能有用。

另一种方法是下载已应用补丁的字体。也就是说,可以编码一个“基础” 文件,其中只有少量或没有数据, 但随后在服务器端将补丁应用到该文件,使下载的文件已经包含 这些补丁中的数据。

只需要一个预加载版本的字体时,这些策略的结果大致相同, 但第一种方法既 更简单,在某些情况下也更具体。但是,需要多个预加载字体时, 预修补方法通常 更好。使用第一种方法时,需要生成多个编码,每个预加载文件一个。使用 第二种方法时,所有预加载文件仍共享相同的整体补丁图,这既 减少存储补丁所需的总空间, 又提高 CDN 缓存效率,因为所有预加载文件都会从同一 集合中选择后续补丁。

表排序

在初始字体文件中(无论是否编码为 woff2),可以自定义 文件内实际表字节的顺序。 编码器应考虑尽可能早地放置映射表(IFT 和 IFTX)以及解码 映射表所需的任何其他表(cmap)。这样,经过优化的客户端实现可以在收到全部字体数据之前 访问 补丁映射,并可能更早发起任何所需 补丁的请求。

类似地,以表为键的补丁为每个被修补的表提供独立的 brotli 流,且该格式允许 将这些流按任意顺序放入 补丁文件。因此,出于相同原因,编码器应考虑尽可能早地在补丁文件中放置 映射表更新,以及解码映射表所需的任何 其他表。

选择输入 ID 编码

本规范支持两种将补丁 ID 嵌入 URL 模板的编码。第一种是base32hex, 它很适合通常存储在文件系统中的预生成补丁。Base32hex 编码仅使用 字符 0-9 和 A-V,这些字符在所有常用文件系统中都可安全用于文件名,并且不会因 大小写不敏感而发生冲突。由于字符串嵌入时不带填充,此格式无法可靠解码, 因此对于动态生成的补丁可能不是理想 选择。另一种编码是base64url,它是适合嵌入 URL 或区分大小写文件系统的 base64 编码变体。使用此编码时,id 会带填充嵌入,从而 可以可靠解码该值。

单个字符选择符 d1 到 d4 仅相对于 base32hex 编码的 id。这些选择符 通常用于 通过根据 id 编码末尾字符将相关文件分散到一个或多个 子目录层级中,减少单个文件系统目录中存储的文件数量。使用整数 id 时,这些字符通常会在各数字之间 均匀分布,但对于字符串 id,可能分布不均甚至恒定。希望将字符串 id 与 d1 到 d4 一起使用的编码器,应注意使 id 字符串的末尾发生变化。将 d1 到 d4 与使用 base64url 编码的 id 混合使用是有效的。

8. 隐私 注意事项

8.1. 根据字符集推断内容

IFT 会向承载 Web 字体的服务器暴露有关浏览器希望使用该字体 渲染的字符集合的信息(有关 详细信息,请参阅 § 4 扩展字体子集)。

对于使用非常庞大字符集的某些语言(例如中文和日文),传输 总字节数的大幅减少 意味着 Web 字体首次变得可用,包括在移动网络上。 但是,对于这些语言,恶意字体服务器可能会分析单个请求,以获取 有关正在阅读的内容 类型的信息。除非请求的 字符非常罕见,否则尚不清楚这种攻击的可行性,或利用它所需的计算复杂度。

更具体地说,IFT 字体包含一组 Unicode 码位分组,并会为与 正在渲染内容相交的分组发出请求。这向承载服务器提供信息,表明某个 分组中至少需要一个码位,但不 包含有关该分组中具体需要哪些码位的信息。从功能上看,这与现有的CSS 字体 4 § 4.5 字符范围:unicode-range 描述符非常相似,并具有相同的隐私 影响。有关 unicode-range 隐私影响的讨论可 在 CSS 字体 4 规范中找到:

当内容作者和/或站点作者选择使用 IFT 字体时,他们必然会对 编码该 字体的人以及加载该字体的服务给予一定信任,因为如上所述,在扩展 IFT 字体时, 会传输一些有关正在渲染内容的信息。(二者之间存在平衡, 因为如下所述, 编码时越谨慎,对服务的信任要求就越低。) 因此, 对于隐私敏感场景,建议自行托管 IFT 字体,因为这样可防止将有关 内容的任何信息传输给第三方。此外,在这种情况下,建议配置内容 安全策略设置,禁止从不同源加载字体。

8.2. IFT 字体编码与隐私

IFT 字体的编码方式,会显著影响从补丁文件传输中 推断出多少有关内容的信息。本节提供一些通用指导,用于 评估编码 IFT 字体时所作选择的隐私影响。

IFT 字体会拆分为一组带有关联激活条件的补丁。当 发出补丁请求时, 它传达出内容包含某些与该补丁激活条件相交的信息。因此, 字体中激活条件的结构,是评估编码隐私 特性时最重要的方面。

字体支持的每个唯一码位、特性和设计空间配置都会提供一个潜在 信号: 它是否存在。激活条件可以是析取条件或合取条件。析取 条件会引入不确定性,因为激活只表示 条件中的至少一个单独项目存在。另一方面,合取补丁不会增加不确定性, 因为它们要求每个单独项目同时 存在。

码位或特性的典型出现频率会影响其存在所传达的信息量。 高频出现的项目传达的信息少于低 频项目。低频码位的存在会大幅缩小可能内容的集合。 从高层次来看,这意味着对于包含低频 项目的条件,应使用更大、粒度更低的条件。

本规范制定期间进行的模拟 发现,(对于析取 条件)最小分组大小为 4 到 7 个项目可产生良好的模糊性。在编码中使用最小分组大小 从性能角度看也 很合理,因为粒度过细的补丁会引入过多开销,导致性能不佳。 由于低频项目很少需要,对包含这些项目的补丁使用最小分组大小 对整体性能影响很小。整体性能通常由高频项目驱动。

编码器应评估生成编码中的每个条件,并确定其有效分组大小。有效分组 大小是跨单独项目(码位、布局标签、设计 空间)中,对触发该条件有贡献的最小析取子条件。对于用于隐私敏感场景的 编码, 编码器应确保所有条件的最小分组大小至少为 4 到 7。例如,考虑以下 情况:

编码器应提供配置控制,用于设置所生成编码的隐私级别,因为 不同使用场景 需要不同级别。例如,通过 https 自行托管的字体编码不会引入 隐私 问题,而计划托管在常见第三方字体服务上的 IFT 字体则不同。在为 隐私敏感度较低的场景编码时,编码器可以选择进行违反建议最小分组 大小的优化。 例如:设置一个条件,用于添加某个可选布局特性的数据,这会产生有效分组 大小 1。

当编码器在给定编码保护隐私的程度方面提供这种灵活性时, 建议 其文档和/或用户界面传达本节中的足够信息,使 生成编码的用户能够作出适当选择。类似地,建议托管 IFT 字体的站点 在编码自己的字体或从第三方获取已编码字体时考虑隐私因素。

8.3. 按源限制可避免指纹识别

根据[css-fonts-4]的要求:

Web 字体不得 在除与 @font-face 规则关联或拥有 FontFaceSet 的文档之外的任何其他文档中访问。 设备上的其他应用不得 访问 Web 字体。”——CSS 字体 4 § 10.2 Web 字体

由于 IFT 字体在 CSS 中与普通字体采用相同方式处理(§ 2 选择启用机制), 这些要求同样适用,并可避免信息跨 源泄露。

类似要求也适用于字体调色板值:

作者定义的 字体颜色调色板只能供引用它的文档使用。在引用它的文档之外使用 作者定义的颜色调色板, 会构成安全泄漏,因为一个页面的内容将能够 影响其他页面,攻击者可将其用作攻击向量。”——CSS 字体 4 § 9.2 用户定义的字体颜色调色板:@font-palette-values 规则

9. 安全 注意事项

一个安全问题是,IFT 字体可能生成大量补丁网络请求。 这可能给 客户端或承载补丁的服务带来问题。IFT 规范包含若干 缓解措施,用于限制过多 请求:

  1. § 4 扩展字体子集:禁止多次重新请求同一 URL,并限制扩展过程中 可发出的请求总数。

  2. 加载补丁文件:规定在实现 Web 浏览器时使用[FETCH], 并匹配初始 字体加载的 CORS 设置。因此,除非承载服务通过适当的 访问控制标头选择启用,否则不允许对补丁文件发出跨源请求。

10. 更改

2025 年 7 月 31 日的候选 推荐标准快照以来(参见提交历史):

2025 年 7 月 15 日的工作 草案以来(参见提交历史):

2025 年 2 月 20 日的工作 草案以来(参见提交 历史):

2024 年 7 月 9 日的工作 草案以来(参见提交 历史):

附录 A:默认特性 标签

本附录为非规范性内容。它提供一组布局特性, 这些特性被认为 在大多数塑形器实现中默认使用。此列表汇总自:

塑形器默认使用的布局特性

标签 名称
abvf 基线上方形式
abvm 基线上方标记定位
abvs 基线上方替换
akhn Akhand
blwf 基线下方形式
blwm 基线下方标记定位
blws 基线下方替换
calt 上下文替代形式
ccmp 字形组合/分解
cfar Ro 之后的合取形式
chws 上下文半宽间距
cjct 合取形式
clig 上下文连字
cswh 上下文花体
curs 草书定位
dist 距离
dnom 分母
dtls 无点形式
fin2 终止形式 #2
fin3 终止形式 #3
fina 终止形式
flac 扁平重音形式
frac 分数
half 半形式
haln Halant 形式
init 初始形式
isol 独立形式
jalt 对齐替代形式
kern 字距调整
liga 标准连字
ljmo 前导 Jamo 形式
locl 本地化形式
ltra 从左到右替代形式
ltrm 从左到右镜像形式
mark 标记定位
med2 中间形式 #2
medi 中间形式
mkmk 标记到标记定位
mset 通过替换进行标记定位
nukt Nukta 形式
numr 分子
pref 基线前形式
pres 基线前替换
pstf 基线后形式
psts 基线后替换
rand 随机化
rclt 必需的上下文替代形式
rkrf Rakar 形式
rlig 必需连字
rphf Reph 形式
rtla 从右到左替代形式
rtlm 从右到左镜像形式
rvrn 必需的变体替代形式
ssty 数学脚本样式替代形式
stch 拉伸字形分解
tjmo 尾随 Jamo 形式
valt 替代垂直度量
vatu Vattu 变体
vchw 垂直上下文半宽间距
vert 垂直书写
vjmo 元音 Jamo 形式
vkrn 垂直字距调整
vpal 成比例的替代垂直度量
vrt2 垂直替代形式与旋转
vrtr 用于旋转的垂直替代形式

附录 B:扩展算法 执行示例

本附录为非规范性内容。它提供典型 IFT 字体如何由 § 4.3 增量字体扩展算法处理的示例。

示例 1:以表和字形为键的 补丁

在此示例中,IFT 字体混合包含§ 6.2 以表为键§ 6.3 以字形为键补丁。

初始字体:包含以下 IFT 和 IFTX 补丁映射。注:当子集定义中未指定 特性和设计空间 集合时,它们默认为空集合。

表 = "IFT " 兼容性 ID = 0x0000_0000_0000_0001
子集定义 URL 格式编号
code points: { 'a', 'b', ..., 'z' }
//foo.bar/01.tk 2,以表为键——部分失效
code points: { 'A', 'B', ..., 'Z' }
//foo.bar/02.tk 2,以表为键——部分失效
code points: { '0', '1', ..., '9' }
//foo.bar/03.tk 2,以表为键——部分失效
code points: { 'a', 'b', ..., 'z',
               'A', 'B', ..., 'Z' }
//foo.bar/04.tk 2,以表为键——部分失效
code points: { 'a', 'b', ..., 'z',
               'A', 'B', ..., 'Z',
               '0', '1', ..., '9' }
//foo.bar/05.tk 2,以表为键——部分失效

表 = "IFTX" 兼容性 ID = 0x0000_0000_0000_0002
子集定义 URL 格式编号
code points: { 'a', 'b', ..., 'm' }
//foo.bar/01.gk 3,以字形为键
code points: { 'n', 'o', ..., 'z' }
//foo.bar/02.gk 3,以字形为键
code points: { 'A', 'B', ..., 'M' }
//foo.bar/03.gk 3,以字形为键
code points: { 'N', 'O', ..., 'Z' }
//foo.bar/04.gk 3,以字形为键
code points: { '0', '1', ..., '9' }
//foo.bar/05.gk 3,以字形为键

优化扩展示例

注:此示例执行按照 经过优化的客户端实现来描述,该实现旨在减少为目标子集定义扩展字体所需的 往返次数。此优化执行完全符合 扩展算法,但会比扩展算法规定的时间更早 加载某些 URL(以字形为键的 URL)。这是允许的,因为这些补丁 不会被之前以表为键的补丁使之失效。

输入:

迭代 1:

迭代 2:

迭代 3:

迭代 4:

示例 2:带 子条目的以字形为键的补丁

在此示例中,IFT 字体包含一组§ 6.3 以字形为键补丁, 它们利用子条目正确处理 UVS 替换补丁的包含。对于每组基础字形,都有一组 通过变体选择符进行替换的替代字形。 这些字形保留在单独补丁中。替代字形 补丁通过子条目配置, 仅当基础字形和变体选择符同时存在时才加载。

初始字体:包含以下 IFT 补丁映射。注:当 子集定义中未指定码位、特性、设计空间 或子条目集合时,它们默认为空集合。

表 = "IFT " 兼容性 ID = 0x0000_0000_0000_0001
子集定义 URL 格式编号 忽略? 注释
code points:
  { 'a', 'b', ..., 'm' }
//foo.bar/01.gk 3,以字形为键
code points:
  { 'n', 'o', ..., 'z' }
//foo.bar/02.gk 3,以字形为键
code points: { VS1 }
N/A 3,以字形为键
child entries: [0, 2],
match mode: conjunctive
//foo.bar/03.gk 3,以字形为键 包含由 VS1 激活的 {'a', ..., 'm'} 替代字形
child entries: [1, 2],
match mode: conjunctive
//foo.bar/04.gk 3,以字形为键 包含由 VS1 激活的 {'n', ..., 'z'} 替代字形

扩展示例 1

输入:

迭代 1:

迭代 2:

扩展示例 2

输入:

迭代 1:

迭代 2:

迭代 3:

一致性

文档 约定

一致性要求通过 描述性断言与 RFC 2119 术语的组合来表达。 本文档规范性部分中的关键词“MUST”“MUST NOT”“REQUIRED”“SHALL”“SHALL NOT”“SHOULD”“SHOULD NOT”“RECOMMENDED” “MAY”和“OPTIONAL” 应按照 RFC 2119 中的描述进行解释。 但是,为提高可读性, 本规范中的这些词并不全部使用大写字母。

除明确标记为非规范性的章节、示例和注释外, 本规范的所有文本均为规范性内容。 [RFC2119]

本规范中的示例以“例如”一词引入, 或通过 class="example" 与规范性文本区分开来, 如下所示:

这是一个信息性示例。

信息性注释以“注”一词开头, 并通过 class="note" 与规范性文本区分开来, 如下所示:

注:这是一条信息性注释。

一致性 算法

作为算法一部分、以祈使语气表述的要求 (例如“移除所有前导空格字符” 或“返回 false 并中止这些步骤”) 应按照引入该算法时所使用的关键词 (“必须”“应当”“可以”等)的含义进行解释。

以算法或具体步骤形式表述的一致性要求 可以通过任何方式实现, 只要最终结果等效即可。 特别是,本规范中定义的算法 旨在易于理解, 而非以性能为目标。 鼓励实现者进行优化。

索引

本规范定义的 术语

通过引用定义的 术语

参考文献

规范性参考文献

[CSS-FONTS-4]
Chris Lilley。CSS 字体模块第 4 级。2024 年 2 月 1 日。工作草案。URL:https://www.w3.org/TR/css-fonts-4/
[FETCH]
Anne van Kesteren。Fetch 标准。现行 标准。URL:https://fetch.spec.whatwg.org/
[I18N-GLOSSARY]
Richard Ishida;Addison Phillips。国际化 术语表。2024 年 10 月 17 日。说明。URL:https://www.w3.org/TR/i18n-glossary/
[ISO14496-22]
信息技术——视听对象编码—— 第 22 部分:开放字体格式。制定中。URL:https://www.iso.org/standard/87621.html
[OPEN-TYPE]
OpenType 规范。2024 年 5 月。说明。URL:https://learn.microsoft.com/en-us/typography/opentype/spec
[PFE-report]
Chris Lilley。渐进式字体扩充:评估 报告。2020 年 10 月 15 日。说明。URL:https://www.w3.org/TR/PFE-evaluation/
[RFC2119]
S. Bradner。用于在 RFC 中 指示要求级别的关键词。1997 年 3 月。最佳当前实践。URL:https://datatracker.ietf.org/doc/html/rfc2119
[RFC4648]
S. Josefsson。Base16、Base32 和 Base64 数据 编码。2006 年 10 月。提议标准。URL:https://www.rfc-editor.org/rfc/rfc4648
[RFC7932]
J. Alakuijala;Z. Szabadka。Brotli 压缩数据 格式。2016 年 7 月。信息性文档。URL:https://www.rfc-editor.org/rfc/rfc7932
[Shared-Brotli]
J. Alakuijala;等。共享 Brotli 压缩数据格式。2025 年 2 月。互联网草案。URL:https://datatracker.ietf.org/doc/html/draft-vandevenne-shared-brotli-format-15
[UNICODE]
Unicode 标准。URL:https://www.unicode.org/versions/latest/
[UTF-8]
F. Yergeau。UTF-8,ISO 10646 的一种转换格式。2003 年 11 月。互联网标准。URL:https://www.rfc-editor.org/rfc/rfc3629
[WHATWG-URL]
Anne van Kesteren。URL 标准。现行标准。 URL:https://url.spec.whatwg.org/
[WOFF2]
Vladimir Levantovsky。WOFF 文件格式 2.0。2024 年 8 月 8 日。推荐标准。URL:https://www.w3.org/TR/WOFF2/

信息性参考文献

[CSS-CONDITIONAL-5]
Chris Lilley;等。CSS 条件规则模块 第 5 级。2025 年 10 月 30 日。工作草案。URL:https://www.w3.org/TR/css-conditional-5/
[ENABLING-TYPOGRAPHY]
John Hudson。启用排版:迈向 OpenType 布局的通用模型。2014 年 4 月 15 日。说明。URL:https://www.tiro.com/articles/enabling-typography
[RFC9113]
M. Thomson,编辑;C. Benfield,编辑。HTTP/2。 2022 年 6 月。提议标准。URL:https://httpwg.org/specs/rfc9113.html
[RFC9114]
M. Bishop,编辑。HTTP/3。2022 年 6 月。提议 标准。URL:https://httpwg.org/specs/rfc9114.html
[WOFF]
Jonathan Kew;Tal Leming;Erik van Blokland。WOFF 文件格式 1.0。2012 年 12 月 13 日。推荐标准。URL:https://www.w3.org/TR/WOFF/