Добавление эффектов рисования клиента в текстовый макет
Содержит краткое руководство по добавлению эффектов рисования клиентской стороны в приложение DirectWrite, которая отображает текст с помощью интерфейса IDWriteTextLayout и кастомного отрисовщика текста.
Конечный продукт этого руководства — это приложение, которое отображает текст с диапазонами текста с различным эффектом рисования цвета на каждом из них, как показано на следующем снимке экрана.
Заметка
Это руководство призвано быть упрощенным примером создания пользовательских эффектов рисования клиента, а не простого метода для рисования цветового текста. См. страницу справочных материалов IDWriteTextLayout::SetDrawingEffect для получения дополнительной информации.
В этом руководстве содержатся следующие части:
- шаг 1. Создание макета текста
- шаг 2. Реализация пользовательского класса эффектов рисования
- шаг 3. Реализация класса пользовательского отрисовщика текста
- шаг 4. Создание отрисовщика текста
- Шаг 5: Создание экземпляров объектов эффектов цветного рисунка
- шаг 6. Настройка эффекта рисования для определенных текстовых диапазонов
- шаг 7. Рисование макета текста с помощью пользовательского средства отрисовки
- шаг 8. Очистка
Шаг 1. Создание текстового макета
Для начала вам потребуется приложение с объектом IDWriteTextLayout. Если у вас уже есть приложение, отображающее текст с текстовым макетом, или вы используете пример пользовательского кода DrawingEffect, перейдите к этапу 2.
Чтобы добавить текстовый макет, необходимо выполнить следующее:
Объявите указатель на интерфейс IDWriteTextLayout в качестве члена класса.
IDWriteTextLayout* pTextLayout_;
В конце метода CreateDeviceIndependentResources создайте объект интерфейса IDWriteTextLayout путем вызова метода CreateTextLayout.
// 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, а целевой объект отрисовки 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_);