OpenType 变量字体

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

什么是 OpenType 变量字体?

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

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

 

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

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

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

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

OpenType 变量字体格式使用传统 OpenType 字体中的数据表,以及描述不同实例中各种数据项的值如何变化的某些附加表。 格式将一个变体实例指定为“默认实例”,该实例使用传统表获取默认值。 所有其他实例都依赖于默认数据以及其他增量数据。 例如,“glyf”表可以具有标称字形的贝塞尔曲线说明,该形状是默认实例使用的形状,而“glyf”表将描述如何针对其他实例调整标志符号的贝塞尔控制点。 同样,其他字体值可以具有一个名义值加上描述这些值在不同实例中如何变化的增量数据;例如,x 高度和其他字体宽指标,或特定于字形的标记定位位置和字距调整。 

由于可变字体可以支持任意一组变体轴,因此它们需要一个可扩展字体系列模型,该模型更直接地反映字体设计者如何创建字体系列:字体系列由家族名称定义,某些设计特征是恒定的,任意数目 (由字体开发人员确定,) 设计可以变化的方式。 可以创建一个字体系列,但可以使用 x 高度、衬线大小、“funkiness”或字体开发人员希望的任何变体创建不同的字体系列。 在此模型中,最好使用常规或“首选”或“版式”、姓氏加上一组键值对来描述字体选择,每个键值对都表示一种变体和特定值,一般情况下的变体类型为可扩展集。 这种字体系列的一般概念可以应用于传统的非变量字体以及可变字体。 例如,在此常规的版式系列模型中,“Selawik VF”系列可能在重量、光学尺寸和衬线设计上存在差异,例如“半轻型横幅 Sans”。 

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

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

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

版式系列模型

WSS 系列模型

系列

人脸

系列

人脸

锡特卡

文本常规

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 使用它来区分与其他变体轴相关的标记中指定权重、拉伸或样式属性的人脸名称标记。  

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

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

OpenType 规范允许字形轮廓数据以以下两种格式之一表示:使用“glyf”表(使用 TrueType 大纲和提示格式)或“CFF”表(使用紧凑字体格式 (“CFF”) 表示形式)。 在具有 TrueType 轮廓的可变字体中,将继续使用“glyf”表,并辅以提供大纲变体数据的“汽改”表。 这意味着,具有 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 变量字体可以像其他 OpenType 字体一样从 Windows shell 安装,还可以在应用创建的自定义字体集中使用。  

在系统中安装时,可变字体的所有命名实例都将包含在通过调用 IDWriteFontFamily3::GetSystemFontSet 方法返回的字体集中。 请注意,字体集是一个没有家庭分组层次结构的平面列表,但该集中的每个项都有一个基于 WSS 系列模型的家族名称属性。 可以使用 IDWriteFontSet::GetMatchingFonts 方法筛选特定变量字体命名实例的字体集。 如果使用采用 familyName 的 GetMatchingFonts 重载,则指定的 familyName 必须使用符合 WSS 字体系列模型的名称。 可以使用 IDWriteFontSet::GetPropertyValues 方法使用 DWRITE_FONT_PROPERTY_ID_FAMILY_NAME 获取字体集中出现的 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 的值,fontWeight 也支持这一点。 因此,从变体权重轴值到 fontWeight 的更改相对较小:为命名实例报告的 fontWeight 可以从用于定义字体内命名实例的精确值进行舍入。 DirectWrite fontStretch 和 OpenType 宽度轴刻度之间的区别更大:DirectWrite使用 1 到 9 之间的值,遵循 OpenType OS/2 表的 usWidthClass 值,而 OpenType 宽度轴刻度使用正值表示正常宽度的百分比。 OpenType 规范中的 usWidthClass 文档提供了值 1 到 9 与正常值百分比之间的映射。 从宽度轴值转换时,为命名实例报告的 fontStretch 值可能涉及舍入。 

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

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