使用 Direct2D 和 DirectWrite 呈现文本
与其他 API(如 GDI、GDI+ 或 WPF)不同,Direct2D 与其他 API、DirectWrite 互操作以操作和呈现文本。 本主题介绍这些独立组件的优点和互操作。
本主题包含以下各节:
Direct2D 支持增量采用
出于各种原因,将应用程序从一个图形 API 移动到另一个图形 API 可能很困难或不是您想要的。 这可能是因为您必须支持仍采用较旧接口的插件,也可能是因为应用程序本身太大,无法移植到一个版本中的新 API,或者因为较新的 API 的某些部分是可取的,但较旧的 API 适用于应用程序的其他部分。
由于 Direct2D 和 DirectWrite 是作为单独的组件实现的,因此您可以升级整个 2D 图形系统或仅升级其文本部分。 例如,您可以更新应用程序以将 DirectWrite 用于文本,但仍使用 GDI 或 GDI+ 进行呈现。
文本服务与文本呈现
随着应用程序的发展,其文本处理要求也越来越复杂。 起初,文本通常局限于静态布局 UI,而且文本呈现在定义明确的框中,例如按钮。 随着应用程序的语言种类越来越多,这种方法变得更加难以维持,因为翻译文本的宽度和高度在语言之间可能有很大差异。 为了适应这种情况,应用程序开始根据实际呈现的文本大小动态布局 UI,而不是其他方式。
为了帮助应用程序完成此任务,DirectWrite 提供了 IDWriteTextLayout 接口。 此 API 使应用程序能够指定具有复杂特征的文本,例如不同的字体和字号、下划线、删除线、双向文本、效果、省略号甚至嵌入的非字形字符(如位图表情符号或图标)。 然后,应用程序可以在迭代确定其 UI 布局时更改文本的各种特征。 DirectWrite Hello World 示例如下图所示,在教程:DirectWrite 主题入门指南中显示了其中许多效果。
布局可以根据字形的宽度(如 WPF 那样)放置字形,也可以将字形贴靠到最近的像素位置(如 GDI 所示)。
除了获取文本度量之外,应用程序还可以命中测试文本的各个部分。 例如,它可能想知道单击文本中的超链接。 (有关命中测试的详细信息,请参阅 如何对文本布局执行命中测试主题。)
文本布局接口与应用程序使用的呈现 API 分离,如下图所示:
这种分离是可能的,因为 DirectWrite 提供了一个呈现接口 (IDWriteTextRenderer),应用程序可以使用您所需的任何图形 API 来实现呈现文本。 应用程序实现的 IDWriteTextRenderer::D rawGlyphRun 回调方法在呈现文本布局时由 DirectWrite 调用。 此方法负责执行绘图操作或传递这些操作。
对于绘图标志符号,Direct2D 提供 ID2D1RenderTarget::D rawGlyphRun,用于绘制到 Direct2D 表面,DirectWrite 提供 IDWriteBitmapRenderTarget::DrawGlyphRun,以便使用 GDI 将绘图传输到窗口。 方便的是,Direct2D 和 DirectWrite 中的 DrawGlyphRun 参数与应用程序在 IDWriteTextRenderer 上实现的 DrawGlyphRun 方法完全兼容。
遵循类似的分隔,特定于文本的功能(如字体枚举和管理、字形分析等)由 DirectWrite 而不是 Direct2D 处理。 DirectWrite 对象由 Direct2D 直接接受。 为了帮助现有 GDI 应用程序利用 DirectWrite,它为 IDWriteGdiInterop 方法接口提供了执行以下操作的方法:
- 从 GDI 逻辑字体 (CreateFontFromLOGFONT) 创建 DirectWrite 字体。
- 从 DirectWrite 字体转换为 GDI 逻辑字体 (ConvertFontFaceToLOGFONT)。
- 从选入 HDC 的字体中检索 DirectWrite 字体。 (CreateFontFaceFromHdc)
- 在系统内存中创建 DirectWrite 位图呈现目标 (CreateBitmapRenderTarget)。
字形与文本
文本是一组 Unicode 代码点(字符),具有各种风格修饰符(字体、粗细、下划线、删除线等),在矩形中布局。 相比之下,字形是特定字体文件中的特定索引。 字形定义一组可以呈现的曲线,但没有任何文本含义。 字形和字符之间可能存在多对多映射。 来自同一 Font Face 并在基线上按顺序布局的字形序列称为 GlyphRun。 DirectWrite 和 Direct2D 都调用其最精确的字形呈现 API DrawGlyphRun,并且它们具有非常相似的签名。 以下是来自Direct2D 中的 ID2D1RenderTarget:
STDMETHOD_(void, DrawGlyphRun)(
D2D1_POINT_2F baselineOrigin,
__in CONST DWRITE_GLYPH_RUN *glyphRun,
__in ID2D1Brush *foregroundBrush,
DWRITE_MEASURING_MODE measuringMode = DWRITE_MEASURING_MODE_NATURAL
) PURE;
此方法来自 DirectWrite 中的 IDWriteBitmapRenderTarget:
STDMETHOD(DrawGlyphRun)(
FLOAT baselineOriginX,
FLOAT baselineOriginY,
DWRITE_MEASURING_MODE measuringMode,
__in DWRITE_GLYPH_RUN const* glyphRun,
IDWriteRenderingParams* renderingParams,
COLORREF textColor,
__out_opt RECT* blackBoxRect = NULL
) PURE;
DirectWrite 版本保留基线原点、测量模式和字形运行参数,并包括其他参数。
DirectWrite 还允许通过实现 IDWriteTextRenderer 接口,为字形使用自定义呈现器。 此接口还具有 DrawGlyphRun 方法,如以下代码示例所示。
STDMETHOD(DrawGlyphRun)(
__maybenull void* clientDrawingContext,
FLOAT baselineOriginX,
FLOAT baselineOriginY,
DWRITE_MEASURING_MODE measuringMode,
__in DWRITE_GLYPH_RUN const* glyphRun,
__in DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription,
__maybenull IUnknown* clientDrawingEffect
) PURE;
此版本包含更多参数,这些参数在实现自定义文本呈现器时非常有用。 最终参数用于应用程序实现的自定义绘图效果。 (有关客户端绘图效果的详细信息,请参阅如何将客户端绘图效果添加到文本布局。
每个字形运行从源开始,并置于从此原点开始的行。 标志符号由当前世界转换和关联的呈现目标上的所选文本呈现设置更改。 此 API 通常仅由自行排版的应用程序(例如 Word Processor)或已实现 IDWriteTextRenderer 接口的应用程序直接调用。
DirectWrite 和 Direct2D
Direct2D 通过 DrawGlyphRun 提供字形级呈现服务。 但是,这要求应用程序实现呈现的详细信息,这基本上可以自行从 GDI 重现 DrawText API 的功能。
因此,Direct2D 提供接受文本而不是字形的 API:ID2D1RenderTarget::D rawTextLayout 和 ID2D1RenderTarget::D rawText。 这两种方法都呈现到 Direct2D 表面。 若要呈现到 GDI 表面,需提供 IDWriteBitmapRenderTarget::D rawGlyphRun。 但此方法要求应用程序实现自定义文本呈现器。 (有关详细信息,请参阅 呈现到 GDI 表面 主题。)
例如,应用程序的文本用法通常很简单:将 OK 或取消放在固定布局按钮上。 但是,随着国际化和其他功能的添加,随着时间的推移,它变得更加复杂。 最终,许多应用程序不得不使用 DirectWrite 的文本布局对象并实现文本呈现器。
因此,Direct2D 提供了分层 API,使应用程序能够简单地启动并增长更复杂,而无需回溯或放弃其工作代码。 下图显示了简化视图:
DrawText
DrawText 是最简单易用的 API。 它需要一个 Unicode 字符串、一个前景画笔、一个单一格式对象和一个目标矩形。 它将在布局矩形内布置并呈现整个字符串,并可选择对其进行剪辑。 在固定布局 UI 中放置一段简单的文本时,这非常有用。
DrawTextLayout
通过创建 IDWriteTextLayout 对象,应用程序可以开始测量和排列文本和其他 UI 元素,并支持多种字体、样式、下划线和删除线。 Direct2D 提供 DrawTextLayout API,该 API 直接接受此对象并在给定点呈现文本。 (布局对象提供宽度和高度)。 除了实现所有预期的文本布局功能外,Direct2D 还将任何效果对象解释为画笔,并将该画笔应用于所选字形范围。 它还将调用任何内联对象。 然后,应用程序可以根据需要将非字形字符(图标)插入文本。 使用文本布局对象的另一个优点是字形位置会缓存在其中。 因此,通过在多次绘制调用中重复使用相同的布局对象并避免在每次调用时重新计算字形位置,可以大大提高性能。 GDI 的 DrawText 不存在此功能。
DrawGlyphRun
最后,应用程序可以实现 IDWriteTextRenderer 接口本身,并调用 DrawGlyphRun 和 FillRectangle 本身或任何其他呈现 API。 与文本布局对象的所有现有交互都将保持不变。
有关如何实现自定义文本呈现器的示例,请参阅使用自定义文本呈现器的呈现主题。
字形呈现
将 DirectWrite 添加到现有 GDI 应用程序使应用程序能够使用 IDWriteBitmapRenderTarget API 呈现字形。 DirectWrite 提供的 IDWriteBitmapRenderTarget::D rawGlyphRun 方法将以纯色呈现给内存 DC,而无需任何其他 API,例如 Direct2D。
这使应用程序能够获取高级文本呈现功能,如下所示:
- 子像素 ClearType 使应用程序能够将字形放在子像素位置上,以允许尖锐字形呈现和字形布局。
- Y 方向抗锯齿使曲线在较大的字形上呈现更流畅。
迁移到 Direct2D 的应用程序还将获得以下功能:
- 硬件加速。
- 能够使用任意 Direct2D 画笔填充文本,例如径向渐变、线性渐变和位图。
- 对通过 PushAxisAlignedClip、PushLayer 和 CreateCompatibleRenderTarget API 进行分层和剪辑提供更多支持。
- 支持灰度文本呈现的功能。 这根据文本画笔不透明度和文本反锯齿正确填充目标 alpha 通道。
为了有效地支持硬件加速,Direct2D 使用了与 Gamma 校正略有不同的近似方法,称为 alpha 校正。 这不需要 Direct2D 在呈现文本时检查呈现目标颜色像素。
结束语
本主题介绍 Direct2D 与 DirectWrite 之间的差异和相似之处,以及将其作为单独的协作 API 提供体系结构动机。