MFC Support for DirectWrite – Outlined Text

In a previous article, I showed the basics of custom text rendering with DirectWrite (see MFC Support for DirectWrite – Part 7: A Step to Custom Rendering). So far so good but it just mimics the default text rendering. Let’s now modify the overridden IDWriteTextRenderer::DrawGlyphRun in order to draw outlined text.

Overridden IDWriteTextRenderer::DrawGlyphRun implementation

STDMETHODIMP CCustomTextRenderer::XCustomTextRenderer::DrawGlyphRun(
    void* pClientDrawingContext, 
    FLOAT fBaselineOriginX, 
    FLOAT fBaselineOriginY, 
    DWRITE_MEASURING_MODE measuringMode,
    const DWRITE_GLYPH_RUN* pGlyphRun, 
    const DWRITE_GLYPH_RUN_DESCRIPTION* pGlyphRunDescription, 
    IUnknown* pClientDrawingEffect)
{
    METHOD_PROLOGUE(CCustomTextRenderer, CustomTextRenderer);
    CRenderTarget* pRenderTarget = pThis->GetRenderTarget();
    ASSERT(pRenderTarget && pRenderTarget->IsValid());

    CD2DPointF point(fBaselineOriginX, fBaselineOriginY);
    CCustomEffect* pCustomEffect = static_cast<CCustomEffect*>(pClientDrawingContext);
    if (NULL == pCustomEffect)
    {
        return _DrawDefaultGlyphRun(pRenderTarget, point, pGlyphRun, measuringMode);
    }
    
    HRESULT hr = E_FAIL;
    switch (pCustomEffect->GetType())
    {
    case CustomEffectType::outlined_text:
        hr = _DrawOutlinedGlyphRun(pRenderTarget,
            dynamic_cast<COutlinedTextEffect*>(pCustomEffect),
            point,
            pGlyphRun,
            measuringMode);
    // ...
    // may add other types of custom effects here
    }

    return hr;
}

A method for drawing outlined text

HRESULT CCustomTextRenderer::XCustomTextRenderer::_DrawOutlinedGlyphRun(
    CRenderTarget* pRenderTarget,
    COutlinedTextEffect* pEffect, 
    const CD2DPointF& point,
    const DWRITE_GLYPH_RUN* pGlyphRun,
    DWRITE_MEASURING_MODE measuringMode)
{
    ASSERT(pRenderTarget && pRenderTarget->IsValid());
    ASSERT(pEffect && pEffect->IsValid());

    // get the Direct2D resources contained the COutlinedTextEffect object
    CD2DBrush* pOutlineBrush = pEffect->GetOutlineBrush();
    ASSERT(pOutlineBrush && pOutlineBrush->IsValid());

    CD2DBrush* pFillBrush = pEffect->GetFillBrush();
    ASSERT(pFillBrush && pFillBrush->IsValid());

    // get outline stroke width and RTL flag
    FLOAT fStrokeWidth = pEffect->GetStrokeWidth();
    BOOL bIsRightToLeft = pEffect->IsRightToLeft();

    // create path geometry
    CD2DPathGeometry pathGeometry = (pRenderTarget);
    HRESULT hr = pathGeometry.Create(pRenderTarget);
    ASSERT(SUCCEEDED(hr));
    if (FAILED(hr)) return hr;

    // create a geometry sink which will be called back 
    // to perform outline drawing operations
    CD2DGeometrySink geometrySink(pathGeometry);
    ASSERT(geometrySink.IsValid());
        
    // get IDWriteFontFace interface from DWRITE_GLYPH_RUN structure
    IDWriteFontFace* pFontFace = pGlyphRun->fontFace;

    // call IDWriteFontFace::GetGlyphRunOutline passing data 
    // contained in DWRITE_GLYPH_RUN and the geometry sink
    hr = pFontFace->GetGlyphRunOutline(pGlyphRun->fontEmSize,
        pGlyphRun->glyphIndices, pGlyphRun->glyphAdvances, 
        pGlyphRun->glyphOffsets, pGlyphRun->glyphCount, 
        pGlyphRun->isSideways, bIsRightToLeft, geometrySink);

    if (FAILED(hr)) return hr;

    geometrySink.Close();

    // keep in mind the render target transform matrix
    D2D1_MATRIX_3X2_F oldMatrix;
    pRenderTarget->GetTransform(&oldMatrix);
    // translate the render target according to baseline coordinates 
    pRenderTarget->SetTransform(D2D1::Matrix3x2F::Translation(point.x, point.y));

    // draw the outline and fill the geometry path
    pRenderTarget->DrawGeometry(&pathGeometry, pOutlineBrush, fStrokeWidth);
    pRenderTarget->FillGeometry(&pathGeometry, pFillBrush);

    // restore the initial render target transform
    pRenderTarget->SetTransform(oldMatrix);

    return S_OK; 
    // well, maybe this method needs a little bit of refactoring. :)
}

Basically, the steps where the following:

  1. if pClientDrawingContext parameter is null then perform the default drawing;
  2. otherwhise, call _DrawOutlinedGlyphRun pasing an object that contains the brushes for text outline and fill (COutlinedTextEffect);
  3. create a path geometry (CD2DPathGeometry);
  4. instantiate a geometry sink that is used to populate the path geometry (CD2DGeometrySink);
  5. get IDWriteFontFace interface from DWRITE_GLYPH_RUN structure;
  6. call IDWriteFontFace::GetGlyphRunOutline, passing a bunch of parameters, most of them contained in DWRITE_GLYPH_RUN structure plus our geometry sink;
  7. close the geometry sink;
  8. translate the render target according to the glyph run baseline origin (baselineOriginX and baselineOriginY parameters of DrawGlyphRun);
  9. finally, call CRenderTarget::DrawGeometry and CRenderTarget::FillGeometry and…
  10. …do not forget to restore the initial render target transform.

You can find more details in the demo application attached here. Also you can have a look in the related articles mentioned at the bottom of this page.

Demo project

The demo project draws outlined text in order to make it readable over any background image.
Download Outlined Text Demo.zip (1468 downloads)

Outlined Text demo project
Outlined Text demo project

Resources and related articles

Leave a Comment