提供一个简短教程,介绍如何将客户端绘图效果添加到 DirectWrite 应用程序,该应用程序使用 IDWriteTextLayout 接口和自定义文本呈现器显示文本。
本教程的最终产品是一个应用程序,它显示文本范围,每个文本都具有不同的颜色绘图效果,如以下屏幕截图所示。
注意
本教程旨在简化如何创建自定义客户端绘图效果的示例,而不是用于绘制颜色文本的简单方法的示例。 有关详细信息,请参阅 IDWriteTextLayout::SetDrawingEffect 参考页。
本教程包含以下部分:
- 步骤 1:创建文本布局
- 步骤 2:实现自定义绘图效果类
- 步骤 3:实现自定义文本呈现器类
- 步骤 4:创建文本呈现器
- 步骤 5:实例化颜色绘制效果对象
- 步骤 6:为特定文本范围设置绘图效果
- 步骤 7:使用自定义呈现器绘制文本布局
- 步骤 8:清理
步骤 1:创建文本布局
要开始,需要一个包含 IDWriteTextLayout 对象的应用程序。 如果您已经有一个应用程序可以通过文本布局来显示文本,或者正在使用自定义绘图效果示例代码,请跳到步骤 2。
若要添加文本布局,必须执行以下操作:
将指向 IDWriteTextLayout 接口的指针声明为类的成员。
IDWriteTextLayout* pTextLayout_;
在 CreateDeviceIndependentResources 方法的末尾,通过调用 CreateTextLayout 方法创建 IDWriteTextLayout 接口对象。
// Create a text layout using the text format. if (SUCCEEDED(hr)) { RECT rect; GetClientRect(hwnd_, &rect); float width = rect.right / dpiScaleX_; float height = rect.bottom / dpiScaleY_; hr = pDWriteFactory_->CreateTextLayout( wszText_, // The string to be laid out and formatted. cTextLength_, // The length of the string. pTextFormat_, // The text format to apply to the string (contains font information, etc). width, // The width of the layout box. height, // The height of the layout box. &pTextLayout_ // The IDWriteTextLayout interface pointer. ); }
最后,请记住在析构函数中释放文本布局。
SafeRelease(&pTextLayout_);
步骤 2:实现自定义绘图效果类
除了从 IUnknown 继承的方法之外,自定义客户端绘图效果接口对它必须实现的方法没有要求。 在本例中,ColorDrawingEffect 类只保留一个 D2D1_COLOR_F 值,并声明用于获取和设置此值的方法,以及一个最初可以设置颜色的构造函数。
将客户端绘图效果应用于 IDWriteTextLayout 对象中的文本范围后,绘图效果将传递给要呈现的任何字形运行 IDWriteTextRenderer::DrawGlyphRun 方法。 然后,绘图效果的方法可供文本呈现器使用。
客户端的绘图效果可以根据你的需求变得非常复杂,不仅能携带比此示例更多的信息,还能提供更改字形的方法、创建用于绘图的对象等功能。
步骤 3:实现自定义文本呈现器类
若要利用客户端绘图效果,必须实现自定义文本呈现器。 此文本呈现器将应用 IDWriteTextLayout::Draw 方法传递给它的绘图效果应用到正在绘制的绘制效果中。
构造函数
自定义文本呈现器构造函数存储 ID2D1Factory 对象,该对象将用于创建 Direct2D 对象,以及要绘制文本的 Direct2D 呈现目标。
CustomTextRenderer::CustomTextRenderer(
ID2D1Factory* pD2DFactory,
ID2D1HwndRenderTarget* pRT
)
:
cRefCount_(0),
pD2DFactory_(pD2DFactory),
pRT_(pRT)
{
pD2DFactory_->AddRef();
pRT_->AddRef();
}
DrawGlyphRun 方法
字形运行是一组具有相同格式的字形,包括客户端绘图效果。 DrawGlyphRun 方法负责指定字形运行的文本呈现。
首先,创建 ID2D1PathGeometry 和 ID2D1GeometrySink,然后使用 IDWriteFontFace::GetGlyphRunOutline 检索字形运行大纲。 然后使用 Direct2DID2D1Factory::CreateTransformedGeometry 方法转换几何图形的原点,如以下代码所示。
HRESULT hr = S_OK;
// Create the path geometry.
ID2D1PathGeometry* pPathGeometry = NULL;
hr = pD2DFactory_->CreatePathGeometry(
&pPathGeometry
);
// Write to the path geometry using the geometry sink.
ID2D1GeometrySink* pSink = NULL;
if (SUCCEEDED(hr))
{
hr = pPathGeometry->Open(
&pSink
);
}
// Get the glyph run outline geometries back from DirectWrite and place them within the
// geometry sink.
if (SUCCEEDED(hr))
{
hr = glyphRun->fontFace->GetGlyphRunOutline(
glyphRun->fontEmSize,
glyphRun->glyphIndices,
glyphRun->glyphAdvances,
glyphRun->glyphOffsets,
glyphRun->glyphCount,
glyphRun->isSideways,
glyphRun->bidiLevel%2,
pSink
);
}
// Close the geometry sink
if (SUCCEEDED(hr))
{
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
);
// Create the transformed geometry
ID2D1TransformedGeometry* pTransformedGeometry = NULL;
if (SUCCEEDED(hr))
{
hr = pD2DFactory_->CreateTransformedGeometry(
pPathGeometry,
&matrix,
&pTransformedGeometry
);
}
接下来,声明 Direct2D 纯色画笔对象。
ID2D1SolidColorBrush* pBrush = NULL;
如果 clientDrawingEffect 参数不为 NULL,请查询 ColorDrawingEffect 接口的对象。 这将起作用,因为你将把此类设置为文本布局对象的文本范围上的客户端绘图效果。
一旦获得指向 ColorDrawingEffect 接口的指针,就可以使用 GetColor 方法检索其存储的 D2D1_COLOR_F 值。 然后,使用 D2D1_COLOR_F 在该颜色中创建 ID2D1SolidColorBrush。
如果 clientDrawingEffect 参数为 NULL,那么只需创建黑色 ID2D1SolidColorBrush。
// If there is a drawing effect create a color brush using it, otherwise create a black brush.
if (clientDrawingEffect != NULL)
{
// Go from IUnknown to ColorDrawingEffect.
ColorDrawingEffect *colorDrawingEffect;
clientDrawingEffect->QueryInterface(__uuidof(ColorDrawingEffect), reinterpret_cast<void**>(&colorDrawingEffect));
// Get the color from the ColorDrawingEffect object.
D2D1_COLOR_F color;
colorDrawingEffect->GetColor(&color);
// Create the brush using the color specified by our ColorDrawingEffect object.
if (SUCCEEDED(hr))
{
hr = pRT_->CreateSolidColorBrush(
color,
&pBrush);
}
SafeRelease(&colorDrawingEffect);
}
else
{
// Create a black brush.
if (SUCCEEDED(hr))
{
hr = pRT_->CreateSolidColorBrush(
D2D1::ColorF(
D2D1::ColorF::Black
),
&pBrush);
}
}
最后,绘制轮廓几何图形,并使用刚刚创建的纯色画笔填充它。
if (SUCCEEDED(hr))
{
// Draw the outline of the glyph run
pRT_->DrawGeometry(
pTransformedGeometry,
pBrush
);
// Fill in the glyph run
pRT_->FillGeometry(
pTransformedGeometry,
pBrush
);
}
破坏者
别忘了释放 Direct2D 工厂并在析构函数中呈现目标。
CustomTextRenderer::~CustomTextRenderer()
{
SafeRelease(&pD2DFactory_);
SafeRelease(&pRT_);
}
步骤 4:创建文本呈现器
在 CreateDeviceDependent 资源中,创建自定义文本呈现器对象。 它依赖于设备,因为它使用 ID2D1RenderTarget,后者本身依赖于设备。
// Create the text renderer
pTextRenderer_ = new CustomTextRenderer(
pD2DFactory_,
pRT_
);
步骤 5:实例化颜色绘制效果对象
将 ColorDrawingEffect 对象实例化为红色、绿色和蓝色。
// Instantiate some custom color drawing effects.
redDrawingEffect_ = new ColorDrawingEffect(
D2D1::ColorF(
D2D1::ColorF::Red
)
);
blueDrawingEffect_ = new ColorDrawingEffect(
D2D1::ColorF(
D2D1::ColorF::Blue
)
);
greenDrawingEffect_ = new ColorDrawingEffect(
D2D1::ColorF(
D2D1::ColorF::Green
)
);
步骤 6:设置特定文本范围的绘图效果
使用 IDWriteTextLayou::SetDrawingEffect 方法和 DWRITE_TEXT_RANGE 结构设置特定文本范围的绘图效果。
// Set the drawing effects.
// Red.
if (SUCCEEDED(hr))
{
// Set the drawing effect for the specified range.
DWRITE_TEXT_RANGE textRange = {0,
14};
if (SUCCEEDED(hr))
{
hr = pTextLayout_->SetDrawingEffect(redDrawingEffect_, textRange);
}
}
// Blue.
if (SUCCEEDED(hr))
{
// Set the drawing effect for the specified range.
DWRITE_TEXT_RANGE textRange = {14,
7};
if (SUCCEEDED(hr))
{
hr = pTextLayout_->SetDrawingEffect(blueDrawingEffect_, textRange);
}
}
// Green.
if (SUCCEEDED(hr))
{
// Set the drawing effect for the specified range.
DWRITE_TEXT_RANGE textRange = {21,
8};
if (SUCCEEDED(hr))
{
hr = pTextLayout_->SetDrawingEffect(greenDrawingEffect_, textRange);
}
}
步骤 7:使用自定义呈现器绘制文本布局
必须调用 IDWriteTextLayout::Draw 方法,而不是 ID2D1RenderTarget::DrawText 或 ID2D1RenderTarget::DrawTextLayout 方法。
// Draw the text layout using DirectWrite and the CustomTextRenderer class.
hr = pTextLayout_->Draw(
NULL,
pTextRenderer_, // Custom text renderer.
origin.x,
origin.y
);
步骤 8:清理
在 DemoApp 析构函数中,释放自定义文本呈现器。
SafeRelease(&pTextRenderer_);
之后,添加代码以释放客户端绘图效果类。
SafeRelease(&redDrawingEffect_);
SafeRelease(&blueDrawingEffect_);
SafeRelease(&greenDrawingEffect_);