如何将客户端绘制效果添加到文本布局

提供有关将客户端绘制效果添加到使用 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检索字形运行大纲。 然后,使用 Direct2D ID2D1Factory::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,它本身依赖于设备。

// 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_);