教程:使用 DirectWrite 入门

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

本教程包含以下部分:

源代码

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

功能 说明
CreateDeviceIndependentResources 创建与设备无关的资源,以便可在任何位置重复使用。
DiscardDeviceIndependentResources 在不再需要与设备无关的资源后释放这些资源。
CreateDeviceResources 创建绑定到特定设备的资源,如画笔和呈现目标。
DiscardDeviceResources 在不再需要设备相关的资源后释放这些资源。
DrawD2DContent 使用 Direct2D 呈现到屏幕。
DrawText 使用 Direct2D 绘制文本字符串。
OnResize 在窗口大小更改时调整 Direct2D 呈现目标的大小。

 

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

绘制简单文本

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

以单个格式显示“hello world using directwrite!”的屏幕截图

在屏幕上绘制简单文本需要四个组件:

  • 要呈现的字符串。
  • 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::D iscardDeviceResources 方法中,释放画笔和呈现目标。

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

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

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

  1. 在类的 SimpleText::D rawText 方法中,通过检索呈现区域的尺寸来定义文本布局的区域,并创建具有相同尺寸的 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::D rawText 方法采用以下参数:

    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::D rawText 方法。
  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 using 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::D rawText 方法中的代码更改为使用 IDWriteTextLayout::D rawTextLayout

  1. Delcare 一 个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 中实现。