使用 Uniscribe 显示文本

你的应用程序可以使用 Uniscribe API 函数来支持版式和国际文本的显示和编辑。 Uniscribe 使用段落作为文本显示的单元,并且 Uniscribe 功能必须用于整个段落。

使用 Uniscribe 显示文本时,应用程序必须经历格式设置(“布局”)过程,通常使用 Uniscribe 来完成。 应用程序将文本段落划分为具有相同样式的字符串,称为“run”。 该样式由特定实现来确定,但通常包括字体、大小和颜色等属性。 在定义 run 时,应用程序还可以应用其他信息,例如维护以用于词法工具的语言和区域设置数据。 例如,应用程序可能会将以法语呈现的一个以英语为主的文本中的段落视为一个单独的 run。

确定所有段落的 run 后,应用程序会将每个段落划分为具有相同书写系统和方向的字符串(“项”)。 应用程序应用项信息以生成在书写系统和方向上唯一且完全位于单个项内中的 run(“range”)。

将项细分为 range 的过程有点任意,但是 range 应包含一个或多个连续的书写系统定义的不可分割字符组,称为“群集”。对于欧洲语言,一个群集通常对应于一个代码页字符或 Unicode 代码点,并包含一个字形。 但是,在泰语等语言中,群集是字形的组,对应于多个连续字符或代码点。 例如,一个泰语群集可能包含一个辅音、一个元音、一个声调符号。 因此,它不会中断群集,应用程序通常应使用它可以使用的最长 range,或使用自己的词法信息在非群集中间的位置在 range 间中断。

当它识别了每个 range 内的群集时,应用程序必须确定每个群集的大小。 它使用 Uniscribe 对群集求和,以确定每个 range 的大小。 然后,应用程序对 range 的大小求和,直到它们溢出一行,即到达边界。 溢出行的 range 会分到当前行和下一行。 对于每一行,应用程序都会为每个 range 生成从视觉位置到逻辑位置的映射。 然后,应用程序将每个 range 的代码点塑造成字形,随后可以对其进行定位和呈现。

应用程序只执行一次文本布局。 之后,它要么保存字形和位置用于显示目的,要么每次显示文本时生成它们,在速度和内存之间做出权衡。 典型的应用程序会实现布局过程一次,然后在每次显示文本时生成字形和位置。

使用复杂书写系统的应用程序在简单的布局和显示方法上存在以下问题。

  • 复杂书写系统字符的宽度取决于其上下文。 无法在简单表中保存宽度。
  • 在泰语等书写系统的字之间中断需要字典支持。 例如,在泰语字之间不使用分隔符。
  • 阿拉伯语、希伯来语、波斯语、乌尔都语和其他双向文本语言需要在显示前重新排序。
  • 要轻松地使用复杂书写系统,通常需要某种形式的字体关联。

Uniscribe 使用段落作为显示单元,这有助于应用程序充分处理这些复杂的书写系统问题。

注意

Uniscribe 必须用于整个段落,即使段落的部分不是复杂的书写系统。

 

如下表所示,Uniscribe 版本 1.6 或更高版本支持利用 OpenType 标记的多个函数。 它们可以替换为相应的常规 Uniscribe 函数。 通常,你的应用程序应完全使用来自某一组的函数,不应尝试“混搭”函数。

常规 Uniscribe 函数 等效的 OpenType 函数
ScriptItemize ScriptItemizeOpenType
ScriptShape ScriptShapeOpenType
ScriptPlace ScriptPlaceOpenType

 

使用 Uniscribe 布局文本

你的应用程序可以使用以下步骤通过 Uniscribe 布局文本段落。 此过程假定应用程序已将段落划分为 run。

  1. 仅在启动或接收 WM_SETTINGCHANGE 消息时调用 ScriptRecordDigitSubstitution

  2. (可选)调用 ScriptIsComplex 以确定段落是否需要复杂的处理。

  3. (可选)如果使用 Uniscribe 处理双向文本和/或数字替换,请调用 ScriptApplyDigitSubstitution 来准备 SCRIPT_CONTROL 并将 SCRIPT_STATE 结构作为 ScriptItemize 的输入。 如果跳过此步骤,但仍需要数字替换,请将国家数字替换为 Unicode U+0030 到 U+0039(欧洲数字)。 有关数字替换的信息,请参阅“数字形状”。

  4. 调用 ScriptItemize 将段落划分为项。 例如,如果不使用 Uniscribe 进行数字替换,并且双向顺序是已知的(例如,由于用于输入字符的键盘布局),请调用 ScriptItemize。 在调用中,为 SCRIPT_CONTROLSCRIPT_STATE 结构提供空指针。 此方法仅使用塑形引擎来生成项,可以使用引擎信息对项进行重新排序。

    注意

    通常,仅适配从左到右的书写系统且没有任何数字替换的应用程序应为 SCRIPT_CONTROLSCRIPT_STATE 结构传递空指针。

     

  5. 将项信息与 run 信息合并以生成 range。

  6. 调用 ScriptShape 来标识群集并生成字形。

  7. 如果 ScriptShape 返回代码 USP_E_SCRIPT_NOT_IN_FONT 或 S_OK,且输出包含缺失的字形,请从其他字体中选择字符。 通过将传递给 ScriptShapeSCRIPT_ANALYSIS 结构的 eScript 成员设置为 SCRIPT_UNDEFINED 来替换其他字体或禁用塑形。 有关详细信息,请参阅使用字体回退

  8. 调用 ScriptPlace 为每个连续 range 中的字形生成前进宽度以及 x 和 y 位置。 这是文本大小成为考虑因素的第一步。

  9. 对 range 大小求和,直到行溢出。

  10. 使用逻辑属性中的 fSoftBreakfWhiteSpace 成员中断字边界上的 range。 要从 run 中断单个字符群集,请使用通过调用 ScriptBreak 返回的信息。

    注意

    确定 range 的第一个代码点是否应为断字点,因为上一个 range 的最后一个字符需要它。 例如,如果一个 range 以逗号结尾,请考虑将下一个 range 的第一个字符作为断字点。

     

  11. 对段落中的每行重复步骤 6 到 10。 但是,如果中断了行的最后一个 run,请调用 ScriptShape 来重塑 run 的剩余部分,作为下一行的第一个 run。

使用 Uniscribe 显示文本

应用程序可以使用以下步骤来显示文本段落。 此过程假定应用程序已布局文本,并且尚未保存布局过程中的字形和位置。 如果速度是个问题,则应用程序可以保存布局过程中的字形和位置,并从显示过程中的步骤 2 开始。

注意

如果文本不包含从右到左的书写系统的字符,不包含双向控制字符,并且使用从左到右的基本嵌入级别,则应用程序可以忽略步骤 2。

 

  1. 对于每次运行,请执行以下操作:

    1. 如果样式自上次运行后已更改,请通过释放并再次获取设备上下文的句柄来更新它。
    2. 调用 ScriptShape 生成运行的字形。
    3. 调用 ScriptPlace 为每个字形生成前进宽度和 x,y 偏移量。
  2. 执行以下操作,为行中的 run 建立正确的视觉顺序:

    1. 提取双向嵌入级别的数组,每个 range 一个。 嵌入级别提供者为 (SCRIPT_ITEM) si.(SCRIPT_ANALYSIS) a. (SCRIPT_STATE) s.uBidiLevel.
    2. 将此数组传递给 ScriptLayout 以生成视觉位置到逻辑位置的映射。
  3. (可选)要对齐文本,请调用 ScriptJustify 或使用文本的专门知识。

  4. 使用视觉到逻辑的映射以视觉顺序显示 run。 从行的最左端开始,调用 ScriptTextOut 以显示映射中第一个条目给出的 run。 对于映射中的每个后续条目,调用 ScriptTextOut 在之前显示的 run 右侧显示指定的 run。

    如果忽略了步骤 2,请从行的最左端开始并调用 ScriptTextOut 以显示第一个逻辑 run,然后在之前的 run 右侧显示每个逻辑 run。

  5. 对段落中的所有行重复上述步骤。

使用 Uniscribe