Partilhar via


Renderizar usando um renderizador de texto personalizado

Um de layout de texto DirectWrite pode ser desenhado por um renderizador de texto personalizado derivado de IDWriteTextRenderer. Um renderizador personalizado é necessário para aproveitar alguns recursos avançados do DirectWrite, como renderização em um bitmap ou superfície GDI, objetos embutidos e efeitos de desenho de cliente. Este tutorial descreve os métodos de IDWriteTextRenderere fornece um exemplo de implementação que usa Direct2D para renderizar texto com um preenchimento de bitmap.

Este tutorial contém as seguintes partes:

Seu renderizador de texto personalizado deve implementar os métodos herdados de IUnknown, além de implementar os métodos listados na página de referência do IDWriteTextRenderer e abaixo.

Para o código-fonte completo do renderizador de texto personalizado, consulte os arquivos CustomTextRenderer.cpp e CustomTextRenderer.h do exemplo DirectWrite Hello World.

O Construtor

Seu renderizador de texto personalizado precisará de um construtor. Este exemplo usa tanto os pincéis sólidos como bitmap Direct2D para renderizar o texto.

Devido a isso, o construtor usa os parâmetros encontrados na tabela abaixo com descrições.

Parâmetro Descrição
pD2DFactory Um ponteiro para um objeto ID2D1Factory que será usado para criar os recursos Direct2D necessários.
pRT Um ponteiro para o objeto ID2D1HwndRenderTarget ao qual o texto será renderizado.
pOutlineBrush Ponteiro para o ID2D1SolidColorBrush que será usado para desenhar o contorno do texto
pFillBrush Um ponteiro para o ID2D1BitmapBrush que será usado para preencher o texto.

 

Eles serão armazenados pelo construtor como mostrado no código a seguir.

CustomTextRenderer::CustomTextRenderer(
    ID2D1Factory* pD2DFactory, 
    ID2D1HwndRenderTarget* pRT, 
    ID2D1SolidColorBrush* pOutlineBrush, 
    ID2D1BitmapBrush* pFillBrush
    )
:
cRefCount_(0), 
pD2DFactory_(pD2DFactory), 
pRT_(pRT), 
pOutlineBrush_(pOutlineBrush), 
pFillBrush_(pFillBrush)
{
    pD2DFactory_->AddRef();
    pRT_->AddRef();
    pOutlineBrush_->AddRef();
    pFillBrush_->AddRef();
}

DrawGlyphRun()

O método DrawGlyphRun é o principal método de retorno de chamada do renderizador de texto. É passada uma série de glifos para serem renderizados, além de informações como a origem da linha de base e o modo de medição. Ele também passa um objeto de efeito de desenho do cliente para ser aplicado à execução do glifo. Para obter mais informações, consulte o tópico Como adicionar efeitos de desenho do cliente a um layout de texto.

Esta implementação do renderizador de texto renderiza sequências de glifos convertendo-as em geometrias de Direct2D e desenhando e preenchendo as geometrias em seguida. Isso consiste nas seguintes etapas.

  1. Crie um objeto ID2D1PathGeometry e, em seguida, obtenha o objeto ID2D1GeometrySink usando o método ID2D1PathGeometry::Open.

    // 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
            );
    }
    
  2. O DWRITE_GLYPH_RUN que é passado para DrawGlyphRun contém um objetoIDWriteFontFace, chamado fontFace, que representa a face da fonte para toda a execução do glifo. Coloque o contorno da execução do glifo no coletor de geometria usando o IDWriteFontFace:: GetGlyphRunOutline método, conforme mostrado no código a seguir.

    // 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
            );
    }
    
  3. Depois de preencher o ponto de escoamento geométrico, feche-o.

    // Close the geometry sink
    if (SUCCEEDED(hr))
    {
        hr = pSink->Close();
    }
    
  4. A origem da sequência do glifo deve ser traduzida para que seja exibida a partir da origem correta da linha de base, como mostrado no código a seguir.

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

    Os baselineOriginX e baselineOriginY são passados como parâmetros ao método de retorno de chamada DrawGlyphRun.

  5. Crie a geometria transformada usando o método ID2D1Factory::CreateTransformedGeometry e passando a geometria do caminho e a matriz de tradução.

    // Create the transformed geometry
    ID2D1TransformedGeometry* pTransformedGeometry = NULL;
    if (SUCCEEDED(hr))
    {
        hr = pD2DFactory_->CreateTransformedGeometry(
            pPathGeometry,
            &matrix,
            &pTransformedGeometry
            );
    }
    
  6. Finalmente, desenhe o contorno da geometria transformada e preencha-o usando os métodos ID2D1RenderTarget::DrawGeometry e ID2D1RenderTarget::FillGeometry e os pincéis Direct2D armazenados como variáveis membro.

        // Draw the outline of the glyph run
        pRT_->DrawGeometry(
            pTransformedGeometry,
            pOutlineBrush_
            );
    
        // Fill in the glyph run
        pRT_->FillGeometry(
            pTransformedGeometry,
            pFillBrush_
            );
    
  7. Agora que terminou de desenhar, não se esqueça de limpar os objetos que foram criados neste método.

    SafeRelease(&pPathGeometry);
    SafeRelease(&pSink);
    SafeRelease(&pTransformedGeometry);
    

DrawUnderline() e DrawStrikethrough()

IDWriteTextRenderer também tem retornos de chamada para desenhar o sublinhado e o tachado. Este exemplo desenha um retângulo simples para um sublinhado ou tachado, mas podem ser desenhadas outras formas.

Desenhar um sublinhado usando Direct2D consiste nas etapas a seguir.

  1. Primeiro, crie uma estrutura D2D1_RECT_F do tamanho e da forma do sublinhado. A estrutura DWRITE_UNDERLINE que é passada para o método callback DrawUnderline fornece o deslocamento, largura e espessura do sublinhado.

    D2D1_RECT_F rect = D2D1::RectF(
        0,
        underline->offset,
        underline->width,
        underline->offset + underline->thickness
        );
    
  2. Em seguida, crie um objeto ID2D1RectangleGeometry usando o método ID2D1Factory::CreateRectangleGeometry e a estrutura D2D1_RECT_F inicializada.

    ID2D1RectangleGeometry* pRectangleGeometry = NULL;
    hr = pD2DFactory_->CreateRectangleGeometry(
            &rect, 
            &pRectangleGeometry
            );
    
  3. Assim como na execução do glifo, a origem da geometria do sublinhado deve ser traduzida com base nos valores de origem da linha de base, utilizando o método CreateTransformedGeometry.

    // Initialize a matrix to translate the origin of the underline
    D2D1::Matrix3x2F const matrix = D2D1::Matrix3x2F(
        1.0f, 0.0f,
        0.0f, 1.0f,
        baselineOriginX, baselineOriginY
        );
    
    ID2D1TransformedGeometry* pTransformedGeometry = NULL;
    if (SUCCEEDED(hr))
    {
        hr = pD2DFactory_->CreateTransformedGeometry(
            pRectangleGeometry,
            &matrix,
            &pTransformedGeometry
            );
    }
    
  4. Finalmente, desenhe o contorno da geometria transformada e preencha-o usando os métodos ID2D1RenderTarget::DrawGeometry e ID2D1RenderTarget::FillGeometry e os pincéis Direct2D armazenados como variáveis membro.

        // Draw the outline of the glyph run
        pRT_->DrawGeometry(
            pTransformedGeometry,
            pOutlineBrush_
            );
    
        // Fill in the glyph run
        pRT_->FillGeometry(
            pTransformedGeometry,
            pFillBrush_
            );
    
  5. Agora que terminou de desenhar, não se esqueça de limpar os objetos que foram criados neste método.

    SafeRelease(&pRectangleGeometry);
    SafeRelease(&pTransformedGeometry);
    

O processo para aplicar um riscado é o mesmo. No entanto, o tachado terá um deslocamento diferente e, provavelmente, uma largura e espessura diferentes.

Correção de Pixels, Pixels por DIP e Transformação

IsPixelSnappingDisabled()

Este método é chamado para determinar se o encaixe de pixel foi desativado. O padrão recomendado é FALSE, e essa é a saída deste exemplo.

*isDisabled = FALSE;

GetCurrentTransform()

Este exemplo renderiza para um destino de renderização Direct2D, portanto, transmita a transformação a partir do destino de renderização usando ID2D1RenderTarget::GetTransform.

//forward the render target's transform
pRT_->GetTransform(reinterpret_cast<D2D1_MATRIX_3X2_F*>(transform));

GetPixelsPerDip()

Este método é chamado para obter o número de pixéis por Pixel Independente de Dispositivo (DIP).

float x, yUnused;

pRT_->GetDpi(&x, &yUnused);
*pixelsPerDip = x / 96;

DrawInlineObject()

Um renderizador de texto personalizado também tem uma função de retorno para desenhar objetos embutidos. Neste exemplo, DrawInlineObject retorna E_NOTIMPL. Uma explicação de como desenhar objetos embutidos está além do escopo deste tutorial. Para obter mais informações, consulte o tópico Como adicionar objetos embutidos a um layout de texto.

O Destruidor

É importante liberar todos os ponteiros que foram usados pela classe de renderizador de texto personalizado.

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

Usando o renderizador de texto personalizado

Você renderiza com o renderizador personalizado usando o método IDWriteTextLayout::D raw, que usa uma interface de retorno de chamada derivada de IDWriteTextRenderer como um argumento, conforme mostrado no código a seguir.

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

O método IDWriteTextLayout::D raw chama os métodos do retorno de chamada do renderizador personalizado fornecido. Os métodos DrawGlyphRun, DrawUnderline, DrawInlineObjecte DrawStrikethrough descritos acima executam as funções de desenho.