自定义字体集
本主题介绍可在应用中使用自定义字体的各种方式。
- 简介
- API 摘要
- 关键概念
- 字体和字体文件格式
- 字体集和字体集合
-
常见方案
- 在本地文件系统中使用任意字体创建字体集
- 在本地文件系统中使用已知字体创建字体集
- 使用已知远程字体在 Web 创建自定义字体集
- 使用加载到内存 的字体数据创建自定义字体集
- 高级方案
介绍
大多数情况下,应用使用本地安装在系统上的字体。 DirectWrite 使用 IDWriteFactory3::GetSystemFontSet 或 IDWriteFactory::GetSystemFontCollection 方法访问这些字体。 在某些情况下,应用可能还需要使用作为 Windows 10 的一部分包含但当前未安装在当前系统上的字体。 可以使用 GetSystemFontSet 方法从 Windows 字体服务访问此类字体,或者调用 IDWriteFactory3::GetSystemFontCollection,并将 includeDownloadableFonts 设置为 TRUE。
但是,在某些应用程序方案中,应用需要使用未安装在系统中的字体,并且不是由 Windows 字体服务提供的。 下面是此类方案的示例:
- 字体作为资源嵌入在应用二进制文件中。
- 字体文件捆绑在应用包中,并存储在应用安装文件夹下的磁盘上。
- 应用是一种字体开发工具,需要加载用户指定的字体文件。
- 字体嵌入可在应用中查看或编辑的文档文件中。
- 应用使用从公共 Web 字体服务获取的字体。
- 应用使用通过专用网络协议流式传输的字体数据。
DirectWrite 提供了用于在这些和类似方案中使用自定义字体的 API。 自定义字体数据可能来自本地文件系统中的文件;从使用 HTTP 访问的远程基于云的源;或从任意源加载到内存缓冲区后。
注意
虽然 DirectWrite 提供了用于处理自定义字体的 API,但 Windows 10 中又在 Windows 10 创意者更新(预览版本 15021 或更高版本)中添加了较新的 API,以便更轻松地实现上述几种方案。 本主题重点介绍 Windows 10 中提供的 API。 有关需要处理早期 Windows 版本的应用程序,请参阅 自定义字体集合(Windows 7/8)。
API 摘要
本主题重点介绍以下 API 提供的功能:
- IDWriteFontSet 接口
- IDWriteFontSetBuilder 接口
- IDWriteFontSetBuilder1 接口
- IDWriteFontFaceReference 接口
- IDWriteFontFile 接口
- IDWriteFactory::CreateFontFileReference 方法
- IDWriteFactory::CreateCustomFontFileReference 方法
- IDWriteFactory3::CreateFontFaceReference 方法
- DWRITE_FONT_PROPERTY 结构
- DWRITE_FONT_PROPERTY_ID 枚举
- IDWriteFontFileLoader 接口
- IDWriteFactory::RegisterFontFileLoader 方法
- IDWriteFactory::UnregisterFontFileLoader 方法
- IDWriteFactory5::CreateInMemoryFontFileLoader 方法
- IDWriteInMemoryFontFileLoader 接口
- IDWriteFactory5::CreateHttpFontFileLoader 方法
- IDWriteRemoteFontFileLoader 接口
- IDWriteFontDownloadQueue 接口
- IDWriteFontDownloadListener 接口
- IDWriteFontFileStream 接口
- IDWriteRemoteFontFileStream 接口
- IDWriteAsyncResult 接口
- IDWriteFactory5::AnalyzeContainerType 方法
- IDWriteFactory5::UnpackFontFile 方法
关键概念
若要了解用于处理自定义字体的 DirectWrite API,了解这些 API 所依据的概念模型会很有帮助。 此处将介绍关键概念。
当 DirectWrite 执行实际文本布局或呈现时,它需要访问实际字体数据。 字体人脸对象保存实际字体数据,该数据必须存在于本地系统中。 但是,对于其他作(例如检查特定字体的可用性或向用户呈现字体选项),需要的是对特定字体的引用,而不是实际字体数据本身。 在 DirectWrite 中,字体人脸引用对象仅保存查找和实例化字体所需的信息。 由于字体人脸引用不保存实际数据,DirectWrite 可以处理实际数据位于远程网络位置以及实际数据本地时的字体人脸引用。
字体集是一组字体人脸引用,以及某些基本的信息属性,可用于引用字体或将其与其他字体(如姓氏)或字体粗细值进行比较。 各种字体的实际数据可能是本地的,也可能都是远程的,也可能是某种混合字体。
字体集可用于获取相应的字体集合对象。 有关详细信息,请参阅下面的字体集和字体集合。
IDWriteFontSet 接口提供了一些方法,用于查询属性值,例如家族名称或字体权重,或用于与特定属性值匹配的字体人脸引用。 筛选到特定选择后,可以获取 IDWriteFontFaceReference 接口的实例,以及下载方法(如果实际字体数据当前为远程),以获取可用于布局和呈现的相应 IDWriteFontFace3 对象。
IDWriteFontFile 接口对每个字体人脸或字体人脸引用进行下层。 这表示字体文件的位置,并具有两个组件:字体文件加载器和字体文件键。 字体文件加载程序(IDWriteFontFileLoader)用于根据需要打开文件,并返回包含数据的流(IDWriteFontFileStream)。 根据加载程序,数据可能位于本地文件路径、远程 URL 或内存缓冲区中。 该键是加载程序定义的值,它唯一标识加载程序上下文中的文件,使加载程序能够找到数据并为其创建流。
可以轻松地将自定义字体添加到自定义字体集中,而自定义字体又可用于筛选或组织字体信息,例如创建字体选取器用户界面。 字体集还可用于创建字体集合,以便在更高级别的 API 中使用,例如 IDWriteTextFormat 和 IDWriteTextLayout。 IDWriteFontSetBuilder 接口可用于创建自定义字体集,其中包含多个自定义字体。 它还可用于创建自定义字体集,用于混合自定义字体和系统提供的字体;或混合字体与实际数据的不同源 - 本地存储、远程 URL 和内存。
如前所述,字体人脸引用可能引用远程源的字体数据,但数据必须是本地数据才能获取可用于布局和呈现的字体人脸对象。 远程数据的下载由字体下载队列处理。 应用可以使用 IDWriteFontDownloadQueue 接口将请求排队以下载远程字体以启动下载过程,并在下载过程完成后注册 IDWriteFontDownloadListener 对象以采取措施。
对于此处所述的大多数接口,DirectWrite 提供了系统实现。 一个例外是 IDWriteFontDownloadListener 接口,应用在本地下载远程字体时实施特定于应用的作。 应用可能出于某种原因为某些其他接口提供自己的自定义实现,但仅在特定、更高级的情况下才需要这样做。 例如,应用需要提供 IDWriteFontFileLoader 接口的自定义实现,以处理使用 WOFF2 容器格式的本地存储中的字体文件。 下面将提供其他详细信息。
字体和字体文件格式
另一个有助于理解的关键概念是各个字体人脸与包含字体文件的字体文件之间的关系。 包含单个字体的 OpenType 字体文件(.ttf或.otf)的想法很熟悉。 但 OpenType 字体格式还允许 OpenType 字体集合(.ttc 或 .otc),这是包含多个字体的单个文件。 OpenType 集合文件通常用于与某些字体数据密切相关且具有相同值的大型字体:通过合并单个文件中的字体,可取消复制通用数据。 因此,字体人脸或字体人脸引用不仅需要引用字体文件(或等效数据源),而且还必须指定该文件中的字体索引,对于文件可能是集合文件的一般情况。
对于 Web 上使用的字体,字体数据通常打包为某些容器格式,WOFF 或 WOFF2,提供一些压缩的字体数据,以及一定程度的保护,防止盗版和违反字体许可证。 在功能上,WOFF 或 WOFF2 文件等效于 OpenType 字体或字体集合文件,但数据采用不同的格式进行编码,需要解压缩才能使用它。
某些 DirectWrite API 可能处理单个字体人脸,而其他 API 可以处理可能包含包含多个人脸的 OpenType 集合文件的文件。 同样,某些 API 仅处理原始 OpenType 格式的数据,而其他 API 可以处理打包的 WOFF 和 WOFF2 容器格式。 以下讨论中提供了这些详细信息。
字体集和字体集合
某些应用程序可以使用 IDWriteFontCollection 接口来处理字体。 字体集合和字体集之间存在直接对应关系。 每个字体可以容纳相同的字体,但它们向不同的组织展示它们。 从任何字体集合中,可以获取相应的字体集,反之亦然。
使用许多自定义字体时,最简单的方法是使用字体集生成器界面创建自定义字体集,然后在创建字体集后获取字体集合。 下面将详细介绍创建自定义字体集的过程。 若要从字体集中获取 IDWriteFontCollection1 接口,请使用 IDWriteFactory3::CreateFontCollectionFromFontSet 方法。
如果应用具有集合对象并且需要获取相应的字体集,则可以使用 IDWriteFontCollection1::GetFontSet 方法完成此作。
常见方案
本部分介绍涉及自定义字体集的一些最常见方案:
- 在本地文件系统的路径中使用任意字体创建自定义字体集。
- 使用存储在本地文件系统中的已知字体(可能与应用捆绑)创建自定义字体集。
- 使用 Web 上的已知远程字体创建自定义字体集。
- 使用加载到内存中的字体数据创建自定义字体集。
DirectWrite 自定义字体集示例中提供了这些方案的完整实现。 此示例还演示了一个用于处理以 WOFF 或 WOFF2 容器格式打包的字体数据的更高级方案,下面将对此进行讨论。
在本地文件系统中使用任意字体创建字体集
在本地存储中处理任意一组字体文件时,IDWriteFontSetBuilder1::AddFontFile 方法很方便,因为在单个调用中,它可以处理 OpenType 字体集合文件中的所有字体人脸,以及 OpenType 变量字体的所有实例。 这在 Windows 10 创意者更新(预览版本 15021 或更高版本)中可用,建议随时可用。
若要使用此方法,请使用以下过程。
- 1.首先创建 IDWriteFactory5 接口:
- 对于本地文件系统中的每个字体文件,请创建引用它的 IDWriteFontFile:
- 将所有文件添加到字体集生成器后,可以创建自定义字体集:
IDWriteFactory5* pDWriteFactory;
HRESULT hr = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory5),
reinterpret_cast<IUnknown**>(&pDWriteFactory)
);
2.使用工厂获取 IDWriteFontSetBuilder1 接口:
IDWriteFontSetBuilder1* pFontSetBuilder;
if (SUCCEEDED(hr))
{
hr = pDWriteFactory->CreateFontSetBuilder(&pFontSetBuilder);
}
IDWriteFontFile* pFontFile;
if (SUCCEEDED(hr))
{
hr = pDWriteFactory->CreateFontFileReference(pFilePath, /* lastWriteTime*/ nullptr, &pFontFile);
}
4. 使用 AddFontFile 方法将 IDWriteFontFile 对象添加到字体集生成器:
hr = pFontSetBuilder->AddFontFile(pFontFile);
如果在调用 CreateFontFileReference 中指定的文件路径 引用受支持的 OpenType 文件以外的其他内容,则调用 AddFontFile 将返回错误,DWRITE_E_FILEFORMAT。
IDWriteFontSet* pFontSet;
hr = pFontSetBuilder->CreateFontSet(&pFontSet);
如果应用需要在早于 Windows 10 创意者更新的 Windows 10 版本上运行,则 AddFontFile 方法将不可用。 可以通过创建 IDWriteFactory3 接口,然后使用 QueryInterface 尝试获取 IDWriteFactory5 接口来检测可用性:如果成功,则 IDWriteFontSetBuilder1 接口和 AddFontFile 方法也可用。
如果 AddFontFile 方法不可用,则必须使用 IDWriteFontSetBuilder::AddFontFaceReference 方法添加单个字体人脸。 若要允许包含多个人脸的 OpenType 字体集合文件,可以使用 IDWriteFontFile::Analyze 方法来确定文件中包含的人脸数。 此过程如下所示。
- 1.首先创建 IDWriteFactory3 接口:
- 使用工厂获取 IDWriteFontSetBuilder 接口:
- 对于每个字体文件,请创建 IDWriteFontFile,如下所示:
- 将所有人脸添加到字体集生成器后,创建自定义字体集,如上所示。
IDWriteFactory3* pDWriteFactory;
HRESULT hr = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory5),
reinterpret_cast<IUnknown**>(&pDWriteFactory)
);
IDWriteFontSetBuilder* pFontSetBuilder;
if (SUCCEEDED(hr))
{
hr = pDWriteFactory->CreateFontSetBuilder(&pFontSetBuilder);
}
IDWriteFontFile* pFontFile;
if (SUCCEEDED(hr))
{
hr = pDWriteFactory->CreateFontFileReference(pFilePath, /* lastWriteTime*/ nullptr, &pFontFile);
}
我们需要确定人脸数并创建单个 IDWriteFontFaceReference 对象,而不是将文件直接添加到字体集生成器。
4. 使用 Analyze 方法获取文件中的人脸数。
BOOL isSupported;
DWRITE_FONT_FILE_TYPE fileType;
UINT32 numberOfFonts;
hr = pFontFile->Analyze(&isSupported, &fileType, /* face type */ nullptr, &numberOfFonts);
Analyze 方法还将设置 isSupported 和 fileType 参数的值。 如果文件不是受支持的格式,则 isSupported 将为 FALSE,并且可以采取适当的作,例如忽略文件。
5. 循环访问 numberOfFonts 参数中设置的字体数。 在循环中,为每个文件/索引对创建 IDWriteFontFaceReference,并将其添加到字体集生成器。
for (uint32_t fontIndex = 0; fontIndex < numberOfFonts; fontIndex++)
{
IDWriteFontFaceReference* pFontFaceReference;
hr = pDWriteFactory->CreateFontFaceReference(pFontFile, fontIndex, DWRITE_FONT_SIMULATIONS_NONE, &pFontFaceReference);
if (SUCCEEDED(hr))
{
hr = pFontSetBuilder->AddFontFaceReference(pFontFaceReference);
}
}
可以设计应用,以便在 Windows 10 创意者更新上运行时使用首选的 AddFontFile 方法,但在早期 Windows 10 版本上运行时,请回退到使用 AddFontFaceReference 方法。 测试 IDWriteFactory5 接口的可用性,如前所述,然后相应地分支。 DirectWrite 自定义字体集示例演示了此方法。
在本地文件系统中使用已知字体创建字体集
如上所述,字体集中的每个字体人脸引用都与某些信息属性(如家族名称和字体粗细)相关联。 使用上面列出的 API 调用将自定义字体添加到字体集生成器时,这些信息属性直接从实际字体数据(在添加字体时读取)获取。 但是,在某些情况下,如果应用有另一个有关字体的信息来源,它可能希望为这些属性提供自己的自定义值。
例如,假设应用捆绑了一些字体,用于在应用中显示特定用户界面元素。 有时,例如使用新的应用版本,应用使用这些元素的特定字体可能需要更改。 如果应用对特定字体进行了编码引用,则用另一种字体替换一个字体将需要更改其中每个引用。 相反,如果应用使用自定义属性根据呈现的元素或文本类型分配功能别名,请将每个别名映射到一个位置中的特定字体,然后在创建和作字体的所有上下文中使用别名,然后将一个字体替换为另一个字体,只需更改别名映射到特定字体的位置。
调用 IDWriteFontSetBuilder::AddFontFaceReference 方法时,可以分配信息属性的自定义值。 执行此作的方法如下所示:这可在任何 Windows 10 版本上使用。
如上所示,首先获取 IDWriteFactory3 和 IDWriteFontSet 接口。 要添加的每个自定义字体人脸,请创建 IDWriteFontFaceReference,如上所示。 在添加到字体集生成器之前(在步骤 5 中所示的循环中),但是,应用定义了要使用的自定义属性值。
使用 DWRITE_FONT_PROPERTY 结构的数组定义一组自定义属性值。 每个属性标识 DWRITE_FONT_PROPERTY_ID 枚举中的特定属性,以及要使用的相应属性值。
请注意,所有属性值都分配为字符串。 如果以后可能会向用户显示这些值,则可以为不同语言设置给定属性的备用值,但这不是必需的。 另请注意,如果应用设置了任何自定义属性值,则只有指定的值才会在字体集中使用;DirectWrite 不会直接从字体派生任何值,以获取字体集中使用的信息属性。
以下示例为三个信息属性定义自定义值:系列名称、全名和字体粗细。
DWRITE_FONT_PROPERTY props[] =
{
{ DWRITE_FONT_PROPERTY_ID_FAMILY_NAME, L"My Icon Font", L"en-US" },
{ DWRITE_FONT_PROPERTY_ID_FULL_NAME, L"My Icon Font", L"en-US" },
{ DWRITE_FONT_PROPERTY_ID_WEIGHT, L"400", nullptr }
};
定义字体的所需属性值数组后,调用 AddFontFaceRefence,传递属性数组以及字体人脸引用。
hr = pFontSetBuilder->AddFontFaceReference(pFontFaceReference, props, ARRAYSIZE(props));
将所有自定义字体面添加到字体集生成器及其自定义属性后,创建自定义字体集,如上所示。
使用 Web 上的已知远程字体创建自定义字体集
自定义属性对于使用远程字体非常重要。 每个字体人脸引用必须具有一些信息属性来描述字体,并将其与其他字体区分开来。 由于远程字体的字体数据不是本地的,DirectWrite 无法直接从字体数据派生属性。 因此,向字体集生成器添加远程字体时,必须显式提供属性。
将远程字体添加到字体集的 API 调用序列类似于前面方案所述的序列。 但是,由于字体数据是远程的,因此读取实际字体数据所涉及的作将不同于在本地存储中使用文件时的作。 在这种情况下,Windows 10 创意者更新中添加了一个新的较低级别的接口,IDWriteRemoteFontFileLoader。
若要使用远程字体文件加载程序,必须先向 DirectWrite 工厂注册它。 只要正在使用与加载程序关联的字体,加载程序就会由应用保留。 一旦字体不再使用,在工厂销毁之前的某些时间点,必须取消注册加载程序。 这可以在拥有 loader 对象的类的析构函数中完成。 以下步骤如下所示。
使用远程字体创建自定义字体集的方法如下所示:这需要 Windows 10 创意者更新。
- 1.创建 IDWriteFactory5 接口,如上所示。
2.创建 IDWriteFontSetBuilder 接口,如下所示。
3.使用工厂获取 IDWriteRemoteFontFileLoader。
- 定义字体的自定义属性,如上所示。
- 将字体人脸引用以及自定义属性添加到字体集生成器,如上所示。
- 将所有字体添加到字体集生成器后,创建字体集,如上所示。
- 在某些情况下,不再使用远程字体时,请注销远程字体文件加载程序。
IDWriteRemoteFontFileLoader* pRemoteFontFileLoader;
if (SUCCEEDED(hr))
{
hr = pDWriteFactory->CreateHttpFontFileLoader(
/* referrerURL */ nullptr,
/* extraHeaders */ nullptr,
&pRemoteFontFileLoader
);
}
这会返回远程字体文件加载程序接口的系统提供的实现,该接口能够处理 HTTP 交互,以便代表应用下载字体数据。 如果字体服务或服务是字体源,则可以指定引用者 URL 或额外标头。
重要
安全说明:尝试提取远程字体时,攻击者可能会欺骗将要调用的预定服务器。 在这种情况下,目标 URL 和引用方 URL 和标头详细信息将披露给攻击者。 应用开发人员负责缓解此风险。 建议使用 HTTPS 协议,而不是 HTTP。
单个远程字体文件加载程序可用于多个字体,但如果从对引用者 URL 或额外标头有不同要求的多个服务获取字体,则可以使用不同的加载程序。
4.向工厂注册远程字体文件加载程序。
if (SUCCEEDED(hr))
{
hr = pDWriteFactory->RegisterFontFileLoader(pRemoteFontFileLoader);
}
从此开始,创建自定义字体集的步骤类似于已知本地字体文件所述的步骤,但有两个重要例外。 首先,IDWriteFontFile 对象是使用远程字体文件加载程序接口而不是工厂创建的。 其次,无法使用 Analyze 方法,因为字体数据不是本地的。 相反,应用必须知道远程字体文件是否为 OpenType 字体集合文件,如果是,则它必须知道它将使用的集合中的字体,以及每个字体的索引。 因此,其余步骤如下所示。
5. 对于每个远程字体文件,请使用远程字体文件加载程序界面创建 IDWriteFontFile,并指定访问字体文件所需的 URL。
IDWriteFontFile* pFontFile;
hr = pRemoteFontFileLoader->CreateFontFileReferenceFromUrl(
pDWriteFactory,
/* baseUrl */ L"https://github.com/",
/* fontFileUrl */ L"winjs/winjs/blob/master/src/fonts/Symbols.ttf?raw=true",
&pFontFile
);
请注意,可以在 fontFileUrl 参数中指定完整的 URL,也可以将其拆分为基部分和相对部分。 如果指定了基 URL,则 baseUrl 和 fontFileUrl 值的串联必须提供完整的 URL — DirectWrite 不会提供任何其他分隔符。
重要
安全性/性能说明:尝试提取远程字体时,不能保证 Windows 将收到来自服务器的响应。 在某些情况下,服务器可能会响应无效相对 URL 的文件找不到错误,但如果服务器收到多个无效请求,则停止响应。 如果服务器未响应,Windows 最终将超时,但如果启动多个提取,则可能需要几分钟时间。 应执行一些作,以确保在进行调用时 URL 有效。
另请注意,URL 可以指向原始 OpenType 字体文件(.ttf、.otf、.ttc、.otc),但它也可以指向 WOFF 或 WOFF2 容器文件中的字体。 如果引用了 WOFF 或 WOFF2 文件,则远程字体文件加载程序的 DirectWrite 实现将自动从容器文件解压缩字体数据。
6. 对于要使用的远程字体文件中的每个字体人脸索引,请创建 IDWriteFontFaceReference。
IDWriteFontFaceReference* pFontFaceReference;
hr = pDWriteFactory->CreateFontFaceReference(pFontFile, /* faceIndex */ 0, DWRITE_FONT_SIMULATIONS_NONE, &pFontFaceReference);
hr = pDWriteFactory->UnregisterFontFileLoader(pRemoteFontFileLoader);
创建具有自定义远程字体的自定义字体集后,该字体集包含远程字体的引用和信息属性,但实际数据仍然很远程。 DirectWrite 对远程字体的支持允许在字体集中维护字体人脸引用,并且要选择用于布局和呈现的字体,但在实际需要使用它之前,不会下载实际数据,例如何时执行文本布局。
应用可以通过请求 DirectWrite 下载字体数据,然后在开始对字体进行任何处理之前等待确认成功下载,从而采取前期方法。 但是,网络下载意味着一些不可预知的持续时间延迟,成功也不确定。 因此,通常最好采用不同的方法,允许最初使用已本地的备用或回退字体来完成布局和呈现,同时请求下载所需的远程字体,然后在下载所需字体后更新结果。
若要请求在使用之前下载整个字体,可以使用 IDWriteFontFaceReference::EnqueueFontDownloadRequest 方法。 如果字体非常大,则处理特定字符串只需要一部分数据。 DirectWrite 提供了其他方法,可用于请求特定内容所需的字体数据部分,EnqueueCharacterDownloadRequest 和 EnqueueGlyphDownloadRequest。
假设要在应用中采用的方法是允许最初使用本地、备用或回退字体完成处理。 IDWriteFontFallback::MapCharacters 方法可用于标识本地回退字体,它还会自动排队请求下载首选字体。 此外,如果使用 IDWriteTextLayout,并且布局中的部分或全部文本使用远程字体引用进行格式化,则 DirectWrite 将自动使用 MapCharacters 方法获取本地回退字体,并排队请求下载远程字体数据。
DirectWrite 为每个工厂维护字体下载队列,并使用上述方法发出的请求将添加到该队列。 可以使用 IDWriteFactory3::GetFontDownloadQueue 方法获取字体下载队列。
如果发出下载请求但字体数据已本地,则会导致 no-op:下载队列中不会添加任何内容。 应用可以通过调用 IDWriteFontDownloadQueue::IsEmpty 方法来检查队列为空还是存在挂起的下载请求。
将远程字体请求添加到队列后,必须启动下载过程。 当 IDWriteTextLayout中使用远程字体时,当应用调用 IDWriteTextLayout 强制布局或呈现作(如 GetLineMetrics 或 Draw 方法)时,将自动启动下载。 在其他方案中,应用必须通过调用 idWriteFontDownloadQueue::BeginDownload直接启动下载。
下载完成后,应用将执行适当的作-继续执行挂起的作,或重复最初使用回退字体完成的作。 (如果使用 DirectWrite 的文本布局,则可以 IDWriteTextLayout3::InvalidateLayout 来清除使用回退字体计算的临时结果。为了使应用在下载过程完成并采取适当作时收到通知,应用必须提供 IDWriteFontDownloadListener 接口的实现,并将其传递到 BeginDownload 调用中。
重要
安全性/性能说明:尝试提取远程字体时,不能保证 Windows 将收到来自服务器的响应。 如果服务器没有响应,Windows 最终将超时,但如果提取了多个远程字体但失败,这可能需要几分钟时间。 BeginDownload 调用将立即返回。 应用不应在等待调用 IDWriteFontDownloadListener::D ownloadCompleted 时阻止 UI。
DirectWrite 自定义字体集示例示例以及 DirectWrite 可下载字体示例示例中可以看到这些与 DirectWrite 字体下载队列和 IDWriteFontDownloadListener 接口的这些交互的示例实现。
使用加载到内存中的字体数据创建自定义字体集
与 Web 上的远程文件不同,从字体文件中读取数据的低级别作与 Web 上的远程文件不同,同样,加载到内存缓冲区中的字体数据也是如此。 Windows 10 创意者更新 IDWriteInMemoryFontFileLoader中添加了用于处理内存中字体数据的新低级别接口。
与远程字体文件加载程序一样,必须先向 DirectWrite 工厂注册内存中字体文件加载程序。 只要正在使用与加载程序关联的字体,加载程序就会由应用保留。 一旦字体不再使用,在工厂销毁之前的某些时间点,必须取消注册加载程序。 这可以在拥有 loader 对象的类的析构函数中完成。 以下步骤如下所示。
如果应用具有有关数据所表示的字体人脸的单独信息,则可以向具有指定自定义属性的字体集生成器添加单个字体人脸引用。 但是,由于字体数据位于本地内存中,因此不需要这样做;DirectWrite 将能够直接读取数据来派生属性值。
DirectWrite 假定字体数据采用原始 OpenType 格式,相当于 OpenType 文件(.ttf、.otf、.ttc、.otc),但在内存中而不是磁盘上。 数据不能采用 WOFF 或 WOFF2 容器格式。 数据可以表示 OpenType 字体集合。 如果未使用自定义属性,则可以使用 IDWriteFontSetBuilder1::AddFontFile 方法在单个调用中添加数据中的所有字体人脸。
内存中方案的一个重要考虑因素是数据的生存期。 如果指向缓冲区的指针提供给 DirectWrite,但没有明确指示存在所有者,则 DirectWrite 会将数据的副本复制到它将拥有的新内存缓冲区中。 为了避免复制数据和额外的内存分配,应用可以传递实现 IUnknown 的数据所有者对象,并拥有包含字体数据的内存缓冲区。 通过实现此接口,DirectWrite 可以添加到对象的 ref 计数,从而确保拥有数据的生存期。
使用内存中字体数据创建自定义字体集的方法如下所示:这需要 Windows 10 创意者更新。 这将假定应用实现的数据所有者对象,该对象实现 IUnknown,并且还具有返回指向内存缓冲区和缓冲区大小的指针的方法。
- 1.创建 IDWriteFactory5 接口,如上所示。
2. 创建 [**IDWriteFontSetBuilder1**](/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontsetbuilder1) 接口,如下所示。
3.使用工厂获取 IDWriteInMemoryFontFileLoader。
IDWriteInMemoryFontFileLoader* pInMemoryFontFileLoader;
if (SUCCEEDED(hr))
{
hr = pDWriteFactory->CreateInMemoryFontFileLoader(&pInMemoryFontFileLoader);
}
这会返回内存中字体文件加载程序接口的系统提供的实现。
4. 向工厂注册内存中字体文件加载程序。
if (SUCCEEDED(hr))
{
hr = pDWriteFactory->RegisterFontFileLoader(pInMemoryFontFileLoader);
}
5. 对于每个内存中字体文件,请使用内存中字体文件加载程序创建 IDWriteFontFile。
IDWriteFontFile* pFontFile;
hr = pInMemoryFontFileLoader->CreateInMemoryFontFileReference(
pDWriteFactory,
pFontDataOwner->fontData /* returns void* */,
pFontDataOwner->fontDataSize /* returns UINT32 */,
pFontDataOwner /* ownerObject, owns the memory with font data and implements IUnknown */,
&pFontFile
);
6. 使用 AddFontFile 方法将 IDWriteFontFile 对象添加到字体集生成器,如上所示。 如果需要,应用可以根据 IDWriteFontFile创建单个 IDWriteFontFaceReference 对象,可以选择为每个字体人脸引用定义自定义属性,然后使用 AddFontFaceReference 方法将字体人脸引用添加到字体集中,如下所示。
7. 将所有字体添加到字体集生成器后,创建自定义字体集,如上所示。
8. 在内存中字体不再使用时,请注销内存中字体文件加载程序。
hr = pDWriteFactory->UnregisterFontFileLoader(pInMemoryFontFileLoader);
高级方案
某些应用可能具有需要比上述更高级处理的特殊要求。
组合字体集
某些应用可能需要创建一个字体集,该字体集包含其他字体集中的一些项目组合。 例如,应用可能想要创建一个字体集,该字体集将系统上安装的所有字体与一系列自定义字体组合在一起,或者将已安装的字体与其他字体组合在一起。 DirectWrite 具有 API 来支持字体集的作和组合。
若要合并两个或多个字体集,IDWriteFontSetBuilder::AddFontSet 方法将给定字体集中的所有字体添加到一次调用中的字体集生成器。 如果新字体集中仅需要现有字体集中的某些字体,则 IDWriteFontSet::GetMatchingFonts 方法可用于派生已筛选为仅包含匹配指定属性的字体的新字体集对象。 这些方法提供了一种简单的方法来创建自定义字体集,将两个或多个现有字体集中的字体组合在一起
使用本地 WOFF 或 WOFF2 字体数据
如果应用在本地文件系统或内存缓冲区中具有字体文件,但它们使用 WOFF 或 WOFF2 容器格式,则 DirectWrite (Windows 10 Creator Update 或更高版本)提供了一种解压缩容器格式的方法,IDWriteFactory5::UnpackFontFile,这将返回 IDWriteFontFileStream。
但是,应用需要一种方法来将 IDWriteFontFileStream 到字体文件加载程序对象中。 执行此作的一种方法是创建包装流的自定义 IDWriteFontFileLoader 实现。 与其他字体文件加载程序一样,必须在使用前注册,并在工厂超出范围之前注销。
如果自定义加载程序还将用于原始(未打包)字体文件,则应用还需要提供用于处理这些文件的 IDWriteFontFileStream 接口的自定义实现。 但是,使用上面讨论的 API 处理原始字体文件的方法更简单。 可以通过对打包字体文件与原始字体文件使用单独的代码路径来避免自定义流实现的需求。
创建自定义字体文件加载程序对象后,打包的字体文件数据将通过特定于应用的方式添加到加载程序。 加载程序可以处理多个字体文件,每个字体文件都使用不透明到 DirectWrite 的应用定义键进行标识。 将打包的字体文件添加到加载程序后,将使用 IDWriteFactory::CreateCustomFontFileReference 方法基于给定键标识的字体数据的加载程序获取 IDWriteFontFile。
字体数据的实际解压缩可以完成,因为字体被添加到加载程序,但也可以在 IDWriteFontFileLoader::CreateStreamFromKey 方法中进行处理,DirectWrite 在首次需要读取字体数据时将调用该方法。
创建 IDWriteFontFile 对象后,将字体添加到自定义字体集的剩余步骤将如上所述。
DirectWrite 自定义字体集示例说明了使用此方法的实现。
将 DirectWrite 远程字体机制与自定义低级别网络实现配合使用
用于处理远程字体的 DirectWrite 机制可以分为更高级别的机制,其字体集包括远程字体的字体引用、检查字体数据的区域,以及管理字体下载请求的队列,以及处理实际下载的较低级别机制。 某些应用可能想要利用更高级别的远程字体机制,但也需要自定义网络交互,例如使用 HTTP 以外的协议与服务器通信。
在这种情况下,应用需要创建 IDWriteRemoteFontFileLoader 接口的自定义实现,以所需的方式与其他较低级别的接口进行交互。 应用还需要提供这些较低级别的接口的自定义实现:IDWriteRemoteFontFileStream,IDWriteAsyncResult。 这三个接口具有 DirectWrite 在下载作期间将调用的回调方法。
调用 IDWriteFontDownloadQueue::BeginDownload 时,DirectWrite 将对远程字体文件加载程序进行查询,了解数据的区域,并请求远程流。 如果数据不是本地数据,它将调用流的 BeginDownload 方法。 流实现不应阻止该调用,但应立即返回,并传递 IDWriteAsyncResult 对象,该对象提供等待句柄 DirectWrite 将用于等待异步下载作。 自定义流实现负责处理远程通信。 完成事件发生后,DirectWrite 将调用 IDWriteAsyncResult::GetResult 来确定作的结果。 如果结果成功,则预计后续 ReadFragment 调用所下载范围的流将成功。
重要
安全/性能说明:尝试提取远程字体时,攻击者通常会欺骗被调用的目标服务器,或者服务器可能无法响应。 如果要实现自定义网络交互,则可能比处理第三方服务器时更好地控制缓解措施。 但是,由你考虑适当的缓解措施,以避免信息泄露或拒绝服务。 建议使用安全协议(如 HTTPS)。 此外,应生成一些超时值,以便返回到 DirectWrite 的事件句柄最终会设置。
早期 Windows 版本的支持方案
在早期版本的 Windows 上,DirectWrite 可以支持已描述的方案,但需要使用 Windows 10 之前的更有限的 API 在应用部分实现更多的自定义实现。 有关详细信息,请参阅 自定义字体集合(Windows 7/8)。