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:
- O construtor
- DrawGlyphRun()
- DrawUnderline() e DrawStrikethrough()
- Captura de pixels, pixels por DIP e transformação
- DrawInlineObject()
- O Destruidor
- Usando o Renderizador de Texto Personalizado
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.
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 ); }
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 ); }
Depois de preencher o coletor de geometria, feche-o.
// Close the geometry sink if (SUCCEEDED(hr)) { hr = pSink->Close(); }
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 .
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 ); }
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_ );
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.
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 );
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 );
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 ); }
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_ );
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.