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

提供一个简短教程,介绍如何将客户端绘图效果添加到DirectWrite应用程序,该应用程序使用 IDWriteTextLayout 接口和自定义文本呈现器显示文本。

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

screen shot of

注意

本教程旨在成为有关如何创建自定义客户端绘图效果的简化示例,而不是用于绘制颜色文本的简单方法的示例。 有关详细信息,请参阅 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_);