OpenType 变量字体

本主题介绍 OpenType 变量字体、它们在 DirectWrite 和 Direct2D 中的支持,以及如何在应用中使用它们。 

什么是 OpenType 变量字体?

OpenType 字体格式规范的版本 1.8 引入了一个新的扩展,即 OpenType 字体变体。 使用这些扩展的字体称为 OpenType 变量字体。 OpenType 变量字体是一种字体,可以通过在不同设计之间使用连续内插(全部在单个字体中定义)来表现类似于多个字体。

OpenType 变量字体可以定义其设计沿一个或多个独立轴的连续变体,例如粗细或宽度:

 

使用字母“G”显示 OpenType 变量字体,并显示沿水平宽度轴和垂直粗细轴的不同变体。

字体开发人员确定一组在给定字体中使用的变体轴。 这些轴可以包括一组已知的(或“已注册”)变体轴,如粗细和宽度,但它们也可以包括字体开发人员定义的任意自定义变体轴。  

通过为字体选择一组变体轴,字体开发人员定义字体设计变体的抽象 n 维空间。 文本引擎可以在该连续空间中指定任何位置或“instance”来布局和呈现文本。 

字体开发人员还可以在设计变体空间中选择和分配特定实例的名称;这些实例称为“命名实例”。 例如,具有粗细变化的字体可能支持非常轻和非常粗细的笔划之间的连续变化,而字体开发人员选择了该连续线的特定粗细,并为其分配了名称,例如“Light”、“Regular”和“Semibold”。 

OpenType 变量字体格式使用传统 OpenType 字体中找到的数据表以及描述不同实例各种数据项的值如何更改的其他表。 格式将一个变体实例指定为“默认实例”,该实例使用传统表获取默认值。 所有其他实例都依赖于默认数据和其他增量数据。 例如,“glyf”表可以具有名义字形形状的 Bezier 曲线描述,该形状是用于默认实例的形状,而“gr”表将描述如何调整字形的 Bezier 控制点用于其他实例。 同样,其他字体值可以具有名义值和增量数据,描述这些值如何更改不同的实例;例如,x 高度和其他字体范围的指标,或字形特定的标记定位位置和字距调整。 

由于可变字体可以支持任意一组变体轴,因此它们需要一个可扩展的字体系列模型,该模型更直接地反映字体设计器如何创建字体系列:字体系列由家族名称和某些设计特征定义,具有任意数字(由字体开发人员确定)的设计可能有所不同的方式。 可以使用变量创建一个字体系列,以便权重,但可以使用 x 高度、serif 大小、“funkiness”或其他字体开发人员希望的任何变体创建其他字体系列。 在此模型中,最好使用常规或“首选”或“版式”来描述字体人脸选择,系列名称加上一组键值对,每个组合表示一种变体和特定值,一般变体类型为可扩展集。 此字体系列的一般概念可以应用于传统、非可变字体以及可变字体。 例如,在此常规版式家庭模型下,家庭“Selawik VF”可能具有重量、光学大小和 serif 设计的变体,例如“Semilight Banner Sans”。 

但是,一些现有的软件实现(包括现有的 DirectWrite API)可以设计为字体系列更有限的模型。 例如,某些应用程序可能假定字体系列最多可以有常规、粗体、斜体和粗体斜体变体。 现有的 IDWriteFontCollectionIDWriteFontFamily 接口假定权重/拉伸/样式(“WSS”)系列模型,允许使用 DWRITE_FONT_WEIGHTDWRITE_FONT_STRETCHDWRITE_FONT_STYLE 枚举作为参数指定系列中的变体。 以前面的示例为例,光学大小和衬线轴不会被视为 WSS 模型中变化的家庭内部轴。 

完全支持可变字体需要 API,这些 API 允许使用可能由字体确定的多个参数指定家庭成员。 但是,现有的 API 设计可以通过将变量字体中定义的命名实例投影到更有限的字体系列模型中,从而为变量字体提供部分支持。 在前面的示例中,“Selawik VF Semilight Banner Sans”可以投影到 WSS 模型中,作为“Selawik VF 横幅 Sans”系列,其中“Semilight”作为重量变体。 

对于另一个示例,请考虑一个版式字体系列,如 Sitka,其粗细和光学大小变体。 家族中命名的变体包括 Sitka Text Regular 和 Sitka 横幅粗体(以及许多其他变体)。 版式系列名称为“Sitka”,而版式系列模型中这些变体的人脸名称将为“文本常规”和“横幅粗体”。 四个成员和 WSS 系列模型不允许家庭中的光学大小变体,因此必须像家庭级区别一样对待光学大小差异。 下表说明了如何在 WSS 系列模型中处理 Sitka 版式系列中的字体选择:

版式系列模型

WSS 系列模型

家庭

家庭

锡特卡

文本常规

Sitka 文本

定期

锡特卡

横幅粗体

Sitka 横幅

大胆

锡特卡

标题斜体

Sitka Caption

斜体的

 

从版式系列模型到 WSS 系列模型的名称投影可以应用于非可变字体,以及变量字体的命名实例。 但是,对于变量字体连续设计变体空间中的其他非命名实例,无法执行此作。 因此,支持可变字体的完整功能需要 API 设计为在版式系列中引用人脸,以不受约束的变体轴和轴值集来引用人脸。 

DirectWrite 中的 OpenType 变量字体支持

自 Windows 10 创意者更新发布起,OpenType 变量字体格式仍然非常新,字体供应商、平台和应用仍在实施新格式的过程中。 此更新在 DirectWrite 中提供此格式的初始实现。 

DirectWrite 内部版本已更新,以支持 OpenType 变量字体。 使用当前 API,这为变量字体的任何命名实例提供支持。 此支持可用于完整的工作流, 从命名实例的枚举、命名实例的选择、在布局和整形中使用,到呈现和打印。 为了受益于也对某些作使用 GDI 文本互作的应用,现有 GDI API 中也添加了类似的支持。 

在 Windows 10 创意者更新中,DirectWrite 不支持使用可变字体的持续变体功能的任意实例。

在许多作中,变量字体命名实例的 DirectWrite 中的行为无法与非可变字体的行为区分开来。 由于支持使用现有的 DirectWrite API 提供,因此变量字体的命名实例甚至可以在许多现有 DirectWrite 应用中工作,而无需进行任何更改。 但是,在某些情况下,异常可能适用:

  • 如果应用直接处理某些作的字体数据。 例如,如果应用直接从字体文件读取字形轮廓数据以创建某些视觉效果。
  • 如果应用对某些作使用第三方库。 例如,如果应用使用 DirectWrite 进行布局,则获取最终字形索引和位置,但随后使用第三方库进行呈现。
  • 如果应用将字体数据嵌入文档,或者以某种其他方式将字体数据传递到下游进程。

如果使用不支持变量字体的实现执行作,则这些作可能不会产生预期的结果。 例如,可以为变量字体的一个命名实例计算字形位置,但字形可能呈现为假设不同的命名实例。 根据应用程序实现的不同,结果在某些上下文中可能起作用,但在可能使用其他库的其他上下文中可能不起作用。 例如,文本可能会在屏幕上正确显示,但在打印时可能不会显示。 如果仅使用 DirectWrite 实现端到端工作流,则可以预期变量字体的命名实例的正确行为。 

由于现有的 DirectWrite API 支持使用粗细/拉伸/样式模型进行人脸选择,因此使用其他变体轴的字体的命名实例将从常规版式系列模型投影到 WSS 模型,如上所述。 这依赖于包含轴值子表的“样式属性”('STAT')表的变量字体,DWrite 使用它来区分人脸名称标记,这些标记指定权重、拉伸或样式属性与与其他变体轴相关的标记。  

如果变量字体不包含“STAT”表(OpenType 规范的变量字体必需),则 DirectWrite 会将字体视为仅包含默认实例的非可变字体。  

如果字体包含“STAT”表,但它不包含适当的轴值子表,这可能会导致意外结果,例如具有具有相同人脸名称的多个人脸。 目前不支持此类字体。 

OpenType 规范允许以以下两种格式之一表示字形大纲数据:使用“glyf”表,该表使用 TrueType 大纲和提示格式,或使用“CFF”表,该表使用压缩字体格式(“CFF”)表示形式。 在具有 TrueType 轮廓的变量字体中,“glyf”表将继续使用,并补充了一个提供大纲变体数据的“lambdar”表。 这意味着,具有 TrueType 大纲的变量字体的默认实例仅使用传统 OpenType 表,而旧版软件不支持可变字体。 但是,在带有 CFF 轮廓的变量字体中,“CFF”表被“CFF2”表取代,该表封装了默认大纲数据和一个表中的关联变体数据。 CFF 数据由单独的光栅器处理,该光栅器用于 TrueType 数据,而“CFF2”表需要具有“CFF2”支持的更新 CFF 光栅器。 较旧的 CFF 光栅器无法处理“CFF2”表。 对于带有 CFF 大纲数据的变量字体,这意味着即使默认实例在较旧的软件中也不起作用。 

在 Windows 10 创意者更新中,DirectWrite 不支持使用“CFF2”表使用 CFF 大纲数据的可变字体。 

使用 OpenType 变量字体

OpenType 变量字体易于使用,请记住上述当前限制:

  • 目前仅支持变量字体的命名实例。
  • 目前仅支持使用 TrueType 字形轮廓数据的变量字体(而不是 CFF 轮廓)。 
  • 对于使用除粗细、拉伸或样式以外的设计轴的字体,命名实例将投影到 WSS 系列模型中,这可能会导致某些命名实例显示为单独的系列(就像过去对非可变字体的情况一样)。 若要支持此功能,变量字体必须有一个“STAT”表,其中包含相应的轴值子表。
  • DirectWrite API 支持变量字体的命名实例,但如果某些作在不支持变量字体的较旧实现中执行,这些作可能会生成不正确的结果。 
  • 某些 DirectWrite API 使用 DWRITE_FONT_WEIGHTDWRITE_FONT_STRETCHDWRITE_FONT_STYLE 枚举在选择人脸时指定权重、拉伸和样式属性。 如果变量字体使用相应的变体轴,但具有许多需要更精细粒度的命名实例,则并非所有命名实例都可以在这些 API 中选择。

可以像安装其他 OpenType 字体一样从 Windows shell 安装符合这些要求的 OpenType 变量字体,也可以在应用创建的自定义字体集中使用。  

在系统中安装时,变量字体的所有命名实例都将包含在通过调用 IDWriteFontFamily3::GetSystemFontSet 方法返回的字体集中。 请注意,字体集是一个没有系列分组层次结构的平面列表,但集中的每一项都有一个基于 WSS 系列模型的系列名称属性。 可以使用 IDWriteFontSet::GetMatchingFonts 方法筛选特定变量字体命名实例的字体集。 但是,如果使用 GetMatchingFonts 采用 familyName 的重载,则指定的 familyName 必须使用符合 WSS 字体系列模型的名称。 可以使用 DWRITE_FONT_PROPERTY_ID_FAMILY_NAME IDWriteFontSet::GetPropertyValues 方法获取字体集中发生的 WSS 兼容系列名称的完整列表。  

同样,变量字体的所有命名实例都将在 IDWriteFactory::GetSystemFontCollection 方法返回的字体集合中表示。 由于字体集合的元素是基于 WSS 模型的字体系列,因此变量字体的命名实例可以在集合中表示为两个或多个字体系列的成员。 如果使用 IDWriteFontCollection::FindFamilyName 方法,则 familyName 参数必须是与 WSS 兼容的系列名称。 若要从字体集合中查找所有与 WSS 兼容的系列名称,应用可以循环访问每个系列并调用 IDWriteFontFamily::GetFamilyNames,不过,获取相应的字体集和使用 GetPropertyValues 方法可能更容易。 

使用自定义字体时,可以使用 自定义字体集 主题中所述的各种方法来创建字体集。 若要将变量字体添加到自定义字体集,建议使用 IDWriteFontSetBuilder1::AddFontFile 方法,因为它支持变量字体,并将在单个调用中添加变量字体的所有命名实例。 目前无法使用 IDWriteFontSetBuilder::AddFontFaceReference 方法将自定义变量字体的单个命名实例添加到字体集,因为无法创建字体人脸引用,以指定变量字体文件中的命名实例。 这意味着,目前无法将自定义字体的命名实例添加到分配有自定义属性的自定义字体集。 反过来,这意味着自定义变量字体当前无法与远程字体的 DirectWrite API 结合使用。 如果变量字体的命名实例包含在系统字体集中,则每个命名实例的字体人脸引用已存在,并且可以将这些引用添加到自定义字体集,包括使用自定义属性值。 有关更多详细信息,请参阅“自定义字体集”主题。 

使用变量字体时,DirectWrite DWRITE_FONT_WEIGHTDWRITE_FONT_STRETCH 枚举与 OpenType 规范中定义的粗细和宽度变体轴紧密相连,但不同。 首先,任何变体轴的数字刻度始终支持小数位数值,而 fontWeight 和 fontStretch 则使用整数。 OpenType 粗细轴刻度使用范围从 1 到 1000 的值,字体Weight 也支持这些值。 因此,从变体权重轴值更改为 fontWeight 相对较小:为命名实例报告的 fontWeight 可以从用于在字体中定义命名实例的精确值舍入。 DirectWrite fontStretch 和 OpenType 宽度轴比例的区别更大:DirectWrite 使用值从 1 到 9,遵循 usWidthClass OpenType OS/2 表的值,而 OpenType 宽度轴刻度使用正值表示正常宽度的百分比。 OpenType 规范中的 usWidthClass 文档提供了值 1 到 9 和正常百分比值的映射。 从宽度轴值转换时,为命名实例报告的 fontStretch 值可能涉及舍入。 

创建 IDWriteTextFormat时,必须指定字体集合和 WSS 兼容的字体属性(系列名称、粗细、拉伸和样式)。 这也适用于在 IDWriteTextLayout 文本范围上设置字体格式属性时。 可以从 IDWriteFontFace3 对象或 IDWriteFontIDWriteFontFamily 表示特定命名实例的对象获取这些属性。 如上所述,GetWeight 和 GetStretch 方法返回的值可能是用于定义命名实例的实际轴值的舍入近似值,但 DirectWrite 会将属性组合映射回所需的命名实例。 

同样,如果应用使用 IDWriteFontFallbackBuilder 创建自定义字体回退数据,则使用 WSS 兼容的系列名称为字符范围映射指定系列。 DirectWrite 中的字体回退基于 DirectWrite 选择回退系列中与起始系列变体最接近的变体的家庭。 对于涉及除权重、拉伸和样式以外的维度的变体,DirectWrite 当前将无法在回退系列中选择此类变体,除非专门创建了自定义回退数据,以便为具有特定非 WSS 属性(如“Caption”光学大小变体)的家庭提供回退映射。