追求枚举字体的最佳方法

1 fonts

自学 VB hack,只有一点 .net 经验。我发现的关于这个主题的大多数帖子都相当老了,所以在我开始探索任何一个特定的兔子洞之前,我正在寻找最好的方向,并确保没有遗漏任何新的东西。

我在 vb.net 中编写了一个加载和卸载字体的应用程序。我正在读取注册表以查看加载了哪些字体,直接从字体文件读取字体信息,并使用 AddFontResource、RemoveFontResource 并修改注册表以在计算机重新启动时进行操作。

所有这些都有效,但正如这篇精明的文章所指出的,仅仅因为它在注册表中并不意味着它正在枚举。我们将生产字体存储在网络驱动器上,而 Windows 10 在重新启动/登录后重新连接时表现很糟糕,因此我做了相当多的研究来枚举所有加载的字体,令人惊讶的是,似乎没有任何东西这很容易工作。

EnumFontFamiliesEx API 的使用对我来说几乎令人难以置信(只是由于我缺乏知识),甚至我下载的代码也运行得相对较好,但无法获取某些加载的字体。与该链接的OP相同,我还没有弄清楚为什么,至少现在还没有。我已按照此处的建议修改了下载的代码以使用 EnumFontFamiliesExW API ,但仍然缺少字体。

这篇文章提出了一个叫做 DirectWrite 的东西,我认为它是 DirectX 的一部分?在看了一些之后,挫败感让我来到了这里。

所以我想有两个问题:

  1. (大事)有比 EnumFontFamiliesExW 更新的东西吗?

  2. 对于 OTF 和 TTF 字体,我正在阅读“Typgraphic Family name”(此处定义的 ID 16 )(当它可用时),因为这就是 Adob​​e 产品显示的内容。使用 EnumFontFamiliesExW 是否可以读取此 ID?我没有看到任何表明它是的。

哎呀,抱歉这么久,感谢您阅读到这里!

Pet*_*ble 6

快速解答

(大事)有比 EnumFontFamiliesExW 更新的东西吗?

是的:直接写入。

对于 OTF 和 TTF 字体,我正在阅读“Typgraphic Family name”(此处定义的 ID 16)(如果可用),因为这就是 Adob​​e 产品显示的内容。使用 EnumFontFamiliesExW 是否可以读取此 ID?我没有看到任何表明它是的。

快速回答是否定的,GDI 不会为您提供名称 ID 16/17。DWrite 确实如此,但在某些情况下,字体的性质可能要求您以某种方式调用。这实际上要完全解释起来有些复杂。我将为您提供一些全面的信息,但特别是为此,请跳至字体系列模型。

现在,您的问题似乎还涉及某些字体在您期望它们枚举时未枚举的一个方面。很难对此发表评论,当然在没有更多细节的情况下也是如此。

Windows 字体 API

Windows 具有三代不同的文本/字体平台 API:GDI、GDI+ 和 DirectWrite。为了您的目的,我们可以忽略 GDI+。

还有 WPF,它是 .Net 的一部分。.Net 不是 Windows本身的一部分,其功能基本上是 D(irect)Write 的子集,因此我也将主要跳过它,除了下面的历史点。

GDI 和 DWrite 都有 API 来枚举加载到操作系统会话中的字体。现在,有一些事情需要理解:

安装与加载

当 GDI 在引导期间初始化时,它没有任何字体。* 它仅在操作系统的另一部分开始调用AddFontResource()或时才获取字体AddFontResourceEx()。操作系统的另一部分读取Fonts注册表中的密钥,并为每个条目调用其中一个 API。

(* 并非 100% 正确:GDI 本身最初会读取 HKLM...\GRE_Initialize 键下的注册表项以加载某些位图字体。)

但请注意,任何应用程序也可以调用AddFontResource().

因此,在调用AddFontResource(). “已安装字体”的概念适用于在注册表中注册的字体,这些字体将在启动时自动加载。

请注意,还有RemoveFontResource()/RemoveFontResourceEx()函数。因此,已安装的字体可能会在使用会话期间的某个时刻被卸载。

相同的概念基本上适用于 DWrite。它读取注册表来加载字体(而不是等待其他东西来加载它们)。它还与 GDI 对话,以在加载的字体方面保持同步。它支持加载附加字体,但仅限于调用应用程序。因此,如果您的意图是加载在任何应用程序中使用的字体,则尚未提供一种方法来执行此操作。

系统与用户

另一件可能有用的事情是,Windows 10 支持区分系统范围内安装或加载的字体与每个用户安装或加载的字体。系统范围内安装的字体在注册表的配置单元中注册HKLM,并(默认情况下)存储在 %windir%\Fonts 文件夹中。每用户字体在HKCU注册表中的(当前用户)配置单元中注册,并存储在 %userprofile%\AppData 文件夹中的某个位置。

当您从 GDI 或 DWrite 获取字体枚举时,它将包括(当前加载的)系统范围字体以及该用户的字体。

所有应用/一个应用/未列举

通常,当在 GDI 中加载字体时,任何应用程序都可以枚举和使用它们。但AddFontResourceEx()添加了可用于限制的标志:

  • FR_PRIVATE:只有加载该字体的应用程序才能使用它。
  • FR_NOT_ENUM:字体永远不会被枚举,即使对于加载它的应用程序也是如此。

DWrite 支持应用程序特定字体的想法,并且比 GDI 具有更多的功能。例如,它提供了一种应用程序可以直接从云服务加载其字体的方法。但这些字体仅适用于一个应用程序。DWrite 针对此类场景有一些不同的 API;差异与“集合”和“集合”的概念有关。(字体集合是最早的设计,但后来添加了字体集以提供某些附加功能。)如果对每个应用程序字体的 DWrite API 感兴趣,GitHub 上有一个代码示例,演示了如何在某些不同的场景中使用 API。

GDI 与 DWrite:C 与 COM

我已经描述了 GDI 和 DWrite 之间的一些相似之处和不同之处。另一个重要的区别是 GDI API 是“平面”C API。DWrite(与所有 DirectX 一样)使用 COM。COM API 并不难使用,但如果您来自较旧的 Win32 世界,它看起来只是有点不同。

GDI有很多函数,你什么时候调用什么函数就可以了。(在某些情况下,您可能需要先调用一个 API 来获取句柄,然后才能调用另一个 API,但它有一种“程序化”的感觉。)

对于 DWrite,只有一个函数DWriteCreateFactory()。您调用该函数来获取“工厂”对象,并使用其方法来获取支持特定接口的其他对象。此外,接口也有版本控制(例如,IDWriteFactoryvs 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(旧)和 I(斜体)按钮的应用程序。因此,这就是呈现具有特定系列模型的字体:一个系列可以具有常规、粗体、斜体和粗体斜体字体。

但这从来都不是字体设计师对家庭的看法。他们认为家庭可能包括 Light、Semibold 或 Heavy;或压缩或超压缩或扩展;或标题 vs. 说明文字 vs.... 从印刷意义上来说,一个系列可能包含任意数量的不同字体样式。

常规/粗体/斜体/粗体斜体模型有时被称为“GDI 系列模型”,尽管 GDI 并不那么受限。相反,是使用 GDI 编写的应用程序造成了这一限制。这迫使字体设计者以适合该模型的方式创建字体。

这就是名称 ID 1 和 2 的作用:它们是 GDI 用来决定什么是族以及什么是“子族”(族内的样式/面)的名称 ID。字体开发人员很久以前就开始限制这些字符串以适应 R/B/I/BI 模型。

但设计师并不想局限于该模型。因此,添加了名称 ID 16 和 17:它们的目的是允许字体开发人员不受限制地按照自己的意愿组织系列。这就是 Adob​​e 应用程序所支持的。

现在,WPF 被引入了。它的开发时间与 CSS2 的开发时间大致相同。在 CSS2 中,引入了字体属性font-weightfont-stretchfont-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 早期就出现了一些涉及光学尺寸变体的字体系列的内容。(就像 Adob​​e 的 Kepler Pro 系列一样。) 总而言之,它引发了一个问题:是否需要另一对名称 ID 来支持以重量、拉伸、风格光学尺寸为轴的系列模型。哎呀!一定会有更好的办法。

好的,现在我们来到了 2016 年。微软与苹果、Adobe、谷歌和其他公司合作开发了 OpenType 的扩展,称为可变字体。(有关可变字体的一些好读物,请参阅此处此处。)可变字体允许字体设计者在设计中沿着单个系列内的任意数量的变化轴构建连续变化。

可变字体意味着永远不需要增加更多的家族/子家族对姓名 ID。(事实上​​,甚至不需要 21/22。)但那是因为可变字体基本上会强制想要支持它们的应用程序支持名称 ID 16/17 的开放式性质,其家族可以很大和字体设计师希望的那样的潜水员。

关于可变字体的另一件事:因为它们支持(近)连续的设计变化,所以您可以要求“粗体”,或者您可以要求只是-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 可以实现此目的。