9

全面披露:我正在开发我的 libui GUI 框架的文本 API。这包含了 Windows 上的 DirectWrite、OS X 上的 Core Text 和其他 Unix 上的 Pango(使用 HarfBuzz 进行 OpenType 整形)。我要指定的文本格式属性之一是要使用的 OpenType 特性的集合,这三个特性都提供;DirectWrite 是IDWriteTypography.

现在,当您使用这些库绘制一些文本时,默认情况下您会启用一些有用的 OpenType 功能,例如标准连字 ( liga),如 f+i 连字。我认为这是特定于字体的,但事实证明这是特定于正在成形的文本的脚本。Microsoft 为 OpenType 支持的所有脚本提供了指南(在“特定于脚本的开发”下),我可以看到在 HarfBuzz 本身中完成这一切以确认它的相当复杂的逻辑。

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

如果您明确指定 IDWriteTypography 对象,DirectWrite 会删除默认的 OpenType 功能

可以在此处找到产生此输出的程序。

显然,我的第一个选择是询问如何获得 DirectWrite 的默认功能。不过,有人已经在这个网站上这样做了,答案似乎是“不”。

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

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

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

DirectWrite 提供了一个接口,该接口IDWriteTextAnalyzer提供了执行整形的工具。我可以使用它,但似乎脚本数据以DWRITE_SCRIPT_ANALYSIS结构返回,并且脚本 ID 的描述说“编写系统脚本的从零开始的索引表示。”。

这没有帮助,所以我编写了一个程序来转储我输入的文本的脚本编号。在输入字符串上运行它

لللللللللللللاااااااااالا 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 标头中的任何内容匹配:如果在任何 API 中定义了阿拉伯文、拉丁文或西里尔文编号,它们与这些不匹配。即使我确实得到了脚本和脚本编号之间的映射,这仍然没有给我提供应用词内特征的数据。

Uniscribe呢?好吧,等效SCRIPT_ANALYSIS类型的文档说它的脚本 ID 是一个“[opaque] 值”,它的“这个成员的值是未定义的,应用程序不应该依赖于它的值从一个版本到下一个版本是相同的”。虽然我可以获得LANG_ENGLISH一个语言代码来识别脚本,但除了“西方”(拉丁语?)脚本之外,仍然没有定义值。DirectWrite 值是否与 Uniscribe 相同?似乎我至少可以通过查看fLinkBeforeandfLinkAfter字段来确定单词的初始和最终状态,但这足以正确应用每个脚本的属性吗?

HarfBuzz 确实有一个实验性的 DirectWrite 后端,它不打算被实际程序使用;我还不确定它是否具有我上面指定的相同的功能破坏。如果我发现了,我会在这里更新这部分。

最后,如果我以类似 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>

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

kaxaml 显示我想要的效果很好

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

在这一点上,我完全迷失了。我希望至少可以跨平台进行预测,而且我不确定程序甚至应该如何,更不用说直接使用 OpenType 功能了。我是否对文本布局 API 抱有不好的期望?如果需要,我是否必须放弃 IDWriteTextLayout 并自己进行所有文本整形和布局?

还是我必须放弃普通 Windows 7 支持并升级到平台更新 DirectWrite 功能集?甚至完全是Windows 7?

4

2 回答 2

4

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

但是,首先,我要说这同样适用于 Uniscribe 和 DirectWrite

事实证明,无论我使用什么功能集,DirectWrite 始终提供一组默认的 OpenType 功能!情况是,提供的默认功能列表会有所不同,具体取决于我是否加载自己的功能,以及取决于整形引擎。对于latn水平书写模式的脚本和英语,这是通过“通用引擎”完成的。

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

locl
ccmp
rlig
rclt
calt
liga
clig

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

locl
ccmp
rclt
rlig
mark
mkmk
dist

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

于 2017-07-23T16:53:13.497 回答
1

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

如果我希望能够在默认值之上添加印刷功能,我是否必须在我的代码中完全放弃使用 IDWriteTextLayout?

这取决于。如果 IDWriteTextLayout 界面在所有方面都非常适合您的项目任务,除了 DirectWrite 默认排版功能的易于变化之外,请了解您应该了解的排版并创建一个适合您需要的 IDWriteTypography 实例。为程序开发自定义文本布局可能需要大量时间和精力,特别是如果程序应该呈现双向文本、复杂脚本、内联对象等。

您的项目任务可能需要开发文本布局引擎,而不仅仅是控制渲染文本中使用的排版功能。例如,您的经理/客户可能会要求实施定制的换行机会或字形提前对齐算法。在这种情况下,您将实现 IDWriteTextAnalizer::GetGlyphs 方法。此方法具有参数 DWRITE_TYPOGRAPHIC_FEATURES ** features、const UINT32 * featureRangeLengths、UINT32 featureRanges,并且此参数使您能够为要呈现的文本范围取代一组“默认”排版功能(请参阅我对另一个问题的回答What IDWriteTextLayout 使用的默认排版设置是什么?)。只会更改受影响的功能;其他功能有其“默认”值。此外,如果您在下一个文本范围的 GetGlyphs 调用中省略此参数(例如,使用 NULL、NULL、0 的值),则在上一个 GetGlyphs 调用中更改的特征将不会被下一个范围的调用更改。

等效 SCRIPT_ANALYSIS 类型的文档说它的脚本 ID 是一个“[opaque] 值”,其“此成员的值是未定义的,应用程序不应依赖于它的值从一个版本到下一个版本是相同的”。虽然我可以获得一个语言代码来识别脚本,但除了“西方”(拉丁语?)脚本的 LANG_ENGLISH 之外,仍然没有定义值。

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

这可能是题外话,但我冒着假设“Unicode 脚本 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 脚本 ID”值;我们需要脚本属性,而不是脚本 ID 或缩写。API 是自洽的,因为它为文本范围提供了实际的脚本属性,当我们将来自 AnalyzeScript 调用的数字传递给 GetScriptProperties 方法时。

于 2018-02-19T10:57:43.787 回答