Поделиться через


Добавление эффектов рисования клиента в текстовый макет

Содержит краткое руководство по добавлению эффектов рисования клиентской стороны в приложение DirectWrite, которая отображает текст с помощью интерфейса IDWriteTextLayout и кастомного отрисовщика текста.

Конечный продукт этого руководства — это приложение, которое отображает текст с диапазонами текста с различным эффектом рисования цвета на каждом из них, как показано на следующем снимке экрана.

снимок экрана

Заметка

Это руководство призвано быть упрощенным примером создания пользовательских эффектов рисования клиента, а не простого метода для рисования цветового текста. См. страницу справочных материалов IDWriteTextLayout::SetDrawingEffect для получения дополнительной информации.

 

В этом руководстве содержатся следующие части:

Шаг 1. Создание текстового макета

Для начала вам потребуется приложение с объектом IDWriteTextLayout. Если у вас уже есть приложение, отображающее текст с текстовым макетом, или вы используете пример пользовательского кода DrawingEffect, перейдите к этапу 2.

Чтобы добавить текстовый макет, необходимо выполнить следующее:

  1. Объявите указатель на интерфейс IDWriteTextLayout в качестве члена класса.

    IDWriteTextLayout* pTextLayout_;
    
    
  2. В конце метода 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.
            );
    }
    
    
  3. Наконец, не забудьте освободить текстовый макет в деструкторе.

    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_);