Compartilhar via


Como adicionar efeitos de desenho do cliente a um layout de texto

Fornece um breve tutorial sobre como adicionar efeitos de desenho do cliente a um aplicativo DirectWrite que exibe texto usando a interface IDWriteTextLayout e um renderizador de texto personalizado.

O produto final deste tutorial é um aplicativo que exibe texto que tem intervalos de texto com um efeito de desenho de cores diferente em cada um, conforme mostrado na captura de tela a seguir.

captura de tela de

Observação

Este tutorial destina-se a ser um exemplo simplificado de como criar efeitos de desenho personalizados do cliente, não um exemplo de um método simples para desenhar texto de cor. Consulte a página de referência IDWriteTextLayout::SetDrawingEffect para obter mais informações.

 

Este tutorial contém as seguintes partes:

Etapa 1: Criar um layout de texto

Para começar, você precisará de um aplicativo com um objeto IDWriteTextLayout . Se você já tiver um aplicativo que exibe texto com um layout de texto ou estiver usando o Código de Exemplo de DrawingEffect Personalizado, pule para a Etapa 2.

Para adicionar um layout de texto, você deve fazer o seguinte:

  1. Declare um ponteiro para uma interface IDWriteTextLayout como um membro da classe .

    IDWriteTextLayout* pTextLayout_;
    
    
  2. No final do método CreateDeviceIndependentResources , crie um objeto de interface IDWriteTextLayout chamando o método 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. Por fim, lembre-se de liberar o layout de texto no destruidor.

    SafeRelease(&pTextLayout_);
    

Etapa 2: Implementar uma classe de efeito de desenho personalizada

Além dos métodos herdados do IUnknown, uma interface de efeito de desenho do cliente personalizada não tem requisitos sobre o que deve implementar. Nesse caso, a classe ColorDrawingEffect simplesmente contém um valor D2D1_COLOR_F e declara métodos para obter e definir esse valor, bem como um construtor que pode definir a cor inicialmente.

Depois que um efeito de desenho do cliente é aplicado a um intervalo de texto em um objeto IDWriteTextLayout , o efeito de desenho é passado para o método IDWriteTextRenderer::D rawGlyphRun de qualquer execução de glifo que deve ser renderizada. Os métodos do efeito de desenho estão então disponíveis para o renderizador de texto.

Um efeito de desenho do cliente pode ser tão complexo quanto você deseja torná-lo, carregando mais informações do que neste exemplo, além de fornecer métodos para alterar glifos, criar objetos a serem usados para desenho e assim por diante.

Etapa 3: Implementar uma classe de renderizador de texto personalizado

Para aproveitar um efeito de desenho do cliente, você deve implementar um renderizador de texto personalizado. Esse renderizador de texto aplicará o efeito de desenho passado a ele pelo método IDWriteTextLayout::D raw à execução do glifo que está sendo desenhada.

O construtor

O construtor do renderizador de texto personalizado armazena o objeto ID2D1Factory que será usado para criar objetos Direct2D e o Direct2D destino de renderização no qual o texto será desenhado.

CustomTextRenderer::CustomTextRenderer(
    ID2D1Factory* pD2DFactory, 
    ID2D1HwndRenderTarget* pRT
    )
:
cRefCount_(0), 
pD2DFactory_(pD2DFactory), 
pRT_(pRT)
{
    pD2DFactory_->AddRef();
    pRT_->AddRef();
}

O método DrawGlyphRun

Uma execução de glifo é um conjunto de glifos que compartilham o mesmo formato, incluindo o efeito de desenho do cliente. O método DrawGlyphRun cuida da renderização de texto para uma execução de glifo especificada.

Primeiro, crie um ID2D1PathGeometry e um ID2D1GeometrySink e recupere a estrutura de tópicos de execução do glifo usando IDWriteFontFace::GetGlyphRunOutline. Em seguida, transforme a origem da geometria usando o método Direct2DID2D1Factory::CreateTransformedGeometry, conforme mostrado no código a seguir.

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

Em seguida, declare um objeto de pincel Direct2D sólido.

ID2D1SolidColorBrush* pBrush = NULL;

Se o parâmetro clientDrawingEffect não for NULL, consulte o objeto para a interface ColorDrawingEffect . Isso funcionará porque você definirá essa classe como o efeito de desenho do cliente nos intervalos de texto do objeto de layout de texto.

Depois de ter um ponteiro para a interface ColorDrawingEffect , você poderá recuperar o valor D2D1_COLOR_F que ele armazena usando o método GetColor . Em seguida, use o D2D1_COLOR_F para criar um ID2D1SolidColorBrush nessa cor.

Se o parâmetro clientDrawingEffect for NULL, basta criar um ID2D1SolidColorBrush preto.

// 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);
    }
}

Por fim, desenhe a geometria da estrutura de tópicos e preencha-a usando o pincel de cor sólida que você acabou de criar.

if (SUCCEEDED(hr))
{
    // Draw the outline of the glyph run
    pRT_->DrawGeometry(
        pTransformedGeometry,
        pBrush
        );

    // Fill in the glyph run
    pRT_->FillGeometry(
        pTransformedGeometry,
        pBrush
        );
}

O Destruidor

Não se esqueça de liberar a fábrica Direct2D e renderizar o destino no destruidor.

CustomTextRenderer::~CustomTextRenderer()
{
    SafeRelease(&pD2DFactory_);
    SafeRelease(&pRT_);
}

Etapa 4: Criar o Renderizador de Texto

Nos recursos CreateDeviceDependent , crie o objeto renderizador de texto personalizado. Ele depende do dispositivo porque usa o ID2D1RenderTarget, que é dependente do dispositivo.

// Create the text renderer
pTextRenderer_ = new CustomTextRenderer(
                        pD2DFactory_,
                        pRT_
                        );

Etapa 5: Criar uma instância dos objetos de efeito de desenho de cores

Instanciar objetos ColorDrawingEffect em vermelho, verde e azul.

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

Etapa 6: Definir o efeito de desenho para intervalos de texto específicos

Defina o efeito de desenho para intervalos específicos de texto usando o método IDWriteTextLayou::SetDrawingEffect e um struct 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);
    }
}

Etapa 7: Desenhar o layout de texto usando o renderizador personalizado

Você deve chamar o método IDWriteTextLayout::D raw em vez dos métodos ID2D1RenderTarget::D rawText ou ID2D1RenderTarget::D rawTextLayout .

// Draw the text layout using DirectWrite and the CustomTextRenderer class.
hr = pTextLayout_->Draw(
        NULL,
        pTextRenderer_,  // Custom text renderer.
        origin.x,
        origin.y
        );

Etapa 8: Limpar

No destruidor DemoApp, libere o renderizador de texto personalizado.

SafeRelease(&pTextRenderer_);

Depois disso, adicione código para liberar as classes de efeito de desenho do cliente.

SafeRelease(&redDrawingEffect_);
SafeRelease(&blueDrawingEffect_);
SafeRelease(&greenDrawingEffect_);