使用自定义文本呈现器进行呈现

派生自 IDWriteTextRenderer 的自定义文本呈现器可以绘制DirectWrite文本布局。 需要自定义呈现器才能利用DirectWrite的某些高级功能,例如呈现到位图或 GDI 图面、内联对象和客户端绘图效果。 本教程介绍 IDWriteTextRenderer 的方法,并提供使用 Direct2D 呈现具有位图填充的文本的示例实现。

本教程包含以下部分:

除了 IDWriteTextRenderer 引用页及下方列出的方法外,自定义文本呈现器还必须实现从 IUnknown 继承的方法。

有关自定义文本呈现器的完整源代码,请参阅DirectWrite Hello World示例的 CustomTextRenderer.cpp 和 CustomTextRenderer.h 文件。

构造函数

自定义文本呈现器需要构造函数。 此示例使用实心画笔和位图 Direct2D 画笔来呈现文本。

因此,构造函数采用下表中的参数以及说明。

参数 说明
pD2DFactory 指向 ID2D1Factory 对象的指针,该对象将用于创建所需的任何 Direct2D 资源。
Prt 指向文本将呈现到的 ID2D1HwndRenderTarget 对象的指针。
pOutlineBrush 指向 ID2D1SolidColorBrush 的指针,该 ID2D1SolidColorBrush 将用于绘制文本的轮廓
pFillBrush 指向 ID2D1BitmapBrush 的指针,该 ID2D1BitmapBrush 将用于填充文本。

 

这些将由构造函数存储,如以下代码所示。

CustomTextRenderer::CustomTextRenderer(
    ID2D1Factory* pD2DFactory, 
    ID2D1HwndRenderTarget* pRT, 
    ID2D1SolidColorBrush* pOutlineBrush, 
    ID2D1BitmapBrush* pFillBrush
    )
:
cRefCount_(0), 
pD2DFactory_(pD2DFactory), 
pRT_(pRT), 
pOutlineBrush_(pOutlineBrush), 
pFillBrush_(pFillBrush)
{
    pD2DFactory_->AddRef();
    pRT_->AddRef();
    pOutlineBrush_->AddRef();
    pFillBrush_->AddRef();
}

DrawGlyphRun ()

DrawGlyphRun 方法是文本呈现器main回调方法。 除了基线原点和测量模式等信息外,还会传递一系列要呈现的字形。 它还传递要应用于字形运行的客户端绘图效果对象。 有关详细信息,请参阅 如何将客户端绘图效果添加到文本布局 主题。

此文本呈现器实现通过将字形转换为 Direct2D 几何图形,然后绘制和填充几何图形来呈现字形运行。 这包括以下步骤。

  1. 创建 ID2D1PathGeometry 对象,然后使用 ID2D1PathGeometry::Open 方法检索 ID2D1GeometrySink 对象。

    // 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
            );
    }
    
  2. 传递给 DrawGlyphRunDWRITE_GLYPH_RUN包含一个名为 fontFaceIDWriteFontFace 对象,该对象代表整个字形运行的字体。 使用 IDWriteFontFace:: GetGlyphRunOutline 方法将字形的轮廓放入 geometry 接收器中,如以下代码所示。

    // 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
            );
    }
    
  3. 填充几何接收器后,将其关闭。

    // Close the geometry sink
    if (SUCCEEDED(hr))
    {
        hr = pSink->Close();
    }
    
  4. 必须转换字形运行的原点,以便从正确的基线原点呈现,如以下代码所示。

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

    baselineOriginXbaselineOriginY 作为参数传递给 DrawGlyphRun 回调方法。

  5. 通过使用 ID2D1Factory::CreateTransformedGeometry 方法并传递路径几何图形和转换矩阵来创建转换后的几何图形。

    // Create the transformed geometry
    ID2D1TransformedGeometry* pTransformedGeometry = NULL;
    if (SUCCEEDED(hr))
    {
        hr = pD2DFactory_->CreateTransformedGeometry(
            pPathGeometry,
            &matrix,
            &pTransformedGeometry
            );
    }
    
  6. 最后,绘制转换后的几何图形轮廓,并使用 ID2D1RenderTarget::D rawGeometryID2D1RenderTarget::FillGeometry 方法和存储为成员变量的 Direct2D 画笔填充该几何图形。

        // Draw the outline of the glyph run
        pRT_->DrawGeometry(
            pTransformedGeometry,
            pOutlineBrush_
            );
    
        // Fill in the glyph run
        pRT_->FillGeometry(
            pTransformedGeometry,
            pFillBrush_
            );
    
  7. 完成绘图后,不要忘记清理在此方法中创建的对象。

    SafeRelease(&pPathGeometry);
    SafeRelease(&pSink);
    SafeRelease(&pTransformedGeometry);
    

DrawUnderline () 和 DrawStrikethrough ()

IDWriteTextRenderer 还具有用于绘制下划线和删除线的回调。 此示例为下划线或删除线绘制一个简单矩形,但可以绘制其他形状。

使用 Direct2D 绘制下划线包括以下步骤。

  1. 首先,创建下划线大小和形状的 D2D1_RECT_F 结构。 传递给 DrawUnderline 回调方法的DWRITE_UNDERLINE结构提供下划线的偏移量、宽度和粗细。

    D2D1_RECT_F rect = D2D1::RectF(
        0,
        underline->offset,
        underline->width,
        underline->offset + underline->thickness
        );
    
  2. 接下来,使用 ID2D1Factory::CreateRectangleGeometry 方法和初始化的 D2D1_RECT_F 结构创建 ID2D1RectangleGeometry 对象。

    ID2D1RectangleGeometry* pRectangleGeometry = NULL;
    hr = pD2DFactory_->CreateRectangleGeometry(
            &rect, 
            &pRectangleGeometry
            );
    
  3. 与字形运行一样,必须使用 CreateTransformedGeometry 方法根据基线原点值转换下划线几何图形的原点。

    // Initialize a matrix to translate the origin of the underline
    D2D1::Matrix3x2F const matrix = D2D1::Matrix3x2F(
        1.0f, 0.0f,
        0.0f, 1.0f,
        baselineOriginX, baselineOriginY
        );
    
    ID2D1TransformedGeometry* pTransformedGeometry = NULL;
    if (SUCCEEDED(hr))
    {
        hr = pD2DFactory_->CreateTransformedGeometry(
            pRectangleGeometry,
            &matrix,
            &pTransformedGeometry
            );
    }
    
  4. 最后,绘制转换后的几何图形轮廓,并使用 ID2D1RenderTarget::D rawGeometryID2D1RenderTarget::FillGeometry 方法和存储为成员变量的 Direct2D 画笔填充该几何图形。

        // Draw the outline of the glyph run
        pRT_->DrawGeometry(
            pTransformedGeometry,
            pOutlineBrush_
            );
    
        // Fill in the glyph run
        pRT_->FillGeometry(
            pTransformedGeometry,
            pFillBrush_
            );
    
  5. 完成绘图后,不要忘记清理在此方法中创建的对象。

    SafeRelease(&pRectangleGeometry);
    SafeRelease(&pTransformedGeometry);
    

绘制删除线的过程是相同的。 但是,删除线将具有不同的偏移量,并且可能具有不同的宽度和粗细。

像素对齐、每个 DIP 的像素数和转换

IsPixelSnappingDisabled ()

调用此方法可确定是否禁用像素贴靠。 建议的默认值为 FALSE,这是此示例的输出。

*isDisabled = FALSE;

GetCurrentTransform ()

此示例呈现到 Direct2D 呈现目标,因此使用 ID2D1RenderTarget::GetTransform 从呈现器目标转发转换。

//forward the render target's transform
pRT_->GetTransform(reinterpret_cast<D2D1_MATRIX_3X2_F*>(transform));

GetPixelsPerDip ()

调用此方法以获取每个设备无关像素 (DIP) 的像素数。

float x, yUnused;

pRT_->GetDpi(&x, &yUnused);
*pixelsPerDip = x / 96;

DrawInlineObject ()

自定义文本呈现器还具有用于绘制内联对象的回调。 在此示例中, DrawInlineObject 返回E_NOTIMPL。 本教程不介绍如何绘制内联对象。 有关详细信息,请参阅 如何将内联对象添加到文本布局 主题。

析构函数

释放自定义文本呈现器类使用的任何指针非常重要。

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

使用自定义文本呈现器

使用 IDWriteTextLayout::D raw 方法使用自定义呈现器进行呈现,该方法将派生自 IDWriteTextRenderer 的 回调接口作为参数,如以下代码所示。

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

IDWriteTextLayout::D raw 方法调用你提供的自定义呈现器回调的方法。 上述 DrawGlyphRunDrawUnderlineDrawInlineObjectDrawStrikethrough 方法执行绘图函数。