MFC Support for DirectWrite – A Step to Custom Rendering

The previous articles from this series show how to format the text layout using built-in DirectWrite methods. However, as said earlier, we can do more custom formatting (e.g. draw double/triple underline/strikethrough, highlight text and so on). How can be done? First, let’s note that ID2D1RenderTarget::DrawTextLayout internally calls IDWriteTextLayout::Draw which has the following prototype:

HRESULT Draw(
   void*                clientDrawingContext,
   IDWriteTextRenderer* renderer,
   FLOAT                originX,
   FLOAT                originY)

We can write our own implementation of IDWriteTextRenderer interface providing custom text rendering then directly call IDWriteTextLayout::Draw instead of CRenderTarget::DrawTextLayout.

Code samples

class CCustomTextRenderer : public CCmdTarget
{
	DECLARE_DYNAMIC(CCustomTextRenderer)
public:
	CCustomTextRenderer() = default;
	virtual ~CCustomTextRenderer() = default;
    IDWriteTextRenderer* Get();
public:
    DECLARE_INTERFACE_MAP()
    BEGIN_INTERFACE_PART(CustomTextRenderer, IDWriteTextRenderer)
        // override IDWriteTextRenderer methods
        STDMETHOD(DrawGlyphRun)(void*, FLOAT, FLOAT, DWRITE_MEASURING_MODE, const DWRITE_GLYPH_RUN*, 
                                const DWRITE_GLYPH_RUN_DESCRIPTION*, IUnknown*);
        STDMETHOD(DrawInlineObject)(void*, FLOAT, FLOAT, IDWriteInlineObject*, BOOL, BOOL, IUnknown*);
        STDMETHOD(DrawStrikethrough)(void*, FLOAT, FLOAT, const DWRITE_STRIKETHROUGH*, IUnknown*);
        STDMETHOD(DrawUnderline)(void*, FLOAT, FLOAT, const DWRITE_UNDERLINE*, IUnknown*);
        // override IDWritePixelSnapping methods
        STDMETHOD(GetCurrentTransform)(void*, DWRITE_MATRIX*);
        STDMETHOD(GetPixelsPerDip)(void*, FLOAT*);
        STDMETHOD(IsPixelSnappingDisabled)(void*, BOOL*);
        // implementation helpers
        void _FillRectangle(void*, IUnknown*, FLOAT, FLOAT, FLOAT, FLOAT, 
                            DWRITE_READING_DIRECTION, DWRITE_FLOW_DIRECTION);
    END_INTERFACE_PART(CustomTextRenderer)
};
BEGIN_INTERFACE_MAP(CCustomTextRenderer, CCmdTarget)
    INTERFACE_PART(CCustomTextRenderer, __uuidof(IDWriteTextRenderer), CustomTextRenderer)
END_INTERFACE_MAP()

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)
{
    // NOTE: This does the same as the default implementation.
    // In a future version will be modified in order to perform some custom rendering.

    CDrawingContext* pDrawingContext = static_cast<CDrawingContext*>(pClientDrawingContext);
    ASSERT_VALID(pDrawingContext);

    ID2D1Brush* pBrush = pDrawingContext->GetDefaultBrush()->Get();
    if(NULL != pClientDrawingEffect)
    {
        pBrush = static_cast<ID2D1Brush*>(pClientDrawingEffect);
    }

    CRenderTarget* pRenderTarget = pDrawingContext->GetRenderTarget();
    ASSERT_VALID(pRenderTarget);
    pRenderTarget->GetRenderTarget()->DrawGlyphRun(CD2DPointF(fBaselineOriginX, fBaselineOriginY), 
        pGlyphRun, pBrush, measuringMode);

    return S_OK;
}
// ...
// Please, find the other methods implementation in the attached demo project!
// ...
void CDirectWriteStaticCtrl::_DrawTextLayout(CHwndRenderTarget* pRenderTarget)
{
    CD2DTextFormat textFormat(pRenderTarget,
        m_strFontFamilyName, m_fFontSize, m_eFontWeight, m_eFontStyle, m_eFontStretch);

    textFormat.Get()->SetTextAlignment(m_eTextAlignment);
    textFormat.Get()->SetWordWrapping(m_eWordWrapping);
    textFormat.Get()->SetParagraphAlignment(m_eParagraphAlignment);

    CD2DSizeF sizeTarget = pRenderTarget->GetSize();
    CD2DSizeF sizeDraw(sizeTarget.width - 2 * m_fMargin, sizeTarget.height - 2 * m_fMargin);
    CD2DTextLayout textLayout(pRenderTarget, m_strText, textFormat, sizeDraw);

    POSITION pos = m_listTextRangeFormat.GetHeadPosition();
    while (NULL != pos)
        m_listTextRangeFormat.GetNext(pos)->Apply(textLayout);

    CD2DSolidColorBrush* pDefaultBrush = new CD2DSolidColorBrush(pRenderTarget, m_crTextColor);
    pDefaultBrush->Create(pRenderTarget);
    CDrawingContext* pDrawingContext = new CDrawingContext(pRenderTarget, pDefaultBrush);

    // call IDWriteTextLayout::Draw passing custom IDWriteTextRenderer 
    textLayout.Get()->Draw(pDrawingContext, m_textRenderer.Get(), m_fMargin, m_fMargin);
}

Demo project

So far, it contains an implementation of IDWriteTextRenderer which does the same as the default one. In a further article I will update it in order to perform custom rendering.
Download: MFC Support for DirectWrite Demo (Part 7).zip (1553 downloads)

Notes

  • You can find an excellent related article on Petzold Book Blog; see the link below.

Resources and related articles

Leave a Comment