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<D2D1_MATRIX_3X2_F*>(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、只有描邊部分
圖1
2、只有填充部分
圖2
3、描邊和填充
圖3

四、總結
1、自定義文本渲染類的核心是重寫IDWriteTextRenderer::DrawGlyphRun這個回調函數。
2、使用自定義文本渲染類繪製文字必須調用IDWriteTextLayout::Draw,而不是ID2D1RenderTarget::DrawText和ID2D1RenderTarget::DrawTextLayout。

PS:
本文代碼是基於微軟官方文檔示例所實現,詳情可參考:
how-to-implement-a-custom-text-renderer
direct2d-quickstart

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章