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:
- if pClientDrawingContext parameter is null then perform the default drawing;
- otherwhise, call _DrawOutlinedGlyphRun pasing an object that contains the brushes for text outline and fill (COutlinedTextEffect);
- create a path geometry (CD2DPathGeometry);
- instantiate a geometry sink that is used to populate the path geometry (CD2DGeometrySink);
- get IDWriteFontFace interface from DWRITE_GLYPH_RUN structure;
- call IDWriteFontFace::GetGlyphRunOutline, passing a bunch of parameters, most of them contained in DWRITE_GLYPH_RUN structure plus our geometry sink;
- close the geometry sink;
- translate the render target according to the glyph run baseline origin (baselineOriginX and baselineOriginY parameters of DrawGlyphRun);
- finally, call CRenderTarget::DrawGeometry and CRenderTarget::FillGeometry and…
- …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 (382)
Resources and related articles
- Codexpert: MFC Support for DirectWrite – Part 7: A Step to Custom Rendering
- Petzold Book Blog: Character Formatting Extensions with DirectWrite
- MSDN Magazine: DirectX Factor – Who’s Afraid of Glyph Runs?
- Codeproject: Outline Text With DirectWrite
- MSDN: IDWriteTextRenderer::DrawGlyphRun method
- MSDN: CRenderTarget::DrawGeometry
- MSDN: CRenderTarget::FillGeometry
- MSDN: CD2DPathGeometry Class
- MSDN: CD2DGeometrySink Class
- MSDN: Render Using a Custom Text Renderer
