Render Using a Custom Text Renderer

A DirectWrite text layout can be drawn by a custom text renderer derived from IDWriteTextRenderer. A custom renderer is required to take advantage of some advanced features of DirectWrite, such as rendering to a bitmap or GDI surface, inline objects, and client drawing effects. This tutorial describes the methods of IDWriteTextRenderer, and provides an example implementation that uses Direct2D to render text with a bitmap fill.

This tutorial contains the following parts:

Your custom text renderer must implement the methods inherited from IUnknown in addition to the methods listed on the IDWriteTextRenderer reference page and below.

For the full source code for the custom text renderer, see the CustomTextRenderer.cpp and CustomTextRenderer.h files of the DirectWrite Hello World Sample.

The Constructor

Your custom text renderer will need a constructor. This example uses both solid and bitmap Direct2D brushes to render the text.

Because of this, the constructor takes the parameters found in the table below with descriptions.

Parameter Description
pD2DFactory A pointer to an ID2D1Factory object that will be used to create any Direct2D resources that are needed.
pRT A pointer to the ID2D1HwndRenderTarget object that the text will be rendered to.
pOutlineBrush A pointer to the ID2D1SolidColorBrush that will be use to draw outline of the text
pFillBrush A pointer to the ID2D1BitmapBrush that will be used to fill the text.

 

These will be stored by the constructor as shown in the following code.

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()

The DrawGlyphRun method is the main callback method of the text renderer. It is passed a run of glyphs to be rendered in addition to information such as the baseline origin and measuring mode. It also passes a client drawing effect object to be applied to the glyph run. For more information, see the How to Add Client Drawing Effects to a Text Layout topic.

This text renderer implementation renders glyph runs by converting them to Direct2D geometries and then drawing and filling the geometries. This consists of the following steps.

  1. Create an ID2D1PathGeometry object, and then retrieve the ID2D1GeometrySink object by using the ID2D1PathGeometry::Open method.

    // 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. The DWRITE_GLYPH_RUN that is passed to DrawGlyphRun contains a IDWriteFontFace object, named fontFace, that represents the font face for the whole glyph run. Put the outline of the glyph run into the geometry sink by using the IDWriteFontFace:: GetGlyphRunOutline method, as shown in the following code.

    // 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. After filling the geometry sink, close it.

    // Close the geometry sink
    if (SUCCEEDED(hr))
    {
        hr = pSink->Close();
    }
    
  4. The origin of the glyph run must be translated so that it is rendered from the correct baseline origin, as shown in the following code.

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

    The baselineOriginX and baselineOriginY are passed as parameters to the DrawGlyphRun callback method.

  5. Create the transformed geometry by using the ID2D1Factory::CreateTransformedGeometry method and passing the path geometry and the translation matrix.

    // Create the transformed geometry
    ID2D1TransformedGeometry* pTransformedGeometry = NULL;
    if (SUCCEEDED(hr))
    {
        hr = pD2DFactory_->CreateTransformedGeometry(
            pPathGeometry,
            &matrix,
            &pTransformedGeometry
            );
    }
    
  6. Finally, draw the outline of the transformed geometry, and fill it by using the ID2D1RenderTarget::DrawGeometry and ID2D1RenderTarget::FillGeometry methods and the Direct2D brushes stored as member variables.

        // Draw the outline of the glyph run
        pRT_->DrawGeometry(
            pTransformedGeometry,
            pOutlineBrush_
            );
    
        // Fill in the glyph run
        pRT_->FillGeometry(
            pTransformedGeometry,
            pFillBrush_
            );
    
  7. Now that you are finished drawing, do not forget to clean up the objects that were created in this method.

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

DrawUnderline() and DrawStrikethrough()

IDWriteTextRenderer also has callbacks for drawing the underline and strikethrough. This example draws a simple rectangle for an underline or strikethrough, but other shapes can be drawn.

Drawing an underline by using Direct2D consists of the following steps.

  1. First, create a D2D1_RECT_F structure of the size and shape of the underline. The DWRITE_UNDERLINE structure that is passed to the DrawUnderline callback method provides the offset, width, and thickness of the underline.

    D2D1_RECT_F rect = D2D1::RectF(
        0,
        underline->offset,
        underline->width,
        underline->offset + underline->thickness
        );
    
  2. Next, create an ID2D1RectangleGeometry object by using the ID2D1Factory::CreateRectangleGeometry method and the initialized D2D1_RECT_F structure.

    ID2D1RectangleGeometry* pRectangleGeometry = NULL;
    hr = pD2DFactory_->CreateRectangleGeometry(
            &rect, 
            &pRectangleGeometry
            );
    
  3. As with the glyph run, the origin of the underline geometry must be translated, based on the baseline origin values, by using the CreateTransformedGeometry method.

    // 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. Finally, draw the outline of the transformed geometry, and fill it by using the ID2D1RenderTarget::DrawGeometry and ID2D1RenderTarget::FillGeometry methods and the Direct2D brushes stored as member variables.

        // Draw the outline of the glyph run
        pRT_->DrawGeometry(
            pTransformedGeometry,
            pOutlineBrush_
            );
    
        // Fill in the glyph run
        pRT_->FillGeometry(
            pTransformedGeometry,
            pFillBrush_
            );
    
  5. Now that you are finished drawing, do not forget to clean up the objects that were created in this method.

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

The process for drawing a strikethrough is the same. However, the strikethrough will have a different offset, and likely a different width and thickness.

Pixel Snapping, Pixels per DIP, and Transform

IsPixelSnappingDisabled()

This method is called to determine whether pixel snapping is disabled. The recommended default is FALSE, and that is the output of this example.

*isDisabled = FALSE;

GetCurrentTransform()

This example renders to a Direct2D render target, so forward the transform from the render target using ID2D1RenderTarget::GetTransform.

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

GetPixelsPerDip()

This method is called to get the number of pixels per Device Independent Pixel (DIP).

float x, yUnused;

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

DrawInlineObject()

A custom text renderer also has a callback for drawing inline objects. In this example, DrawInlineObject returns E_NOTIMPL. An explanation of how to draw inline objects is beyond the scope of this tutorial. For more information, see the How to Add Inline Objects to a Text Layout topic.

The Destructor

It is important to release any pointers that were used by the custom text renderer class.

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

Using the Custom Text Renderer

You render with the custom renderer by using the IDWriteTextLayout::Draw method, which takes a callback interface derived from IDWriteTextRenderer as an argument, as shown in the following code.

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

The IDWriteTextLayout::Draw method calls the methods of the custom renderer callback you provide. The DrawGlyphRun, DrawUnderline, DrawInlineObject, and DrawStrikethrough methods described above perform the drawing functions.