Compartilhar via


Renderizar usando um renderizador de texto personalizado

Um layout DirectWritetexto pode ser desenhado por um renderizador de texto personalizado derivado de IDWriteTextRenderer. Um renderizador personalizado é necessário para aproveitar alguns recursos avançados de DirectWrite, como renderização para uma superfície de bitmap ou GDI, objetos embutidos e efeitos de desenho do cliente. Este tutorial descreve os métodos de IDWriteTextRenderer e fornece uma implementação de exemplo 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 dos métodos listados na página de referência IDWriteTextRenderer e abaixo.

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

O construtor

O renderizador de texto personalizado precisará de um construtor. Este exemplo usa pincéis de Direct2D sólidos e bitmap para renderizar o texto.

Por 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 qualquer Direct2D recursos necessários.
Prt Um ponteiro para o objeto ID2D1HwndRenderTarget para o qual o texto será renderizado.
pOutlineBrush Um ponteiro para o ID2D1SolidColorBrush que será usado para desenhar a estrutura de tópicos do texto
pFillBrush Um ponteiro para o ID2D1BitmapBrush que será usado para preencher o texto.

 

Elas serão armazenadas pelo construtor, conforme 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 método de retorno de chamada main do renderizador de texto. Ele é passado uma execução de glifos a 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 a 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 .

Essa implementação do renderizador de texto renderiza execuções de glifo convertendo-as em geometrias Direct2D e, em seguida, desenhando e preenchendo as geometrias. Isso consiste nas etapas a seguir.

  1. Crie um objeto ID2D1PathGeometry e recupere 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 passado para DrawGlyphRun contém um objeto IDWriteFontFace , chamado fontFace, que representa a face da fonte para toda a execução do glifo. Coloque a estrutura de tópicos da execução do glifo no coletor de geometria usando o método IDWriteFontFace:: GetGlyphRunOutline , 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 coletor de geometria, feche-o.

    // Close the geometry sink
    if (SUCCEEDED(hr))
    {
        hr = pSink->Close();
    }
    
  4. A origem da execução do glifo deve ser convertida para que ela seja renderizada da origem de linha de base correta, conforme 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 para o 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. Por fim, desenhe a estrutura de tópicos da geometria transformada e preencha-a usando os métodos ID2D1RenderTarget::D rawGeometry 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 você terminou de desenhar, não se esqueça de limpo 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 outras formas podem ser desenhadas.

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 de retorno de chamada DrawUnderline fornece o deslocamento, a largura e a 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 de D2D1_RECT_F inicializada.

    ID2D1RectangleGeometry* pRectangleGeometry = NULL;
    hr = pD2DFactory_->CreateRectangleGeometry(
            &rect, 
            &pRectangleGeometry
            );
    
  3. Assim como acontece com a execução do glifo, a origem da geometria sublinhada deve ser convertida, com base nos valores de origem da linha de base, usando 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. Por fim, desenhe a estrutura de tópicos da geometria transformada e preencha-a usando os métodos ID2D1RenderTarget::D rawGeometry 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 você terminou de desenhar, não se esqueça de limpo os objetos que foram criados neste método.

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

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

Captura de pixels, pixels por DIP e transformação

IsPixelSnappingDisabled()

Esse método é chamado para determinar se a captura de pixel está desabilitada. O padrão recomendado é FALSE e essa é a saída deste exemplo.

*isDisabled = FALSE;

GetCurrentTransform()

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

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

GetPixelsPerDip()

Esse método é chamado para obter o número de pixels por DIP (Pixel Independente do Dispositivo).

float x, yUnused;

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

DrawInlineObject()

Um renderizador de texto personalizado também tem um retorno de chamada 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 personalizada.

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 que você fornece. Os métodos DrawGlyphRun, DrawUnderline, DrawInlineObject e DrawStrikethrough descritos acima executam as funções de desenho.