如何使用DirectWrite平衡面向脚本的OpenType功能与其他OpenType功能?

时间:2017-06-18 03:47:33

标签: winapi directx opentype directwrite uniscribe

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

现在,当您使用这些库绘制一些文本时,默认情况下,您将启用一些有用的OpenType功能,例如标准连字(liga),如f + i连字。我认为这是特定于字体的,但事实证明这是特定于正在形成的文本的脚本。 Microsoft provides guidelines for all the scripts supported by OpenType(在"特定于脚本的开发"),我可以看到相当复杂的逻辑,在HarfBuzz本身完成所有操作以确认它。

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

DirectWrite removes default OpenType features if you explicitly specify a IDWriteTypography object

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

显然,我的第一个选择是询问如何获取DirectWrite的默认功能。 Someone did so already on this site, though, and the answer seems to be "no".

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

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

那么为什么不使用HarfBuzz的列表来重新创建DirectWrite的默认功能列表呢?它似乎想要对其他塑造者准确,所以这应该工作,对吧?好吧,我需要做两件事:找出要使用的脚本,并找出哪些属性用于脚本中哪个字符的位置对于脚本的位置很重要。

DirectWrite提供了一个接口IDWriteTextAnalyzer,它提供了执行整形的工具。我可以使用它,但似乎脚本数据在DWRITE_SCRIPT_ANALYSIS structure中返回,脚本ID的描述表示&#34;编写系统脚本的从零开始的索引表示。&#34;。< / p>

这没有用,所以我写了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标题中的任何内容相匹配:如果在任何API中都有阿拉伯语,拉丁语或西里尔语的定义数字,则它们不匹配。即使我确实得到了脚本和脚本编号之间的映射,但仍然没有给我提供应用字内功能的数据。

Uniscribe怎么样?好吧,the equivalent SCRIPT_ANALYSIS type的文档说它的脚本ID是&#34; [opaque]值&#34;该成员的&#34;值未定义,应用程序不应依赖于从一个发行版到下一个发行版的值相同的情况。虽然我可以获取语言代码来识别脚本,但除了LANG_ENGLISH之外,还没有定义的值,而#34; Western&#34; (拉丁文?)脚本。 DirectWrite值是否与Uniscribe相同?似乎我至少可以通过查看fLinkBeforefLinkAfter字段来计算单词的初始和最终状态,但这足以正确应用每个脚本的属性吗?

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

最后,如果我在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 shows what I want works fine

(最后的部分只是为了证明正在应用属性。)如果我假设XAML使用DirectWrite,那么这证明了我的第一个选项(简单地将我的自定义属性覆盖在上面)默认情况下)应该可能......(我根据XAML为Direct2D提供了一个与绘制2D图形非常相似的API的想法做出了这个假设,并且在我拥有的地方填充了很多漏洞手动编写大量的胶水代码来与vanilla Direct2D做同样的事情,所以我假设XAML中可能的任何东西都可以使用Direct2D,并且扩展DirectWrite,因为它们在技术上一起引入......)

此时我完全迷失了。我希望至少可以在各个平台上进行预测,而且我不确定程序是如何直接使用OpenType功能的,或者更不用说直接使用OpenType功能。我对文本布局API的期望不高吗?如果我需要,我是否必须删除IDWriteTextLayout并自行完成所有文本整形和布局?

或者我是否必须放弃vanilla Windows 7支持并升级到Platform Update DirectWrite功能集?甚至完全是Windows 7?

2 个答案:

答案 0 :(得分:4)

在与Peter Sikking和Ebrahim Byagowi进行一些讨论之后,我去调试了一个更为通用的程序,我快速构建它以测试事物,我想出了内部发生了什么。

首先,我会说同样适用于Uniscribe和DirectWrite

事实证明,无论我使用哪种功能集,DirectWrite始终提供一组默认的OpenType功能!情况是提供的默认功能列表取决于我是否加载自己的功能,具体取决于整形引擎。对于水平书写模式和英语的latn脚本,这是通过&#34;泛型引擎&#34;来完成的。

如果我没有提供任何功能,通用引擎将加载特定于脚本的功能。对于水平latn,此列表为

locl
ccmp
rlig
rclt
calt
liga
clig

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

locl
ccmp
rclt
rlig
mark
mkmk
dist

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

答案 1 :(得分:1)

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

  如果我希望能够在默认设置之上添加排版功能,我是否必须完全放弃使用IDWriteTextLayout?

这取决于。如果IDWriteTextLayout接口以各种方式很好地适合您的项目任务,除了易于改变DirectWrite默认排版功能,了解您应该如何排版并创建适合您需求的IDWriteTypography实例。为程序开发自定义文本布局可能需要大量的时间和精力,特别是如果程序应该呈现双向文本,复杂脚本,内联对象等。

您的项目任务可能需要开发文本布局引擎,原因不仅仅是控制渲染文本中使用的排版功能。例如,您的经理/客户可能会要求实施定制的折线机会或字形推进理由算法。在这种情况下,您将实现IDWriteTextAnalizer :: GetGlyphs方法。此方法具有参数DWRITE_TYPOGRAPHIC_FEATURES **功能,const UINT32 * featureRangeLengths,UINT32 featureRanges,此参数使您可以取代一组要呈现的文本的“默认”排版功能(请参阅我对其他问题的回答{ {3}})。只会改变受影响的功能;其他功能具有“默认”值。更重要的是,如果在下一个文本范围的GetGlyphs调用中省略此参数(例如,使用NULL,NULL,0的值),则在下一个范围的调用中不会更改先前GetGlyphs调用中更改的功能。

  

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

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

这可能不是主题,但我冒险假设“Unicode脚本ID”值的来源。截至2010-07-17,Unicode,Inc。发布了Unicode 6.0版本。该标准包含该文件 What are the default typography settings used by IDWriteTextLayout?,其中包含一个脚本列表。清单如此:

   # Script (sc)

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

阿拉伯语脚本为#1,西里尔语脚本为#20,拉丁文脚本为#47。此外,在其他地方我看到这个列表以脚本Common和Inherited开头。它将阿拉伯语脚本放在第3位,将西里尔语放到第22位,将拉丁语放到第49位。这些序数对你来说很熟悉,不是吗?

幸运的是,我们不需要依赖“Unicode脚本ID”值;我们需要脚本属性,而不是脚本ID或缩写。 API是自洽的,因为当我们将从AnalyzeScript调用派生的数字传递给GetScriptProperties方法时,它会为文本范围提供实际的脚本属性。