如何向文本布局添加客户端绘图效果

提供有关将客户端绘图效果添加到使用 IDWriteTextLayout 接口和自定义文本呈现器显示文本的DirectWrite应用程序的简短教程。

本教程的最终产品是一个应用程序,用于显示具有不同颜色绘制效果的文本范围的文本,如以下屏幕截图所示。

不同颜色的“客户端绘图效果示例!”的屏幕截图

注意

本教程旨在简化如何创建自定义客户端绘图效果的示例,而不是绘制颜色文本的简单方法的示例。 有关详细信息,请参阅 IDWriteTextLayout::SetDrawingEffect 参考页。

 

本教程包含以下部分:

步骤 1:创建文本布局

首先,需要一个具有 IDWriteTextLayout 对象的应用程序。 如果已有一个使用文本布局显示文本的应用程序,或者正在使用自定义 DrawingEffect 示例代码,请跳到步骤 2。

若要添加文本布局,必须执行以下操作:

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

    IDWriteTextLayout* pTextLayout_;
    
    
  2. CreateDeviceIndependentResources 方法的末尾,通过调用 CreateTextLayout 方法创建 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.
            );
    }
    
    
  3. 最后,请记住释放析构函数中的文本布局。

    SafeRelease(&pTextLayout_);
    

步骤 2:实现自定义绘图效果类

除了从 IUnknown 继承的方法外,自定义客户端绘图效果接口对它必须实现的内容没有要求。 在这种情况下, ColorDrawingEffect 类只保存 一个D2D1_COLOR_F 值,并声明用于获取和设置此值的方法,以及一个最初可以设置颜色的构造函数。

将客户端绘图效果应用于 IDWriteTextLayout 对象中的文本范围后,绘制效果将传递给要呈现的任何字形运行的 IDWriteTextRenderer::D rawGlyphRun 方法。 然后,文本呈现器可以使用绘图效果的方法。

客户端绘图效果可以尽可能复杂,可以携带比此示例中更多的信息,并提供更改字形的方法、创建用于绘图的对象等。

步骤 3:实现自定义文本呈现器类

若要利用客户端绘图效果,必须实现自定义文本呈现器。 此文本呈现器会将 IDWriteTextLayout::D raw 方法传递给它的绘图效果应用于正在绘制的字形运行。

构造函数

自定义文本呈现器的构造函数存储将用于创建 Direct2D 对象的 ID2D1Factory 对象,以及将文本绘制到的 Direct2D 呈现器目标。

CustomTextRenderer::CustomTextRenderer(
    ID2D1Factory* pD2DFactory, 
    ID2D1HwndRenderTarget* pRT
    )
:
cRefCount_(0), 
pD2DFactory_(pD2DFactory), 
pRT_(pRT)
{
    pD2DFactory_->AddRef();
    pRT_->AddRef();
}

DrawGlyphRun 方法

字形运行是一组共享相同格式的字形,包括客户端绘制效果。 DrawGlyphRun 方法负责指定字形运行的文本呈现。

首先,创建 ID2D1PathGeometryID2D1GeometrySink,然后使用 IDWriteFontFace::GetGlyphRunOutline 检索字形运行大纲。 然后,使用 Direct2DID2D1Factory::CreateTransformedGeometry 方法转换几何图形的原点,如以下代码所示。

HRESULT hr = S_OK;

// Create the path geometry.
ID2D1PathGeometry* pPathGeometry = NULL;
hr = pD2DFactory_->CreatePathGeometry(
        &pPathGeometry
        );

// Write to the path geometry using the geometry sink.
ID2D1GeometrySink* pSink = NULL;
if (SUCCEEDED(hr))
{
    hr = pPathGeometry->Open(
        &pSink
        );
}

// Get the glyph run outline geometries back from DirectWrite and place them within the
// geometry sink.
if (SUCCEEDED(hr))
{
    hr = glyphRun->fontFace->GetGlyphRunOutline(
        glyphRun->fontEmSize,
        glyphRun->glyphIndices,
        glyphRun->glyphAdvances,
        glyphRun->glyphOffsets,
        glyphRun->glyphCount,
        glyphRun->isSideways,
        glyphRun->bidiLevel%2,
        pSink
        );
}

// Close the geometry sink
if (SUCCEEDED(hr))
{
    hr = pSink->Close();
}

// Initialize a matrix to translate the origin of the glyph run.
D2D1::Matrix3x2F const matrix = D2D1::Matrix3x2F(
    1.0f, 0.0f,
    0.0f, 1.0f,
    baselineOriginX, baselineOriginY
    );

// Create the transformed geometry
ID2D1TransformedGeometry* pTransformedGeometry = NULL;
if (SUCCEEDED(hr))
{
    hr = pD2DFactory_->CreateTransformedGeometry(
        pPathGeometry,
        &matrix,
        &pTransformedGeometry
        );
}

接下来,声明 一个 Direct2D 实心画笔对象。

ID2D1SolidColorBrush* pBrush = NULL;

如果 clientDrawingEffect 参数不为 NULL,请查询 ColorDrawingEffect 接口的对象。 这将起作用,因为您将此类设置为对文本布局对象的文本范围进行客户端绘制效果。

拥有指向 ColorDrawingEffect 接口的指针后,可以使用 GetColor 方法检索它存储的D2D1_COLOR_F值。 然后,使用 D2D1_COLOR_F 以该颜色创建 ID2D1SolidColorBrush

如果 clientDrawingEffect 参数为 NULL,则只需创建黑色 ID2D1SolidColorBrush

// If there is a drawing effect create a color brush using it, otherwise create a black brush.
if (clientDrawingEffect != NULL)
{
    // Go from IUnknown to ColorDrawingEffect.
    ColorDrawingEffect *colorDrawingEffect;

    clientDrawingEffect->QueryInterface(__uuidof(ColorDrawingEffect), reinterpret_cast<void**>(&colorDrawingEffect));

    // Get the color from the ColorDrawingEffect object.
    D2D1_COLOR_F color;

    colorDrawingEffect->GetColor(&color);

    // Create the brush using the color pecified by our ColorDrawingEffect object.
    if (SUCCEEDED(hr))
    {
        hr = pRT_->CreateSolidColorBrush(
            color,
            &pBrush);
    }

    SafeRelease(&colorDrawingEffect);
}
else
{
    // Create a black brush.
    if (SUCCEEDED(hr))
    {
        hr = pRT_->CreateSolidColorBrush(
            D2D1::ColorF(
            D2D1::ColorF::Black
            ),
            &pBrush);
    }
}

最后,绘制轮廓几何图形并使用刚刚创建的纯色画笔填充它。

if (SUCCEEDED(hr))
{
    // Draw the outline of the glyph run
    pRT_->DrawGeometry(
        pTransformedGeometry,
        pBrush
        );

    // Fill in the glyph run
    pRT_->FillGeometry(
        pTransformedGeometry,
        pBrush
        );
}

析构函数

不要忘记释放 Direct2D 工厂并在析构函数中呈现目标。

CustomTextRenderer::~CustomTextRenderer()
{
    SafeRelease(&pD2DFactory_);
    SafeRelease(&pRT_);
}

步骤 4:创建文本呈现器

CreateDeviceDependent 资源中,创建自定义文本呈现器对象。 它依赖于设备,因为它使用 ID2D1RenderTarget,而 ID2D1RenderTarget 本身依赖于设备。

// Create the text renderer
pTextRenderer_ = new CustomTextRenderer(
                        pD2DFactory_,
                        pRT_
                        );

步骤 5:实例化颜色绘制效果对象

以红色、绿色和蓝色实例化 ColorDrawingEffect 对象。

// Instantiate some custom color drawing effects.
redDrawingEffect_ = new ColorDrawingEffect(
    D2D1::ColorF(
        D2D1::ColorF::Red
        )
    );

blueDrawingEffect_ = new ColorDrawingEffect(
    D2D1::ColorF(
        D2D1::ColorF::Blue
        )
    );

greenDrawingEffect_ = new ColorDrawingEffect(
    D2D1::ColorF(
        D2D1::ColorF::Green
        )
    );

步骤 6:设置特定文本范围的绘制效果

使用 IDWriteTextLayou::SetDrawingEffect 方法和 DWRITE_TEXT_RANGE 结构设置特定文本范围的绘制效果。

// Set the drawing effects.

// Red.
if (SUCCEEDED(hr))
{
    // Set the drawing effect for the specified range.
    DWRITE_TEXT_RANGE textRange = {0,
                                   14};
    if (SUCCEEDED(hr))
    {
        hr = pTextLayout_->SetDrawingEffect(redDrawingEffect_, textRange);
    }
}

// Blue.
if (SUCCEEDED(hr))
{
    // Set the drawing effect for the specified range.
    DWRITE_TEXT_RANGE textRange = {14,
                                   7};
    if (SUCCEEDED(hr))
    {
        hr = pTextLayout_->SetDrawingEffect(blueDrawingEffect_, textRange);
    }
}

// Green.
if (SUCCEEDED(hr))
{
    // Set the drawing effect for the specified range.
    DWRITE_TEXT_RANGE textRange = {21,
                                   8};
    if (SUCCEEDED(hr))
    {
        hr = pTextLayout_->SetDrawingEffect(greenDrawingEffect_, textRange);
    }
}

步骤 7:使用自定义呈现器绘制文本布局

必须调用 IDWriteTextLayout::D raw 方法,而不是 ID2D1RenderTarget::D rawTextID2D1RenderTarget::D rawTextLayout 方法。

// Draw the text layout using DirectWrite and the CustomTextRenderer class.
hr = pTextLayout_->Draw(
        NULL,
        pTextRenderer_,  // Custom text renderer.
        origin.x,
        origin.y
        );

步骤 8:清理

在 DemoApp 析构函数中,释放自定义文本呈现器。

SafeRelease(&pTextRenderer_);

之后,添加代码以释放客户端绘图效果类。

SafeRelease(&redDrawingEffect_);
SafeRelease(&blueDrawingEffect_);
SafeRelease(&greenDrawingEffect_);