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.
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
- Etapa 2: Implementar uma classe de efeito de desenho personalizada
- Etapa 3: Implementar uma classe de renderizador de texto personalizado
- Etapa 4: Criar o Renderizador de Texto
- Etapa 5: Criar uma instância dos objetos de efeito de desenho de cores
- Etapa 6: Definir o efeito de desenho para intervalos de texto específicos
- Etapa 7: Desenhar o layout de texto usando o renderizador personalizado
- Etapa 8: Limpar
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:
Declare um ponteiro para uma interface IDWriteTextLayout como um membro da classe .
IDWriteTextLayout* pTextLayout_;
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. ); }
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_);