DirectWrite 简介

人们在日常生活中时刻通过文本进行交流。它是人们使用日益剧增的信息的主要方式。过去都是通过打印的内容(主要是文档、报纸、书籍等)来使用信息。现在,越来越多的是通过 Windows PC 上的在线内容使用信息。典型的 Windows 用户会花费大量的时间阅读计算机屏幕内容。他们可能是在网上冲浪、扫描电子邮件、撰写报告、填写电子表格或编写软件,但他们实际上都是在阅读。即使文本和字体几乎遍布 Windows 用户体验的每个部分,但是对于多数用户来说,阅读屏幕内容并不像阅读打印输出内容那样舒适。

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

本文介绍 DirectWrite 如何使 Windows 应用程序改善 UI 和文档的文本体验。

改善文本体验

现代 Windows 应用程序对其 UI 和文档中的文本具有更高要求。这些要求包括增强可读性、支持各种语言和脚本以及出色的呈现性能。此外,大多数现有应用程序需要通过某种方式继续使用对 WindowsWin32 代码库的现有投入。

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

呈现系统独立性

DirectWrite 独立于任何特定的图形技术。应用程序可自由选择最适合其需要的呈现技术。这样一来,应用程序便可以灵活地通过 GDI 继续呈现其应用程序的某些部分,并通过 Direct3D 或 Direct2D 呈现其他部分。实际上,应用程序可以选择通过专用的呈现堆栈呈现 DirectWrite。

高质量版式

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

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

对高级版式功能的支持

DirectWrite 使应用程序开发人员能解锁他们在 WinForms 或 GDI 中无法使用的 OpenType 字体功能。DirectWrite IDWriteTypography 对象公开了 OpenType 字体的许多高级功能,如样式备用项和花体。Microsoft Windows Software Development Kit (SDK) 提供了一组功能丰富的 OpenType 字体示例,如 Pericles 和 Pescadero 字体。有关 OpenType 功能的更多信息,请参见 OpenType Font Features (http://msdn.microsoft.com/en-us/library/ms745109.aspx)。

对国际文本的支持

DirectWrite 使用 OpenType 字体提供了对国际文本的广泛支持。支持 Unicode 功能,如代理项、BIDI、换行以及 UVS。语言引导的脚本明细、数字替换以及字形定型可确保任何脚本中的文本都具有正确的布局和呈现。

当前支持以下脚本:

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

  • 阿拉伯语
  • 亚美尼亚语
  • 孟加拉语
  • 注音
  • 盲文*
  • 加拿大土著音节
  • 切罗基语
  • 中文(简体和繁体)
  • 西里尔文
  • 科普特语*
  • 梵文
  • 埃塞俄比亚语
  • 格鲁吉亚语
  • 格拉哥里语*
  • 希腊语
  • 古吉拉特语
  • 果鲁穆奇语
  • 希伯来语
  • 日语
  • 卡纳达语
  • 高棉语
  • 韩语
  • 老挝语
  • 拉丁语
  • 马拉雅拉姆语
  • 蒙古语
  • 缅甸语
  • 西双版纳新傣文
  • 欧甘文*
  • 奥里雅语
  • 八思巴文
  • 古北欧文字*
  • 僧伽罗语
  • 叙利亚语
  • 德宏傣文
  • 泰米尔语
  • 泰卢固语
  • 塔纳文
  • 泰语
  • 藏语
  • 彝语

多个功能层

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

 

API 图。

 

文本布局 API 提供了 DirectWrite 中可用的最高级别功能。它为应用程序提供度量、显示以及与格式丰富的文本字符串交互的服务。可以在当前使用 Win32 的 DrawText 的应用程序中使用该文本 API 构建一个具有格式丰富文本的现代 UI。

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

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

DirectWrite 字体系统可用于所有 DirectWrite 功能层并且允许应用程序访问字体和字形信息。它旨在处理常用的字体技术和数据格式。DirectWrite 字体模型遵循常用版式惯例,即支持同一字体系列中任意程度的粗细、样式和拉伸。此模型与 WPF 和 CSS 遵循的模型相同,它指定将仅在粗细(粗、细等)、样式(竖直、斜体或倾斜​​)或拉伸(窄、压缩、宽等)方面有区别的字体视为一个字体系列的成员。

使用 ClearType 改善的文本呈现

提高屏幕可读性是所有 Windows 应用程序的一个关键要求。认知心理学的研究表明我们需要精确地识别每个字母,甚至字母之间的间距对于快速处理也是至关重要的。不对称的字母和单词非常不美观并且会降级阅读体验。Microsoft 高级阅读技术组的 Kevin Larson 撰写了一篇有关该主题的文章,该文章已在 Spectrum IEEE 中发表。该文章题为“文本技术”(http://www.spectrum.ieee.org/print/5049)。

DirectWrite 中的文本使用 Microsoft ClearType 进行呈现,这将提高文本的清晰度和可读性。ClearType 利用了现代 LCD 显示中每个像素都有可单独控制的 RGB 条纹这一事实。DirectWrite 使用了 ClearType 的最新改进功能(最初包含在附带 Windows Presentation Foundation 的 Windows Vista 中),这使它不仅能评估各个字母,而且还能评估字母之间的间距。在这些 ClearType 改进之前,“读取”大小为 10 或 12 磅的文本很难显示:我们可以在字母之间放置 1 个像素(通常太小)或者 2 个像素(通常太大)。在子像素中使用额外的分辨率为我们提供了小数间距,这会使整个页面更加均匀对称。

以下两个示例演示当使用子像素定位时字形如何从任一子像素边界处开始。

 

"Hello" 已呈现,没有使用子像素定位。

 

前面的示例是使用 GDI 版本的 ClearType 呈现器呈现的,其中未使用子像素定位。

下一个示例是使用 DirectWrite 版本的 ClearType 呈现器呈现的,其中使用了子像素定位。

 

"Hello" 已呈现,使用了子像素定位。

 

请注意,第二个图像中字母 h 和 n 之间的间距更均匀,字母 o 与字母 n 的间距更远并且与字母 l 的间距也更均匀。还请注意字母 l 的竖直线看起来更自然。

子像素 ClearType 定位提供了屏幕上字符的最精确间距,尤其是尺寸较小(子像素和整个像素之间的差距代表有效的字形宽度比例)时。如此便可通过子像素粒度以理想的分辨率间距度量文本,并且在 LCD 颜色条纹的适当位置上呈现该文本以使其自然呈现。根据定义,使用这种技术度量和呈现文本与分辨率无关,这意味着在各种显示分辨率下文本布局完全相同。

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

默认情况下,文本字符串 API 采用子像素文本呈现,这意味着它以其理想的分辨率(与当前显示分辨率无关)度量文本,并且根据真实比例的字形步进宽度和位置偏移量生成字形位置结果。

对于尺寸较大的文本,DirectWrite 还支持 y 轴抗锯齿,以使边缘更加平滑并按照字体设计者的预期呈现字母。以下屏幕快照显示了 Y 方向抗锯齿。

 

Y 方向抗锯齿的文本。

 

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

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

  • 子像素 ClearType(默认值)。
  • 在水平和垂直维度抗锯齿的子像素 ClearType。
  • 锯齿文本。
  • GDI 自然宽度(例如,由 Microsoft Word 阅读视图使用)。
  • GDI 兼容宽度(包括东亚嵌入位图)。
可以通过 DirectWrite API 和新的 Windows 7 内置 ClearType 调谐器调整这些呈现模式。

API 概述

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

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

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

使用 DirectWrite 格式化并绘制“Hello World”

以下代码示例演示应用程序如何使用 IDWriteTextFormat 格式化一个段落以及如何使用 Direct2D DrawText 函数绘制该段落。

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
        );
    }

    return hr;
}

访问字体系统

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

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

然后,应用程序可以调用 GetFontFamily 访问集合中的特定 FontFamily 对象,再调用 IDWriteFontFamily::GetFirstMatchingFont 访问特定 IDWriteFont 对象。IDWriteFont 对象表示字体集合中的某一个字体并且公开属性以及一些基本字体度量。

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

下图演示了这些对象之间的关系。

 

字体集合、字体系列和字体的关系图。

 

IDWriteFontFace

IDWriteFontFace 对象表示某个字体,该对象提供的字体相关信息要比 IDWriteFont 对象提供的更为详细。对于实现文本布局的应用程序来说,IDWriteFontFace 中的字体和字形度量非常有用。

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

下表总结了这两个对象的使用情形。

类别IDWriteFontIDWriteFontFace
支持字体选择器用户界面等用户交互环境的 API:描述 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 设备的位图,或者转换为边框或位图。DirectWrite 中的 ClearType 呈现支持子像素定位,并且与之前在 Windows 上的实现相比增强了清晰度和对比度。DirectWrite 还支持锯齿黑白文本,目的是支持涉及具有嵌入位图的东亚字体的方案或用户已禁用任何类型的字体平滑显示的方案。

所有选项均可由通过 DirectWrite API 可访问的所有可用 ClearType 旋钮进行调整,并且还可以通过新的 Windows 7 ClearType 调谐器控制面板小程序来公开这些选项。

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

  1. ID2DRenderTarget::DrawGlyphRun

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

  2. IDWriteBitmapRenderTarget::DrawGlyphRun

    应用程序可以使用 IDWriteBitmapRenderTarget::DrawGlyphRun 方法执行一串字形到 32-bpp 位图的软件呈现。IDWriteBitmapRenderTarget 对象封装可用于呈现字形的位图和内存设备上下文。当您拥有一个采用 GDI 呈现的现有代码库,因此想保留 GDI 时,此 API 非常有用。

如果您的应用程序具有使用 GDI 的现有文本布局代码,并且您想保留其现有布局代码,而只对呈现字形的最后一步使用 DirectWrite,则 IDWriteGdiInterop::CreateFontFaceFromHdc 在这两个 API 之间提供了桥梁。调用此函数之前,应用程序将使用 IDWriteGdiInterop::CreateFontFaceFromHdc 函数从设备上下文获取字体引用。

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

自定义呈现模式

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

希望获得更多控制的应用程序可以使用 IDWriteFactory::CreateCustomRenderingParams 来实现不同的呈现选项。此函数还可用于设置伽玛值、ClearType 级别、像素几何图形以及增强的对比度。

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

  • 子像素 ClearType

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

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

    应用程序将 renderingMode 参数设置为 DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC 以指定在水平和垂直维度都抗锯齿的 ClearType 呈现。这样便使曲线和对角线看起来更平滑(但会牺牲一些柔度),因此通常在尺寸高于 16 ppem 时使用。

  • 锯齿文本

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

  • 灰度文本

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

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

    应用程序将 renderingMode 参数设置为 DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC 以指定与 GDI 兼容的宽度 ClearType。

  • GDI 自然宽度

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

  • 轮廓文本

    为了以较大尺寸呈现,应用程序开发人员可能会首选使用字体轮廓呈现,而不光栅化为位图。应用程序将 renderingMode 参数设置为 DWRITE_RENDERING_MODE_OUTLINE 以指定呈现应绕过光栅器而直接使用轮廓。

GDI 互操作性

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

下面列出了能使应用程序迁移到 GDI 字体系统或从 GDI 字体系统迁移出的 API:

  • CreateFontFromLOGFONT

    创建与 LOGFONT 结构所指定的属性相匹配的 IDWriteFont 对象。

  • ConvertFontToLOGFONT

    基于指定的 IDWriteFont 的 GDI 兼容属性初始化 LOGFONT 结构。

  • ConvertFontFaceToLOGFONT

    基于指定的 IDWriteFontFace 的 GDI 兼容属性初始化 LOGFONT 结构。

  • CreateFontFaceFromHdc

    创建与当前所选择的 HFONT 相对应的 IDWriteFontFace 对象。

总结

无论是对于屏幕显示内容还是纸张上的内容,改善阅读体验都对用户具有非常重要的意义。DirectWrite 提供了易于使用且分层的编程模型,应用程序开发人员可以使用该模型改善其 Windows 应用程序的文本体验。应用程序可以使用 DirectWrite 通过布局 API 为其 UI 和文档呈现格式丰富的文本。对于更复杂的方案,应用程序可以直接处理字形、访问字体等,并且利用 DirectWrite 的功能来交付高质量的版式。

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

 

 

社区附加资源

添加
显示:
© 2015 Microsoft