Direct2D实现文字的描边和填充

一、实现思路
通过继承IDWriteTextRenderer定义自己的文本渲染类并重写DrawGlyphRun方法,最终将该类实例作为 IDWriteTextLayout::Draw的参数传入进行文本的绘制。

二、代码实现
1、自定义文本渲染类

class CustomTextRenderer : public IDWriteTextRenderer
{
protected:
	ULONG m_cRef;

	ID2D1Factory* m_pD2DFactory;
	ID2D1RenderTarget *m_pRenderTarget;
	ID2D1Brush *m_pTextBodyBrush;
	ID2D1SolidColorBrush *m_pTextOutlineBrush;
	float mStrokeWidth;

public:
	CustomTextRenderer(
		ID2D1Factory* pD2DFactory, ID2D1RenderTarget* pRenderTarget,
		ID2D1Brush *pTextBodyBrush, ID2D1SolidColorBrush *pTextOutlineBrush, 
		float strokeWidth = 1.0f);

	~CustomTextRenderer();

	STDMETHOD(DrawGlyphRun)(
		void                               *clientDrawingContext,
		FLOAT                              baselineOriginX,
		FLOAT                              baselineOriginY,
		DWRITE_MEASURING_MODE              measuringMode,
		DWRITE_GLYPH_RUN const             *glyphRun,
		DWRITE_GLYPH_RUN_DESCRIPTION const *glyphRunDescription,
		IUnknown                           *clientDrawingEffect
	);

	STDMETHOD(DrawUnderline)(
		void                   *clientDrawingContext,
		FLOAT                  baselineOriginX,
		FLOAT                  baselineOriginY,
		DWRITE_UNDERLINE const *underline,
		IUnknown               *clientDrawingEffect
	);

	STDMETHOD(DrawStrikethrough)(
		void                       *clientDrawingContext,
		FLOAT                      baselineOriginX,
		FLOAT                      baselineOriginY,
		DWRITE_STRIKETHROUGH const *strikethrough,
		IUnknown                   *clientDrawingEffect
	);

	STDMETHOD(DrawInlineObject)(
		void                *clientDrawingContext,
		FLOAT               originX,
		FLOAT               originY,
		IDWriteInlineObject *inlineObject,
		BOOL                isSideways,
		BOOL                isRightToLeft,
		IUnknown            *clientDrawingEffect
	)
	{
		return E_NOTIMPL;
	}

	STDMETHOD(IsPixelSnappingDisabled)(
		 void* clientDrawingContext,
		 BOOL* isDisabled
	)
	{
		*isDisabled = FALSE;
		return S_OK;
	}

	STDMETHOD(GetCurrentTransform)(
		void* clientDrawingContext,
		DWRITE_MATRIX* transform
		)
	{
		m_pRenderTarget->GetTransform(reinterpret_cast(transform));
		return S_OK;
	}

	STDMETHOD(GetPixelsPerDip)(
		void* clientDrawingContext,
		FLOAT* pixelsPerDip
		)
	{
		float x, yUnused;

		m_pRenderTarget->GetDpi(&x, &yUnused);
		*pixelsPerDip = x / 96;
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void FAR* FAR* ppvObj)
	{
		if (iid == IID_IUnknown /*|| iid == IID_IDWritePixelSnapping || iid == IID_IDWriteTextRenderer*/)
		{
			*ppvObj = this;
			AddRef();
			return NOERROR;
		}
		return E_NOINTERFACE;
	}

	ULONG STDMETHODCALLTYPE AddRef()
	{		
		return ++m_cRef;
	}

	ULONG STDMETHODCALLTYPE Release()
	{
		// Decrement the object's internal counter.
		if (0 == --m_cRef)
		{
			delete this;
		}
		return m_cRef;
	}
};

2、DrawGlyphRun方法实现

HRESULT CustomTextRenderer::DrawGlyphRun(
	void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY,
	DWRITE_MEASURING_MODE measuringMode, DWRITE_GLYPH_RUN const *glyphRun,
	DWRITE_GLYPH_RUN_DESCRIPTION const *glyphRunDescription, IUnknown *clientDrawingEffect)
{
	HRESULT hr = S_OK;

	ID2D1PathGeometry* pPathGeometry = nullptr;
	hr = m_pD2DFactory->CreatePathGeometry(&pPathGeometry);
	ID2D1GeometrySink* pSink = nullptr;		
	hr = pPathGeometry->Open(&pSink);

	hr = glyphRun->fontFace->GetGlyphRunOutline(
			glyphRun->fontEmSize,
			glyphRun->glyphIndices,
			glyphRun->glyphAdvances,
			glyphRun->glyphOffsets,
			glyphRun->glyphCount,
			glyphRun->isSideways,
			glyphRun->bidiLevel,
			pSink
		);
	hr = pSink->Close();

	// 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
	);
	ID2D1TransformedGeometry *pTransformedGeometry = nullptr;
	hr = m_pD2DFactory->CreateTransformedGeometry(pPathGeometry, &matrix, &pTransformedGeometry);

	// 绘制文字描边部分
	m_pRenderTarget->DrawGeometry(pTransformedGeometry, m_pTextOutlineBrush, mStrokeWidth);
	// 绘制文字填充部分
	m_pRenderTarget->FillGeometry(pTransformedGeometry, m_pTextBodyBrush);

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

	return hr;
}

3、使用自定义渲染对象绘制文本

m_pTextRenderer = new CustomTextRenderer(
m_pDirect2dFactory, m_pRenderTarget, 
m_pTextBodyBrush, m_pTextOutlineBrush, strokeWidth);

m_pRenderTarget->BeginDraw();
m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());		
m_pTextLayout->Draw(nullptr, m_pTextRenderer, 0.0f, 0.0f);
m_pRenderTarget->EndDraw();

三、绘制结果
1、只有描边部分
Direct2D实现文字的描边和填充_第1张图片
2、只有填充部分
Direct2D实现文字的描边和填充_第2张图片
3、描边和填充
Direct2D实现文字的描边和填充_第3张图片

四、总结
1、自定义文本渲染类的核心是重写IDWriteTextRenderer::DrawGlyphRun这个回调函数。
2、使用自定义文本渲染类绘制文字必须调用IDWriteTextLayout::Draw,而不是ID2D1RenderTarget::DrawText和ID2D1RenderTarget::DrawTextLayout。

PS:
本文代码是基于微软官方文档示例所实现,详情可参考:
how-to-implement-a-custom-text-renderer
direct2d-quickstart

你可能感兴趣的:(Direct2D)