DirectWrite简介

人员在日常生活中随时与文本通信。 这是人们使用越来越多的信息的主要方式。 过去,它过去是通过印刷内容,主要是文档、报纸、书籍等。 它越来越多地成为其 Windows 电脑上的在线内容。 典型的 Windows 用户花费大量时间从计算机屏幕阅读。 他们可能正在浏览 Web、扫描电子邮件、撰写报表、填写电子表格或编写软件,但他们真正要做的是阅读。 尽管文本和字体几乎渗透到 Windows 中用户体验的每个部分,但对于大多数用户来说,在屏幕上阅读并不像阅读打印输出那样令人愉快。

对于 Windows 应用程序开发人员来说,编写文本处理代码是一项挑战,因为对更好的可读性、复杂的格式设置和布局控制以及应用程序必须显示的多种语言的支持越来越高。 即使是最基本的文本处理系统,也必须允许文本输入、布局、显示、编辑以及复制和粘贴。 Windows 用户通常期望的不仅仅是这些基本功能,甚至要求简单的编辑器支持多种字体、各种段落样式、嵌入图像、拼写检查和其他功能。 新式 UI 设计也不再局限于单一格式的纯文本,而是需要通过丰富的字体和文本布局提供更好的体验。

本文介绍DirectWrite如何使 Windows 应用程序能够增强 UI 和文档的文本体验。

改进文本体验

新式 Windows 应用程序对其 UI 和文档中的文本有复杂的要求。 其中包括更好的可读性、对各种语言和脚本的支持以及卓越的呈现性能。 此外,大多数现有应用程序都需要一种方法来延续 WindowsWin32 基本代码中的现有投资。

DirectWrite提供以下三项功能,使 Windows 应用程序开发人员能够改进其应用程序中的文本体验:独立于呈现系统、高质量版式和多层功能。

Rendering-System独立

DirectWrite独立于任何特定的图形技术。 应用程序可以使用最适合其需求的渲染技术。 这使应用程序能够灵活地继续通过 GDI 呈现应用程序的某些部分,通过 Direct3D 或 Direct2D 呈现其他部分。 事实上,应用程序可以选择通过专有呈现堆栈呈现DirectWrite。

High-Quality版式

DirectWrite利用 OpenType 字体技术的进步,在 Windows 应用程序中实现高质量的版式。 DirectWrite字体系统提供用于处理字体枚举、字体回退和字体缓存的服务,这些都是处理字体的应用程序所需要的。

DirectWrite提供的 OpenType 支持使开发人员能够向其应用程序添加高级版式功能和对国际文本的支持。

支持高级版式功能

DirectWrite使应用程序开发人员能够解锁他们在 WinForms 或 GDI 中无法使用的 OpenType 字体功能。 DirectWrite IDWriteTypography 对象公开 OpenType 字体的许多高级功能,例如样式备用和斜面。 Microsoft Windows 软件开发工具包 (SDK) 提供了一组示例 OpenType 字体,这些字体设计具有丰富的功能,例如 Pericles 和 Pescadero 字体。 有关 OpenType 功能的详细信息,请参阅 OpenType 字体功能

支持国际文本

DirectWrite使用 OpenType 字体实现对国际文本的广泛支持。 支持代理项、BIDI、换行和 UVS 等 Unicode 功能。 语言引导式脚本项化、数字替换和字形调整可确保任何脚本中的文本具有正确的布局和呈现。

目前支持以下脚本:

注意

对于标有 *的脚本,没有默认的系统字体。 应用程序必须安装支持这些脚本的字体。

 

  • 阿拉伯语
  • 亚美尼亚语
  • 孟加拉
  • Bopomofo
  • 盲文*
  • 加拿大土著领音
  • 切罗基语
  • 简体中文 (繁体中文 &)
  • 西里尔语
  • 科普特人*
  • 梵语
  • 埃塞俄比亚文
  • 格鲁吉亚语
  • Glagolitic*
  • 希腊语
  • 古吉拉特语
  • 果鲁穆奇语
  • 希伯来语
  • 日语
  • 卡纳达语
  • 高棉语
  • 韩语
  • 老挝语
  • 拉丁语
  • 马拉雅拉姆语
  • 蒙古语
  • 缅甸
  • 西双版纳新傣文
  • Ogham*
  • 奥里亚语
  • 'Phags-pa
  • 符文*
  • 僧伽罗语
  • 叙利亚语
  • 德宏傣文
  • 泰米尔语
  • 泰卢固语
  • 塔安那文
  • 泰语
  • 藏语
  • 彝语

多个功能层

DirectWrite提供分解的功能层,每个层与下一层无缝交互。 API 设计使应用程序开发人员能够自由和灵活地根据需求和计划采用各个层。 下图显示了这些层之间的关系。

directwrite 层及其如何与应用程序或 ui 框架以及图形 API 通信的关系图

文本布局 API 提供DirectWrite提供的最高级别功能。 它为应用程序提供度量、显示和与格式丰富的文本字符串交互的服务。 此文本 API 可用于当前使用 Win32 的 DrawText 构建具有丰富格式文本的新式 UI 的应用程序。

实现其自己的布局引擎的文本密集型应用程序可以使用下一层:脚本处理器。 脚本处理器将文本块分解为脚本块,并处理 Unicode 表示形式与字体中适当字形表示形式的映射,以便脚本文本可以正确显示正确的语言。 文本布局 API 层使用的布局系统基于字体和脚本处理系统构建。

字形呈现层是功能的最低层,为实现其自己的文本布局引擎的应用程序提供字形呈现功能。 字形呈现层对于实现自定义呈现器以通过DirectWrite文本格式 API 中的回调函数修改字形绘制行为的应用程序也很有用。

DirectWrite字体系统适用于所有DirectWrite功能层,并使应用程序能够访问字体和字形信息。 它旨在处理常见的字体技术和数据格式。 DirectWrite字体模型遵循支持相同字体系列中任意数量的粗细、样式和拉伸的常见版式做法。 此模型(后跟 WPF 和 CSS 的同一模型)指定仅在 (粗体、浅色等) 、样式 (直立、斜体或倾斜) 或拉伸 (窄、浓缩、宽等) 的字体被视为单个字体系列的成员。

改进了使用 ClearType 的文本呈现

提高屏幕上的可读性是所有 Windows 应用程序的关键要求。 认知心理学研究的证据表明,我们需要能够准确识别每一个字母,并且字母之间的间距对于快速处理至关重要。 不对称的字母和单词被视为丑陋的,会降低阅读体验。 Microsoft 高级阅读技术组凯文·拉森(Kevin Larson)撰写了一篇有关该主题的文章,该文章发表在 Spectrum IEEE 上。 本文名为“文本技术”。

DirectWrite中的文本是使用 Microsoft ClearType 呈现的,这增强了文本的清晰度和可读性。 ClearType 利用了新式 LCD 显示器为每个可以单独控制的像素的 RGB 条纹这一事实。 DirectWrite使用 ClearType 的最新增强功能(首先包含在带 Windows Presentation Foundation 的 Windows Vista 中),使它不仅可以评估单个字母,还可以评估字母之间的间距。 在这些 ClearType 增强功能之前,“阅读”大小为 10 或 12 磅的文本很难显示:我们可以在字母之间放置 1 个像素(通常太小)或 2 个像素,这通常太多。 使用子像素中的额外分辨率为我们提供了小数部分间距,从而提高整个页面的均匀性和对称性。

以下两张图显示了在使用子像素定位时,字形如何从任何子像素边界开始。

下图是使用未使用子像素定位的 ClearType 呈现器 GDI 版本呈现的。

未进行子像素定位的“技术”的插图

下图是使用 DirectWrite 版本的 ClearType 呈现器呈现的,该呈现器使用子像素定位。

使用子像素定位呈现的“技术”插图

请注意,第二张图像中字母 h 和 n 之间的间距更均匀,而字母 o 与字母 n 之间的间距更大,甚至与字母 l 更相等。 另请注意字母 l 上的茎如何更自然的外观。

子像素 ClearType 定位提供屏幕上最准确的字符间距,尤其是在小尺寸时,子像素和整个像素之间的差异表示字形宽度的很大比例。 它允许在理想的分辨率空间中测量文本,并在其自然位置在LCD色带处呈现,具有亚像素粒度。 根据定义,使用此技术测量和呈现的文本与分辨率无关,这意味着在各种显示分辨率范围内实现完全相同的文本布局。

与任一类型的 GDI ClearType 呈现不同,子像素 ClearType 提供最准确的字符宽度。

文本字符串 API 默认采用子像素文本呈现,这意味着它以与当前显示分辨率无关的理想分辨率测量文本,并根据真正缩放的字形进阶宽度和定位偏移量生成字形定位结果。

对于大尺寸文本,DirectWrite还支持沿 y 轴进行抗锯齿,使边缘更平滑,并按照字体设计器的预期呈现字母。 下图显示了 y 方向抗锯齿。

呈现为 gdi 文本和具有 y 方向抗锯齿的 directwrite 文本的“cleartype”的插图

尽管默认情况下,DirectWrite文本使用子像素 ClearType 进行定位和呈现,但其他呈现选项也可用。 许多现有应用程序使用 GDI 来呈现其大部分 UI,而某些应用程序则使用继续使用 GDI 进行文本呈现的系统编辑控件。 向这些应用程序添加DirectWrite文本时,可能需要牺牲子像素 ClearType 提供的阅读体验改进,使文本在整个应用程序中具有一致的外观。

为了满足这些要求,DirectWrite还支持以下呈现选项:

  • 子像素 ClearType (默认) 。
  • 在水平和垂直维度中具有抗锯齿的子像素 ClearType。
  • 别名文本。
  • Microsoft Word阅读视图使用的 GDI 自然宽度 (,例如) 。
  • GDI 兼容宽度 (,包括东亚嵌入式位图) 。

这些呈现模式中的每一种都可以通过 DirectWrite API 和新的 Windows 7 收件箱 ClearType 优化器进行微调。

注意

从Windows 8开始,在大多数情况下应使用灰度文本抗锯齿。 有关详细信息,请参阅下一部分。

 

支持自然布局

自然布局与分辨率无关,因此,在放大或缩小时,字符间距不会更改,也不会影响显示器的 DPI。 第二个优点是间距与字体设计相等。 自然布局是通过DirectWrite支持自然渲染实现的,这意味着单个字形可以定位到像素的一小部分。

虽然默认采用自然布局,但某些应用程序需要呈现与 GDI 相同的间距和外观的文本。 对于此类应用程序,DirectWrite提供 GDI 经典和 GDI 自然测量模式以及相应的呈现模式。

上述任何呈现模式都可以与两种抗锯齿模式之一结合使用:ClearType 或灰度。 ClearType 抗锯齿通过单独操作每个像素的红色、绿色和蓝色值来模拟更高的分辨率。 灰度抗锯齿只计算每个像素的一个覆盖率 (或 alpha) 值。 ClearType 是默认值,但建议将灰度抗锯齿功能用于 Windows 应用商店应用,因为它速度更快且与标准抗锯齿兼容,同时仍具有高度可读性。

API 概述

IDWriteFactory 接口是使用DirectWrite功能的起点。 工厂是创建一组可以一起使用的对象的根对象。

格式设置和布局操作是操作的先决条件,因为文本需要正确设置格式并按一组指定的约束进行布局,然后才能进行绘制或命中测试。 可以使用 IDWriteFactory 为此创建的两个关键对象是 IDWriteTextFormatIDWriteTextLayoutIDWriteTextFormat 对象表示文本段落的格式设置信息。 IDWriteFactory::CreateTextLayout 函数采用输入字符串、关联的约束(例如要填充的空间的维度)和 IDWriteTextFormat 对象,并将完全分析和格式化的结果放入 IDWriteTextLayout 中,以用于后续操作。

然后,应用程序可以使用 Direct2D 提供的 DrawTextLayout 函数或实现可以使用 GDI、 Direct2D 或其他图形系统呈现字形的回调函数来呈现文本。 对于单一格式文本,Direct2D 上的 DrawText 函数提供了一种更简单的方法来绘制文本,而无需首先创建 IDWriteTextLayout 对象。

使用 DirectWrite 设置和绘制“Hello World”

下面的代码示例演示了应用程序如何使用 IDWriteTextFormat 设置单个段落的格式,并使用 Direct2DDrawText 函数绘制 该段落。

HRESULT DemoApp::DrawHelloWorld(
    ID2D1HwndRenderTarget* pIRenderTarget
    )
{
    HRESULT hr = S_OK;
    ID2D1SolidColorBrush* pIRedBrush = NULL;
    IDWriteTextFormat* pITextFormat = NULL;
    IDWriteFactory* pIDWriteFactory = NULL;

    if (SUCCEEDED(hr))
    {
        hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED,
                __uuidof(IDWriteFactory),
                reinterpret_cast<IUnknown**>(&pIDWriteFactory));
    }

    if(SUCCEEDED(hr))
    {
        hr = pIDWriteFactory->CreateTextFormat(
            L"Arial", 
            NULL,
            DWRITE_FONT_WEIGHT_NORMAL, 
            DWRITE_FONT_STYLE_NORMAL, 
            DWRITE_FONT_STRETCH_NORMAL, 
            10.0f * 96.0f/72.0f, 
            L"en-US", 
            &pITextFormat
        );
    }

    if(SUCCEEDED(hr))
    {
        hr = pIRenderTarget->CreateSolidColorBrush(
            D2D1:: ColorF(D2D1::ColorF::Red),
            &pIRedBrush
        );
    }
    
   D2D1_RECT_F layoutRect = D2D1::RectF(0.f, 0.f, 100.f, 100.f);

    // Actually draw the text at the origin.
    if(SUCCEEDED(hr))
    {
        pIRenderTarget->DrawText(
            L"Hello World",
            wcslen(L"Hello World"),
            pITextFormat,
            layoutRect, 
            pIRedBrush
        );
    }

    // Clean up.
    SafeRelease(&pIRedBrush);
    SafeRelease(&pITextFormat);
    SafeRelease(&pIDWriteFactory);

    return hr;
}

访问字体系统

除了使用上述示例中的 IDWriteTextFormat 接口为文本字符串指定字体系列名称外,DirectWrite还通过字体枚举为应用程序提供了对字体选择的更多控制,以及基于嵌入的文档字体创建自定义字体集合的功能。

IDWriteFontCollection 对象是字体系列的集合。 DirectWrite通过称为系统字体集合的特殊字体集合提供对系统上安装的字体集的访问。 这是通过调用 IDWriteFactory 对象的 GetSystemFontCollection 方法获取的。 应用程序还可以从应用程序定义的回调枚举的一组字体(即应用程序安装的专用字体或文档中嵌入的字体)创建自定义字体集合。

然后,应用程序可以调用 GetFontFamily 来获取集合中的特定 FontFamily 对象,然后调用 IDWriteFontFamily::GetFirstMatchingFont 以访问特定的 IDWriteFont 对象。 IDWriteFont 对象表示字体集合中的字体,并公开属性和一些基本字体指标。

IDWriteFontFace 是另一个对象,它表示字体并公开字体的完整指标集。 可以直接从字体名称创建 IDWriteFontFace ;应用程序无需获取字体集合即可访问它。 这对于需要查询特定字体详细信息的文本布局应用程序(例如 Microsoft Word)非常有用。

下图说明了这些对象之间的关系。

字体集合、字体系列和字体之间的关系图

IDWriteFontFace

IDWriteFontFace 对象表示字体,并提供与 IDWriteFont 对象相比更详细的字体信息。 IDWriteFontFace 中的字体和字形指标对于实现文本布局的应用程序很有用。

大多数主流应用程序不会直接使用这些 API,而是使用 IDWriteFont 或直接指定字体系列名称。

下表汇总了这两个对象的使用方案。

类别 IDWriteFont IDWriteFontFace
支持用户交互的 API,例如字体选择器用户界面:说明和其他信息性 API
支持字体映射的 API:系列、样式、粗细、拉伸、字符覆盖
DrawText API
用于呈现的 API
用于文本布局的 API:字形指标等
UI 控件和文本布局的 API:字体范围的指标

 

下面是枚举系统字体集合中字体的示例应用程序。

#include <dwrite.h>
#include <string.h>
#include <stdio.h>
#include <new>

// SafeRelease inline function.
template <class T> inline void SafeRelease(T **ppT)
{
    if (*ppT)
    {
        (*ppT)->Release();
        *ppT = NULL;
    }
}

void wmain()
{
    IDWriteFactory* pDWriteFactory = NULL;

    HRESULT hr = DWriteCreateFactory(
            DWRITE_FACTORY_TYPE_SHARED,
            __uuidof(IDWriteFactory),
            reinterpret_cast<IUnknown**>(&pDWriteFactory)
            );

    IDWriteFontCollection* pFontCollection = NULL;

    // Get the system font collection.
    if (SUCCEEDED(hr))
    {
        hr = pDWriteFactory->GetSystemFontCollection(&pFontCollection);
    }

    UINT32 familyCount = 0;

    // Get the number of font families in the collection.
    if (SUCCEEDED(hr))
    {
        familyCount = pFontCollection->GetFontFamilyCount();
    }

    for (UINT32 i = 0; i < familyCount; ++i)
    {
        IDWriteFontFamily* pFontFamily = NULL;

        // Get the font family.
        if (SUCCEEDED(hr))
        {
            hr = pFontCollection->GetFontFamily(i, &pFontFamily);
        }

        IDWriteLocalizedStrings* pFamilyNames = NULL;
        
        // Get a list of localized strings for the family name.
        if (SUCCEEDED(hr))
        {
            hr = pFontFamily->GetFamilyNames(&pFamilyNames);
        }

        UINT32 index = 0;
        BOOL exists = false;
        
        wchar_t localeName[LOCALE_NAME_MAX_LENGTH];

        if (SUCCEEDED(hr))
        {
            // Get the default locale for this user.
            int defaultLocaleSuccess = GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH);

            // If the default locale is returned, find that locale name, otherwise use "en-us".
            if (defaultLocaleSuccess)
            {
                hr = pFamilyNames->FindLocaleName(localeName, &index, &exists);
            }
            if (SUCCEEDED(hr) && !exists) // if the above find did not find a match, retry with US English
            {
                hr = pFamilyNames->FindLocaleName(L"en-us", &index, &exists);
            }
        }
        
        // If the specified locale doesn't exist, select the first on the list.
        if (!exists)
            index = 0;

        UINT32 length = 0;

        // Get the string length.
        if (SUCCEEDED(hr))
        {
            hr = pFamilyNames->GetStringLength(index, &length);
        }

        // Allocate a string big enough to hold the name.
        wchar_t* name = new (std::nothrow) wchar_t[length+1];
        if (name == NULL)
        {
            hr = E_OUTOFMEMORY;
        }

        // Get the family name.
        if (SUCCEEDED(hr))
        {
            hr = pFamilyNames->GetString(index, name, length+1);
        }
        if (SUCCEEDED(hr))
        {
            // Print out the family name.
            wprintf(L"%s\n", name);
        }

        SafeRelease(&pFontFamily);
        SafeRelease(&pFamilyNames);

        delete [] name;
    }

    SafeRelease(&pFontCollection);
    SafeRelease(&pDWriteFactory);
}

文本呈现

文本呈现 API 使DirectWrite字体中的字形可以呈现到 Direct2D 图面或 GDI 设备独立位图,或者转换为轮廓或位图。 与 Windows 上以前的实现相比,DirectWrite中的 ClearType 呈现支持子像素定位,并且具有更好的锐度和对比度。 DirectWrite还支持带别名的黑白文本,以支持涉及具有嵌入位图的东亚字体的方案,或者用户禁用了任何类型的字体平滑。

所有选项都可通过DirectWrite API 访问的所有可用 ClearType 旋钮进行调整,并通过新的 Windows 7 ClearType 优化器控制面板小程序公开。

有两个 API 可用于呈现字形,一个通过 Direct2D 提供硬件加速呈现,另一个提供软件呈现到 GDI 位图。 使用 IDWriteTextLayout 并实现 IDWriteTextRenderer 回调的应用程序可以调用上述任一函数来响应 DrawGlyphRun 回调。 此外,实现自己的布局或处理字形级数据的应用程序可以使用这些 API。

  1. ID2DRenderTarget::D rawGlyphRun

    应用程序可以使用 Direct2D API DrawGlyphRun 为使用 GPU 的文本呈现提供硬件加速。 硬件加速会影响文本呈现管道的所有阶段-从将字形合并为字形运行和筛选字形运行位图,到将 ClearType 混合算法应用于最终显示的输出。 这是用于获得最佳呈现性能的建议 API。

  2. IDWriteBitmapRenderTarget::D rawGlyphRun

    应用程序可以使用 IDWriteBitmapRenderTarget::D rawGlyphRun 方法将一组字形软件呈现为 32-bpp 位图。 IDWriteBitmapRenderTarget 对象封装可用于呈现字形的位图和内存设备上下文。 如果想要继续使用 GDI,此 API 非常有用,因为有一个在 GDI 中呈现的现有代码库。

如果你有一个具有使用 GDI 的现有文本布局代码的应用程序,并且你想要保留其现有的布局代码,但仅将DirectWrite用于呈现字形的最后一步,则 IDWriteGdiInterop::CreateFontFaceFromHdc 会在两个 API 之间提供桥接。 在调用此函数之前,应用程序将使用 IDWriteGdiInterop::CreateFontFaceFromHdc 函数从设备上下文中获取字体面引用。

注意

在大多数情况下,应用程序可能不需要使用这些字形呈现 API。 应用程序创建 IDWriteTextLayout 对象后,可以使用 ID2D1RenderTarget::D rawTextLayout 方法来呈现文本。

 

自定义呈现模式

许多参数会影响文本呈现,例如 gamma、ClearType 级别、像素几何图形和增强对比度。 呈现参数由实现公共 IDWriteRenderingParams 接口的对象封装。 呈现参数对象根据通过 Windows 7 中的 ClearType 控制面板小程序指定的硬件属性和/或用户首选项自动初始化。 通常,如果客户端使用DirectWrite布局 API,DirectWrite将自动选择与指定测量模式对应的呈现模式。

需要更多控件的应用程序可以使用 IDWriteFactory::CreateCustomRenderingParams 来实现不同的呈现选项。 此函数还可用于设置灰度、像素几何图形和增强对比度。

以下是可用的各种呈现选项:

  • 子像素抗锯齿

    应用程序将 renderingMode 参数设置为DWRITE_RENDERING_MODE_NATURAL指定仅在水平维度中使用抗锯齿的呈现。

  • 水平和垂直维度中的子像素抗锯齿。

    应用程序将 renderingMode 参数设置为 DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC,以在水平和垂直维度中指定具有抗锯齿的呈现。 这使得曲线和对角线看起来更平滑,牺牲了一些柔和性,并且通常用于大于 16 ppem 的大小。

  • 别名文本

    应用程序将 renderingMode 参数设置为DWRITE_RENDERING_MODE_ALIASED以指定别名文本。

  • 灰度文本

    应用程序将 pixelGeometry 参数设置为DWRITE_PIXEL_GEOMETRY_FLAT以指定灰度文本。

  • GDI 兼容宽度 (,包括东亚嵌入式位图)

    应用程序将 renderingMode 参数设置为 DWRITE_RENDERING_MODE_GDI_CLASSIC以指定 GDI 兼容宽度抗锯齿。

  • GDI 自然宽度

    应用程序将 renderingMode 参数设置为 DWRITE_RENDERING_MODE_GDI_NATURAL以指定与 GDI 自然宽度兼容的抗锯齿。

  • 大纲文本

    对于大尺寸呈现,应用程序开发人员可能更喜欢使用字体轮廓而不是光栅化为位图来呈现。 应用程序将 renderingMode 参数设置为 DWRITE_RENDERING_MODE_OUTLINE ,以指定渲染应绕过光栅器并直接使用轮廓。

GDI 互操作性

IDWriteGdiInterop 接口提供与 GDI 的互操作性。 这使应用程序能够继续对 GDI 代码库的现有投资,并有选择地将DirectWrite用于呈现或布局。

以下是使应用程序能够迁移到 GDI 字体系统或从 GDI 字体系统迁移的 API:

结论

无论是在屏幕上还是在纸上,改善阅读体验对用户都有很大的价值。 DirectWrite为应用程序开发人员提供易用性和分层编程模型,以改善其 Windows 应用程序的文本体验。 应用程序可以使用 DirectWrite 通过布局 API 呈现其 UI 和文档的丰富格式文本。 对于更复杂的方案,应用程序可以直接使用字形、访问字体等,并利用DirectWrite的强大功能来提供高质量的版式。

DirectWrite的互操作性功能使应用程序开发人员能够延续其现有的 Win32 代码库,并有选择地在其应用程序中采用DirectWrite。