教程:DirectWrite 入门

本文档介绍如何使用 DirectWriteDirect2D 创建包含单个格式的简单文本,以及包含多种格式的文本。

本教程包含以下部分:

源代码

本概述中显示的源代码取自 DirectWrite Hello World 示例。 每个部分在单独的类(SimpleText 和 MultiformattedText)中实现,并显示在单独的子窗口中。 每个类表示Microsoft Win32 窗口。 除了 WndProc 方法,每个类还包含以下方法:

功能 DESCRIPTION
CreateDeviceIndependentResources 创建独立于设备的资源,以便可以在任何位置重复使用它们。
DiscardDeviceIndependentResources 当不再需要时,释放独立于设备的资源。
CreateDeviceResources 创建与特定设备关联的资源,例如画笔和渲染目标。
DiscardDeviceResources 不再需要依赖设备的资源后释放这些资源。
DrawD2DContent 使用 Direct2D 渲染到屏幕。
DrawText 使用 Direct2D 绘制文本字符串。
OnResize 窗口大小更改时调整 Direct2D 渲染目标的大小。

 

可以使用提供的示例,或使用以下说明将 DirectWriteDirect2D 添加到你自己的 Win32 应用程序。 有关示例和关联的项目文件的详细信息,请参阅 DirectWrite HelloWorld

绘制简单文本

本部分介绍如何使用 DirectWriteDirect2D 呈现具有单个格式的简单文本,如以下屏幕截图所示。

以单个格式显示“使用 DirectWrite 的 Hello World!”的屏幕截图

将简单文本绘制到屏幕需要四个组件:

  • 要呈现的字符串。
  • IDWriteTextFormat 的实例。
  • 要包含文本的区域的尺寸。
  • 可以呈现文本的对象。 在本教程中。 使用 Direct2D 渲染目标。

IDWriteTextFormat 接口描述用于设置文本格式的字体系列名称、大小、粗细、样式和拉伸,并描述区域设置信息。 IDWriteTextFormat 还定义了用于设置和获取以下属性的方法:

  • 行距。
  • 相对于布局框的左边缘和右边缘的文本对齐方式。
  • 相对于布局框顶部和底部的段落对齐方式。
  • 阅读方向。
  • 溢出布局框的文本的文本剪裁粒度。
  • 增量式制表位。
  • 段落排列方向。

使用本文档中所述的两个过程绘制文本时,需要使用 IDWriteTextFormat 接口。

在创建 IDWriteTextFormat 对象或任何其他 DirectWrite 对象之前,需要 IDWriteFactory 实例。 使用 IDWriteFactory 创建 IDWriteTextFormat 实例和其他 DirectWrite 对象。 若要获取工厂实例,请使用 DWriteCreateFactory 函数。

第 1 部分:声明 DirectWrite 和 Direct2D 资源。

在本部分中,你将声明一些对象,这些对象稍后将用于创建并显示文本,并作为类的私有数据成员。 DirectWrite 的所有接口、函数和数据类型都在 dwrite.h 头文件中声明,Direct2D 的所有接口、函数和数据类型在 d2d1.h 中声明;如果尚未执行此作,请将这些标头包含在项目中。

  1. 在类头文件中(SimpleText.h),将指向 IDWriteFactoryIDWriteTextFormat 接口的指针声明为私有成员。

    IDWriteFactory* pDWriteFactory_;
    IDWriteTextFormat* pTextFormat_;
    
    
  2. 声明成员变量用于保存要呈现的文本字符串及其长度。

    const wchar_t* wszText_;
    UINT32 cTextLength_;
    
    
  3. 声明指向 ID2D1FactoryID2D1HwndRenderTargetID2D1SolidColorBrush 接口的指针,以便使用 Direct2D 呈现文本。

    ID2D1Factory* pD2DFactory_;
    ID2D1HwndRenderTarget* pRT_;
    ID2D1SolidColorBrush* pBlackBrush_;
    
    

第 2 部分:创建独立于设备的资源。

Direct2D 提供两种类型的资源:依赖于设备的资源和设备无关的资源。 设备依赖资源与呈现设备相关联,如果删除该设备,则不再正常运行。 另一方面,与设备无关的资源可以在整个应用程序范围内使用。

DirectWrite 资源与设备无关。

在本部分中,将创建应用程序使用的与设备无关的资源。 必须通过调用接口的 Release 方法释放这些资源。

某些用于的资源必须只创建一次,并且不会绑定到设备。 这些资源的初始化放置在 SimpleText::CreateDeviceIndependentResources 方法中,该方法在初始化类时调用。

  1. 在类实现文件(SimpleText.cpp)的 SimpleText::CreateDeviceIndependentResources 方法中,调用 D2D1CreateFactory 函数来创建 ID2D1Factory 接口,这是所有 Direct2D 对象的根工厂接口。 您使用相同的工厂来实例化其他的 Direct2D 资源。

    hr = D2D1CreateFactory(
        D2D1_FACTORY_TYPE_SINGLE_THREADED,
        &pD2DFactory_
        );
    
    
  2. 调用 DWriteCreateFactory 函数以创建 IDWriteFactory 接口,该接口是所有 DirectWrite 对象的根工厂接口。 使用相同的工厂来实例化其他 DirectWrite 资源。

    if (SUCCEEDED(hr))
    {
        hr = DWriteCreateFactory(
            DWRITE_FACTORY_TYPE_SHARED,
            __uuidof(IDWriteFactory),
            reinterpret_cast<IUnknown**>(&pDWriteFactory_)
            );
    }
    
    
  3. 初始化文本字符串并存储其长度。

    wszText_ = L"Hello World using  DirectWrite!";
    cTextLength_ = (UINT32) wcslen(wszText_);
    
    
  4. 使用 IDWriteFactory::CreateTextFormat 方法创建 IDWriteTextFormat 接口对象。 IDWriteTextFormat 指定用于呈现文本字符串的字体、粗细、拉伸、样式和区域设置。

    if (SUCCEEDED(hr))
    {
        hr = pDWriteFactory_->CreateTextFormat(
            L"Gabriola",                // Font family name.
            NULL,                       // Font collection (NULL sets it to use the system font collection).
            DWRITE_FONT_WEIGHT_REGULAR,
            DWRITE_FONT_STYLE_NORMAL,
            DWRITE_FONT_STRETCH_NORMAL,
            72.0f,
            L"en-us",
            &pTextFormat_
            );
    }
    
    
  5. 通过调用 IDWriteTextFormat::SetTextAlignmentIDWriteTextFormat::SetParagraphAlignment 方法实现文本水平和垂直居中。

    // Center align (horizontally) the text.
    if (SUCCEEDED(hr))
    {
        hr = pTextFormat_->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
    }
    
    if (SUCCEEDED(hr))
    {
        hr = pTextFormat_->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
    }
    
    

在本部分中,你初始化了应用程序使用的设备无关的资源。 在下一部分中,将初始化依赖于设备的资源。

第 3 部分:创建 Device-Dependent 资源。

在本部分中,你将创建 ID2D1HwndRenderTargetID2D1SolidColorBrush 来呈现文本。

呈现目标是一个 Direct2D 对象,该对象创建绘图资源并将绘图命令呈现到呈现设备。 ID2D1HwndRenderTarget 是渲染到 HWND 的渲染目标。

渲染目标可以创建的绘图资源之一是一种用于绘制轮廓、填充和文本的画笔。 ID2D1SolidColorBrush可用于以纯色进行绘制。

ID2D1HwndRenderTargetID2D1SolidColorBrush 接口在创建时绑定到呈现设备,如果设备无效,则必须释放和重新创建它们。

  1. 在 SimpleText::CreateDeviceResources 方法中,检查呈现目标指针是否为 NULL。 如果是,请检索呈现区域的大小,并创建该大小的 ID2D1HwndRenderTarget 。 使用 ID2D1HwndRenderTarget 创建 ID2D1SolidColorBrush

    RECT rc;
    GetClientRect(hwnd_, &rc);
    
    D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top);
    
    if (!pRT_)
    {
        // Create a Direct2D render target.
        hr = pD2DFactory_->CreateHwndRenderTarget(
                D2D1::RenderTargetProperties(),
                D2D1::HwndRenderTargetProperties(
                    hwnd_,
                    size
                    ),
                &pRT_
                );
    
        // Create a black brush.
        if (SUCCEEDED(hr))
        {
            hr = pRT_->CreateSolidColorBrush(
                D2D1::ColorF(D2D1::ColorF::Black),
                &pBlackBrush_
                );
        }
    }
    
    
  2. 在 SimpleText::DiscardDeviceResources 方法中,释放画笔和渲染目标。

    SafeRelease(&pRT_);
    SafeRelease(&pBlackBrush_);
    
    

创建呈现目标和画笔后,可以使用它们来呈现文本。

第 4 部分:使用 Direct2D DrawText 方法绘制文本。

  1. 在类的 SimpleText::DrawText 方法中,通过检索呈现区域的尺寸来定义文本布局的区域,并创建具有相同尺寸的 Direct2D 矩形。

    D2D1_RECT_F layoutRect = D2D1::RectF(
        static_cast<FLOAT>(rc.left) / dpiScaleX_,
        static_cast<FLOAT>(rc.top) / dpiScaleY_,
        static_cast<FLOAT>(rc.right - rc.left) / dpiScaleX_,
        static_cast<FLOAT>(rc.bottom - rc.top) / dpiScaleY_
        );
    
    
  2. 使用 ID2D1RenderTarget::D rawText 方法和 IDWriteTextFormat 对象将文本呈现到屏幕。 ID2D1RenderTarget::DrawText 方法使用以下参数:

    pRT_->DrawText(
        wszText_,        // The string to render.
        cTextLength_,    // The string's length.
        pTextFormat_,    // The text format.
        layoutRect,       // The region of the window where the text will be rendered.
        pBlackBrush_     // The brush used to draw the text.
        );
    
    

第 5 部分:使用 Direct2D 呈现窗口内容

若要在收到画图消息时使用 Direct2D 呈现窗口的内容,请执行以下作:

  1. 通过调用第 3 部分中实现的 SimpleText::CreateDeviceResources 方法创建设备依赖资源。
  2. 调用呈现目标的 ID2D1HwndRenderTarget::BeginDraw 方法。
  3. 通过调用 ID2D1HwndRenderTarget::Clear 方法清除呈现目标。
  4. 调用在第 4 章中实现的 SimpleText::DrawText 方法。
  5. 调用呈现目标的 ID2D1HwndRenderTarget::EndDraw 方法。
  6. 如有必要,请放弃依赖设备的资源,以便在重新绘制窗口时重新创建它们。
hr = CreateDeviceResources();

if (SUCCEEDED(hr))
{
    pRT_->BeginDraw();

    pRT_->SetTransform(D2D1::IdentityMatrix());

    pRT_->Clear(D2D1::ColorF(D2D1::ColorF::White));

    // Call the DrawText method of this class.
    hr = DrawText();

    if (SUCCEEDED(hr))
    {
        hr = pRT_->EndDraw(
            );
    }
}

if (FAILED(hr))
{
    DiscardDeviceResources();
}

SimpleText 类在 SimpleText.h 中实现,SimpleText.cpp。

绘制具有多种格式的文本。

本部分介绍如何使用 DirectWriteDirect2D 呈现具有多种格式的文本,如以下屏幕截图所示。

“Hello World 使用 DirectWrite!”的屏幕截图,某些部分具有不同的样式、大小和格式

本部分的代码作为 DirectWrite HelloWorld 中的 MultiformattedText 类实现。 它基于上一部分的步骤。

若要创建多格式文本,除了上一节中引入的 IDWriteTextFormat 接口外,还可以使用 IDWriteTextLayout 接口。 IDWriteTextLayout 接口描述文本块的格式和布局。 除了 IDWriteTextFormat 对象指定的默认格式外,还可以使用 IDWriteTextLayout 更改特定文本范围的格式设置。 这包括字体系列名称、大小、粗细、样式、拉伸、删除线和下划线。

IDWriteTextLayout 还提供命中测试方法。 这些方法返回的命中测试指标是相对于在使用 IDWriteFactory 接口的 CreateTextLayout 方法创建 IDWriteTextLayout 接口对象时所指定的布局框。

IDWriteTypography 接口用于向文本布局添加可选的 OpenType 版式功能,例如填充和替代样式文本集。 可以通过调用 IDWriteTypography 接口的 AddFontFeature 方法,将版式功能添加到文本布局中的特定文本范围。 此方法接收 DWRITE_FONT_FEATURE 结构作为包含 DWRITE_FONT_FEATURE_TAG 枚举常量和 UINT32 执行参数的参数。 可在 microsoft.com 上的 OpenType 布局标记注册表中找到已注册的 OpenType 功能列表。 有关等效的 DirectWrite 枚举常量,请参阅 DWRITE_FONT_FEATURE_TAG

第 1 部分:创建 IDWriteTextLayout 接口。

  1. 将指向 IDWriteTextLayout 接口的指针声明为 MultiformattedText 类的成员。

    IDWriteTextLayout* pTextLayout_;
    
    
  2. 在 MultiformattedText::CreateDeviceIndependentResources 方法的末尾,通过调用 CreateTextLayout 方法创建 IDWriteTextLayout 接口对象。 IDWriteTextLayout 接口提供了其他格式设置功能,例如能够对所选文本部分应用不同的格式。

    // Create a text layout using the text format.
    if (SUCCEEDED(hr))
    {
        RECT rect;
        GetClientRect(hwnd_, &rect); 
        float width  = rect.right  / dpiScaleX_;
        float height = rect.bottom / dpiScaleY_;
    
        hr = pDWriteFactory_->CreateTextLayout(
            wszText_,      // The string to be laid out and formatted.
            cTextLength_,  // The length of the string.
            pTextFormat_,  // The text format to apply to the string (contains font information, etc).
            width,         // The width of the layout box.
            height,        // The height of the layout box.
            &pTextLayout_  // The IDWriteTextLayout interface pointer.
            );
    }
    
    

第 2 部分:使用 IDWriteTextLayout 应用格式设置。

格式(如字号、粗细和下划线)可以应用于使用 IDWriteTextLayout 接口显示的文本子字符串。

  1. 通过声明 DWRITE_TEXT_RANGE 并调用 IDWriteTextLayout::SetFontSize 方法,将“DirectWrite”的子字符串“Di”的字号设置为 100。

    // Format the "DirectWrite" substring to be of font size 100.
    if (SUCCEEDED(hr))
    {
        DWRITE_TEXT_RANGE textRange = {20,        // Start index where "DirectWrite" appears.
                                        6 };      // Length of the substring "Direct" in "DirectWrite".
        hr = pTextLayout_->SetFontSize(100.0f, textRange);
    }
    
  2. 通过调用 IDWriteTextLayout::SetUnderline 方法,为子字符串“DirectWrite”下划线。

    // Format the word "DWrite" to be underlined.
    if (SUCCEEDED(hr))
    {
    
        DWRITE_TEXT_RANGE textRange = {20,      // Start index where "DirectWrite" appears.
                                       11 };    // Length of the substring "DirectWrite".
        hr = pTextLayout_->SetUnderline(TRUE, textRange);
    }
    
  3. 通过调用 IDWriteTextLayout::SetFontWeight 方法,将子字符串“DirectWrite”的字体粗体设置为粗体。

    if (SUCCEEDED(hr))
    {
        // Format the word "DWrite" to be bold.
        DWRITE_TEXT_RANGE textRange = {20,
                                       11 };
        hr = pTextLayout_->SetFontWeight(DWRITE_FONT_WEIGHT_BOLD, textRange);
    }
    

第 3 部分:使用 IDWriteTypography 添加版式功能。

  1. 通过调用 IDWriteFactory::CreateTypography 方法声明和创建 IDWriteTypography 接口对象。

    // Declare a typography pointer.
    IDWriteTypography* pTypography = NULL;
    
    // Create a typography interface object.
    if (SUCCEEDED(hr))
    {
        hr = pDWriteFactory_->CreateTypography(&pTypography);
    }
    
    
  2. 通过声明一个具有指定样式集 7 的DWRITE_FONT_FEATURE对象,并调用IDWriteTypography::AddFontFeature 方法来添加字体功能。

    // Set the stylistic set.
    DWRITE_FONT_FEATURE fontFeature = {DWRITE_FONT_FEATURE_TAG_STYLISTIC_SET_7,
                                       1};
    if (SUCCEEDED(hr))
    {
        hr = pTypography->AddFontFeature(fontFeature);
    }
    
    
  3. 通过声明 DWRITE_TEXT_RANGE 变量并调用 IDWriteTextLayout::SetTypography 方法并传入文本范围,将文本布局设置为使用整个字符串的版式。

    if (SUCCEEDED(hr))
    {
        // Set the typography for the entire string.
        DWRITE_TEXT_RANGE textRange = {0,
                                       cTextLength_};
        hr = pTextLayout_->SetTypography(pTypography, textRange);
    }
    
    
  4. 在 MultiformattedText::OnResize 方法中设置文本布局对象的新宽度和高度。

    if (pTextLayout_)
    {
        pTextLayout_->SetMaxWidth(static_cast<FLOAT>(width / dpiScaleX_));
        pTextLayout_->SetMaxHeight(static_cast<FLOAT>(height / dpiScaleY_));
    }
    

第 4 部分:使用 Direct2D DrawTextLayout 方法绘制文本。

若要使用 IDWriteTextLayout 对象指定的文本布局设置绘制文本,请将 MultiformattedText::DrawText 方法中的代码更改为使用 IDWriteTextLayout::DrawTextLayout

  1. 声明 D2D1_POINT_2F 变量,并将其设置为窗口左上角。

    D2D1_POINT_2F origin = D2D1::Point2F(
        static_cast<FLOAT>(rc.left / dpiScaleX_),
        static_cast<FLOAT>(rc.top / dpiScaleY_)
        );
    
    
  2. 通过调用 Direct2D 呈现目标的 ID2D1RenderTarget::D rawTextLayout 方法并传递 IDWriteTextLayout 指针,将文本绘制到屏幕。

    pRT_->DrawTextLayout(
        origin,
        pTextLayout_,
        pBlackBrush_
        );
    
    

MultiformattedText 类在 MultiformattedText.h 中实现,MultiformattedText.cpp。