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 (26)
Notes
- You can find an excellent related article on Petzold Book Blog; see the link below.
Resources and related articles
- Petzold Book Blog: Character Formatting Extensions with DirectWrite
- MSDN: IDWriteTextLayout::Draw method
- MSDN: IDWriteTextRenderer interface
- MSDN: IDWritePixelSnapping interface