快速解答
(大佬)有没有比 EnumFontFamiliesExW 更新的东西?
是:DirectWrite。
对于 OTF 和 TTF 字体,我正在阅读“Typographic Family name”(此处定义的 ID 16),因为它是可用的,因为这是 Adobe 产品显示的内容。这个 ID 甚至可以使用 EnumFontFamiliesExW 读取吗?我没有看到任何迹象表明它是。
快速回答是否定的,GDI 不会为您提供 ID 16/17 的名称。 DWrite 可以,但在某些情况下,字体的性质可能需要您以某种方式调用。这实际上有点复杂,无法完全解释。我将为您提供一些全面的信息,但特别是对于这一点,请跳至 Font 系列模型。
现在,您的问题似乎还涉及某些字体在您希望它们枚举时未枚举的方面。很难对此发表评论,当然不是没有更多细节。
Windows 字体 API
Windows 具有三代不同的文本/字体平台 API:GDI、GDI+ 和 DirectWrite。出于您的目的,我们可以忽略 GDI+。
还有 WPF,它是 .Net 的一部分。 .Net 不是 Windows proper 的一部分,该功能基本上是 D(irect)Write 的一个子集,所以我也将主要跳过它,除了下面的历史点。 p>
GDI 和 DWrite 都有 API 来枚举加载到操作系统会话中的字体。现在,有一些事情需要了解:
已安装与已加载
在引导期间初始化 GDI 时,它没有任何字体。* 它仅在操作系统的另一部分开始调用 AddFontResource() 或 AddFontResourceEx() 时获取字体。操作系统的其他部分读取注册表中的 Fonts 键并为每个条目调用其中一个 API。
(* 不是 100% 正确:GDI 本身首先读取 HKLM...\GRE_Initialize 键下的 reg 条目以加载某些位图字体。)
但请注意,任何应用程序也可以调用AddFontResource()。
因此,在字体文件通过对AddFontResource() 的调用传递之后,在 GDI 中“加载”了字体。 “已安装字体”的概念适用于在注册表中注册的字体,这些字体将在启动时自动加载。
请注意,还有 RemoveFontResource() / RemoveFontResourceEx() 函数。因此,已安装的字体可能会在使用会话期间的某个时间点被卸载。
相同的概念基本上适用于 DWrite。它读取注册表来加载字体(而不是等待其他东西来加载它们)。它还与 GDI 对话以在加载的字体方面保持同步。它支持加载其他字体,但仅适用于调用应用程序。因此,如果您的意图是加载字体以在任何应用程序中使用,那么(尚未)提供一种方法来做到这一点。
系统与用户
另一件可能有用的事情是,Windows 10 支持区分安装或加载的字体系统范围 与每个用户。在系统范围内安装的字体在注册表的 HKLM 配置单元中注册,并且(默认情况下)存储在 %windir%\Fonts 文件夹中。每用户字体在注册表的HKCU(当前用户)配置单元中注册,并存储在 %userprofile%\AppData 文件夹中的某个位置。
当您从 GDI 或 DWrite 获取字体枚举时,它将包括(当前加载的)系统范围字体以及该用户的字体。
所有应用/一个应用/未枚举
通常在 GDI 中加载字体时,它们可以被任何应用枚举和使用。但是AddFontResourceEx() 添加了可用于限制的标志:
-
FR_PRIVATE:只有加载字体的app才能使用。
-
FR_NOT_ENUM:从不枚举字体,即使对于加载它的应用也是如此。
DWrite,支持应用特定字体的概念,并且比 GDI 拥有更多的功能。例如,它提供了一种应用程序可以直接从云服务加载其字体的方式。但是字体只能用于一个应用程序。 DWrite 为这些场景提供了一些不同的 API;差异与“集合”和“集合”的概念有关。 (字体集合是最早的设计,但后来添加了字体集以提供某些附加功能。)如果对每个应用字体的 DWrite API 感兴趣,有一个 code sample on GitHub 演示了在一些不同的场景中使用 API。
GDI 与 DWrite:C 与 COM
我已经描述了 GDI 和 DWrite 之间的一些相似之处和不同之处。另一个重要区别是 GDI API 是“平面”C API。 DWrite(与所有 DirectX 一样)使用 COM。 COM API 并不难使用,但如果您来自较早的 Win32 世界,它看起来会有些不同。
使用 GDI,有很多函数,您可以随时调用任何函数。 (在某些情况下,您可能需要先调用一个 API 来获取句柄,然后才能调用另一个,但它具有“程序化”的感觉。)
使用 DWrite,只有一个函数:DWriteCreateFactory()。您调用该函数以获取“工厂”对象,然后使用其方法获取支持特定接口的其他对象。此外,接口是版本化的(例如,IDWriteFactory vs. IDWriteFactory1... vs. IDWriteFactory7)。这些对象总是能够支持最新的接口版本,但最初它们可能只呈现一个较旧的接口,并且您必须使用 COM 技术来获得较新的接口。 (这很像获取父类的对象,然后将其转换为子类。)
字体类型
另一个区别与支持的字体类型有关。 GDI 支持
- 旧版位图字体
- 传统笔画字体
- 旧版“Postscript”Type 1 字体
- TrueType / OpenType 字体 -- 一些较新的功能除外
DWrite 支持
- 旧版位图字体
- TrueType / OpenType 字体 -- 包括更新的功能
近年来,OpenType 格式已扩展为支持彩色字体和可变字体。 GDI 不支持彩色字体,并且在支持可变字体方面存在限制。 DWrite 没有这些限制。
字体系列模型
从 GDI 函数的名称可以看出,字体具有“家族”的概念。例如,“Arial”是一个家庭; “Arial Regular”和“Arial Italic”是该家族中的两张面孔。
让EnumFontFamiliesEx() 有点复杂的一件事是它既列举了家庭,也列举了家庭中的面孔。 DWrite 也有将字体组织成两级层次结构的 API,但它更清晰一点,因为它具有针对系列级别(字体系列)与面部级别(字体/字体)的不同接口。
现在,问题来了:如何定义家庭?您可能熟悉在下拉菜单中使用 Arial 作为字体选项的应用程序,以及 B(old) 和 I(talic) 按钮。所以,这是用特定的家族模型呈现字体:一个家族可以有 Regular、Bold、Italic 和 Bold Italic 面。
但这从来不是字体设计师对家庭的看法。他们认为家庭可能包括 Light、Semibold 或 Heavy;或浓缩或超浓缩或扩展;或 Title vs. Caption vs.... 从排版的意义上说,一个家族可能包含任意数量的不同面孔样式。
Regular/Bold/Italic/Bold Italic 模型有时被称为“GDI 系列模型”,但 GDI 并没有那么有限。相反,是在 GDI 上编写的应用程序造成了这种限制。这迫使字体设计师以适合该模型的方式创建字体。
这就是名称 ID 1 和 2 的作用:它们是 GDI 用来决定什么是系列以及什么是“子系列”(系列中的风格/面孔)的名称 ID。字体开发人员很久以前就开始尝试限制这些字符串以适应 R/B/I/BI 模型。
但设计师不想局限于该模型。因此,添加了名称 ID 16 和 17:它们旨在允许字体开发人员根据需要组织系列,而不受限制。这是 Adobe 应用程序所支持的。
现在介绍了 WPF。它的开发时间与 CSS2 开发的时间差不多。在 CSS2 中,引入了字体属性 font-weight、font-stretch 和 font-style,它们由三个独立的轴组成,一个家族中的字体可能在这些轴上有所不同。还添加了 font-variant 属性,但它更适合 OpenType 的 字体特征 概念,而不是家族风格轴。因此,WPF API 旨在支持具有权重、拉伸和样式轴的系列模型。
当第一次引入 DWrite 时,它也是这样做的。因此,在 DWrite 中,IDWriteFontCollection 接口假定,例如,“Arial”、“Arial Narrow”、“Arial Bold”、“Arial Black”适合同一个系列,并且它提供了一个系列 每个都可以提供一个字体列表。
但对于字体设计师来说,这三个轴仍然过于局限。因此,名称 ID 16 / 17 可能包含 WPF / DWrite 无法处理的家族内部区别。因此添加了名称 ID 21 和 22:如果 16/17 包含除重量、拉伸或样式之外的子系列区别,则应添加 21/22 字符串以以 WPF/DWrite 友好的方式呈现系列模型。
(不用说,字体设计师发现必须在他们的字体中正确放置三对单独的系列/子系列字符串真的很烦人。)
我不会详细介绍,但 Windows 10 早期出现了一些涉及光学尺寸变体的字体系列。 (就像 Adobe 的 Kepler Pro 系列一样。)总而言之,它导致了一个问题,即是否需要另一对名称 ID 来支持具有重量、拉伸和样式的系列模型和光学尺寸作为轴。哎呀!一定有更好的办法。
好的,现在我们来到 2016 年。Microsoft 与 Apple、Adobe、Google 和其他公司合作开发了 OpenType 扩展,称为可变字体。 (请参阅 here 和 here 了解有关可变字体的一些好读物。)可变字体允许字体设计人员在单个系列中沿任意数量的变化轴在设计中构建连续变化.
可变字体意味着永远不需要增加更多的名称 ID 家族/子家族对。 (事实上,即使是 21/22 也不需要。)但那是因为可变字体基本上会迫使想要支持它们的应用程序支持名称 ID 16/17 的开放性性质,并且家族可以一样大和字体设计师希望他们成为的潜水员。
关于可变字体的另一件事:因为它们支持(接近)连续的设计变化,你可以要求“粗体”,或者你可以要求 just-a-tiny-bit-bolder-then-bold ,或许多其他可能性。现在,字体设计者不可能为所有可能性赋予子系列名称(原则上,任何轴上都有 2^14 个可能的变体)。但他们可以选择特定的实例来命名。在枚举可变字体时,您将获得命名实例的枚举。如果需要,您还可以获得详细的轴信息(哪些轴以及每个轴的数值范围)。
枚举名称 ID 16/17
现在我终于可以回答您的问题了:如果您想像 Adobe 应用程序那样枚举字体,使用名称 ID 16 表示系列,然后使用 IDWriteFactory7 接口调用 GetSystemFontCollection() 方法。请注意,您需要选择 DWRITE_FONT_FAMILY_MODEL 选项:
-
DWRITE_FONT_FAMILY_MODEL_WEIGHT_STRETCH_STYLE:许多字体系列只有适合粗细/拉伸/样式模型的子系列成员。对于这些字体,您将获得一个基于名称 ID 16 和 17 组织的系列/字体的集合。但是如果一个系列有其他类型的设计变体,例如光学尺寸,那么 DWrite 将呈现基于名称 ID 21 组织的系列和 22。
-
DWRITE_FONT_FAMILY_MODEL_TYPOGRAPHIC:这将始终显示根据名称 ID 16 和 17 组织的系列。请注意:您可能会得到许多不同类型的子系列区别,它们不是重量、伸展度或风格。
如果你想知道字体是否是可变字体,如果是,则获取轴详细信息,还有其他 API 可以做到这一点。