如何平衡 script-oriented OpenType 功能与使用 DirectWrite 的其他 OpenType 功能?

How do I balance script-oriented OpenType features with other OpenType features using DirectWrite?

完全披露:我正在处理我的 libui GUI 框架的文本 API。这在 Windows 上包装了 DirectWrite,在 OS X 上包装了 Core Text,在其他 Unix 上包装了 Pango(它使用 HarfBuzz 进行 OpenType 整形)。我要指定的文本格式化属性之一是要使用的 collection OpenType 功能,这三个属性都提供; DirectWrite 的是 IDWriteTypography.

现在,当您使用这些库绘制一些文本时,默认情况下您会启用一些有用的 OpenType 功能,例如标准连字 (liga),如 f+i 连字。我以为这是 font-specific,但事实证明这是特定于正在塑造的文本的脚本。 Microsoft provides guidelines for all the scripts supported by OpenType(在 "Script-specific Development" 下),我可以在 HarfBuzz 本身中看到相当复杂的逻辑来确认它。

在 Core Text 和 Pango 上,如果我启用其他属性,它们将被添加到这些默认值之上。但是对于 DirectWrite,特别是 IDWriteTextLayout::SetTypography(),这样做 会删除默认设置 :

可以找到生成此输出的程序 here

显然我的第一个选择是询问如何获得 DirectWrite 的默认功能。

我猜 DirectWrite 允许我完全控制应用于某些文本的功能列表。这很好,除了我不能对其他 API 执行此操作,除非我以某种方式明确禁用默认功能!当然,我不知道这个列表是否会改变,所以硬编码可能不是最好的主意。

即使硬编码是一种选择,我也可以为每个脚本获取 HarfBuzz 的列表,但是 a) it's rather complicated b) 脚本有多种可能的整形器,这取决于(我认为)版本兼容性(例如,缅甸)。

那么为什么不使用 HarfBuzz 的列表来重新创建 DirectWrite 的默认功能列表呢?它似乎无论如何都想对其他整形器准确,所以这应该有效,对吧?好吧,我需要做两件事:弄清楚要使用什么脚本,并弄清楚要在脚本的哪些字符上使用哪些属性,其中字符在单词中的位置很重要。

DirectWrite 提供了一个接口 IDWriteTextAnalyzer,它提供了执行整形的工具。我可以使用它,但脚本数据似乎在 DWRITE_SCRIPT_ANALYSIS structure 中返回,脚本 ID 的描述显示 "The zero-based index representation of writing system script.".

这没有用,所以我写了a program to just dump the script numbers for text I type in。 运行 它在输入字符串上

لللللللللللللاااااااااالا abcd محمد ابن بطوطة‎‎ Отложения датского яруса

产生输出

0 - 26 script 3 shapes 0
26 - 5 script 49 shapes 0
31 - 14 script 3 shapes 0
45 - 2 script 1 shapes 1
47 - 25 script 22 shapes 0

我无法将这些脚本编号与 Windows headers 中的任何内容匹配:如果在任何 API 中定义了阿拉伯文、拉丁文或西里尔文的编号,它们不匹配这些。即使我确实得到了脚本和脚本编号之间的映射,这仍然没有给我应用 intra-word 功能的数据。

Uniscribe 怎么样?好吧,the equivalent SCRIPT_ANALYSIS type 的文档说它的脚本 ID 是一个 "value for this member is undefined and applications should not rely on its value being the same from one release to the next" 的“[不透明] 值”。虽然我 可以 获得一个语言代码来识别脚本,但对于 "Western"(拉丁语?)脚本,除了 LANG_ENGLISH 之外仍然没有定义的值。 DirectWrite 值是否与 Uniscribe 值相同?看起来我至少可以通过查看 fLinkBeforefLinkAfter 字段来计算单词的初始和最终状态,但这是否足以正确应用属性 per-script?

HarfBuzz 确实有一个实验性的 DirectWrite 后端 isn't intended to be used by real programs;我还不确定它是否与我上面指定的 feature-clobbering 相同。如果我发现了,我会在这里更新这部分。

最后,如果我在 kaxaml 中输入以下与上面第一个等效的测试用例:

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid>  
  <FlowDocumentPageViewer>
  <FlowDocument FontFamily="Constantia" FontSize="48">
  <Paragraph>
  afford afire aflight 1/4<LineBreak/>
  <Run Typography.Fraction="1">afford afire aflight 1/4</Run>
  </Paragraph>
  </FlowDocument>
  </FlowDocumentPageViewer>
  </Grid>
</Page>

我看到连字应用正确,即使在后一种情况下也是如此:

(末尾的分数只是为了证明那个属性被应用了。)如果我假设XAML使用DirectWrite,那么这证明了我的第一个选择(简单地将我的自定义属性覆盖在默认值之上)应该是可能的...(我做出这个假设是基于 XAML 提供了惊人相似的 API 用于绘制 2D 图形的 Direct2D,并且填充了很多漏洞,我不得不手动编写大量胶水代码来使用 vanilla Direct2D 做同样的事情,所以我假设 XAML 中的任何可能是可以使用 Direct2D,并通过扩展 DirectWrite,因为它们在技术上是一起引入的...)

此时我完全迷路了。我希望至少跨平台是可预测的,而且我不确定程序应该如何直接或不直接使用 OpenType 功能,更不用说了。我对文本布局 APIs 抱有不好的期望吗?如果需要,我是否必须放弃 IDWriteTextLayout 并自己完成所有文本整形和布局?

或者我是否必须放弃 vanilla Windows 7 支持并升级到平台更新 DirectWrite 功能集?甚至 Windows 7 个?

在与 Peter Sikking 和 Ebrahim Byagowi 讨论后,我调试了一个我快速构建的更通用的程序来测试东西,我弄清楚了内部发生的事情。

不过,首先我要说这同样适用于 Uniscribe 和 DirectWrite

事实证明,无论我使用什么功能集,DirectWrite 总是提供一组默认的 OpenType 功能!情况是提供的默认功能列表根据我是否加载自己的功能以及整形引擎而有所不同。对于水平书写模式下的 latn 脚本和英语,这是通过 "generic engine".

完成的

如果我不提供任何功能,通用引擎将加载特定于脚本的功能。对于横向latn,这个列表是

locl
ccmp
rlig
rclt
calt
liga
clig

如果我提供功能,通用引擎将对所有脚本使用相同的默认列表:

locl
ccmp
rclt
rlig
mark
mkmk
dist

所以我不知道该怎么办。我可能只在 libui 代码中提供 liga 和其他几个我自己(当然标记为 HACK),但这仍然很奇怪。我也不确定动机是什么。无论哪种方式,这都解释了我所看到的行为。

假设您的一般问题是关于编程或至少涉及编程,我将尝试回答您的一些疑问句。

would I have to drop the use of IDWriteTextLayout entirely in my code if I want to be able to add typographical features on top of the defaults?

视情况而定。如果 IDWriteTextLayout 接口在所有方面都非常适合您的项目任务,除了 DirectWrite 默认版式功能的易变性之外,请了解您应该了解的版式并创建适合您需要的 IDWriteTypography 实例。为程序开发自定义文本布局可能需要大量时间和精力,尤其是当程序要呈现双向文本、复杂脚本、内联对象等时。

您的项目任务可能需要开发文本布局引擎,而不仅仅是控制呈现文本中使用的排版功能。例如,您的 manager/customer 可能要求实施自定义的换行机会或字形提前对齐算法。在此场景中,您将实现一个 IDWriteTextAnalizer::GetGlyphs 方法。此方法具有参数 DWRITE_TYPOGRAPHIC_FEATURES ** features、const UINT32 * featureRangeLengths、UINT32 featureRanges,此参数使您能够取代一组 "default" 排版功能以呈现一系列文本(请参阅我的回答另一个问题 )。只有受影响的特征才会被改变;其他特征有它们的 "default" 值。此外,如果您在下一个文本范围的 GetGlyphs 调用中省略此参数(例如,使用 NULL、NULL、0 值),则在上一个 GetGlyphs 调用中更改的功能将不会被下一个范围的调用更改。

the documentation for the equivalent SCRIPT_ANALYSIS type says that its script ID is an "[opaque] value" whose "value for this member is undefined and applications should not rely on its value being the same from one release to the next". And while I can get a language code to identify the script by, there's still no defined value other than LANG_ENGLISH for "Western" (Latin?) scripts.

严格来说,这不是疑问句,但我猜你对这些 Unicode 脚本 ID 的定义方式以及如何使用定义如此模糊的结构和常量的 API 不满意。

这可能与主题无关,但我冒险假设 "Unicode script ID" 值的来源。截至 2010 年 7 月 17 日,Unicode, Inc. 发布了 Unicode 6.0 版本。标准包含文件 http://www.unicode.org/Public/6.0.0/ucd/PropertyValueAliases.txt,其中一个部分包含脚本列表。名单是这样的:

   # Script (sc)

   sc ; Arab      ; Arabic
   sc ; Armi      ; Imperial_Aramaic
   etc.

在此列表中,阿拉伯文字排名第 1,西里尔文字排名第 20,拉丁文字排名第 47。此外,我在别处看到这个列表以脚本 Common 和 Inherited 开头。它将阿拉伯文字排在第 3 位,西里尔文字排在第 22 位,拉丁文字排在第 49 位。这些序数你很熟悉吧?

幸运的是,我们不需要依赖 "Unicode script ID" 值;我们需要脚本属性,而不是脚本 ID 或缩写。 API 是 self-consistent,因为当我们将从 AnalyzeScript 调用派生的数字传递给 GetScriptProperties 方法时,它给出了文本范围的实际脚本属性。