Renderização de texto com Direct2D e DirectWrite

Ao contrário de outras APIs, como GDI, GDI+ ou WPF, o Direct2D interopera com outra API, DirectWrite, para manipular e renderizar texto. Este tópico descreve os benefícios e a interoperação desses componentes separados.

Este tópico inclui as seções a seguir.

O Direct2D permite a adoção incremental

Mover um aplicativo de uma API gráfica para outra pode ser difícil ou não ser o que você deseja por vários motivos. Isso pode ocorrer porque você precisa oferecer suporte a plug-ins que ainda usam as interfaces mais antigas, porque o aplicativo em si é muito grande para ser portado para uma nova API em uma versão ou porque alguma parte da API mais recente é desejável, mas a API mais antiga está funcionando bem o suficiente para outras partes do aplicativo.

Como Direct2D e DirectWrite são implementados como componentes separados, você pode atualizar todo o sistema gráfico 2D ou apenas a parte de texto dele. Por exemplo, você pode atualizar um aplicativo para usar DirectWrite para texto, mas ainda usar GDI ou GDI+ para renderização.

Serviços de texto versus renderização de texto

À medida que os aplicativos evoluíram, seus requisitos de processamento de texto se tornaram cada vez mais complexos. No início, o texto geralmente era confinado à interface do usuário estaticamente definida e o texto era renderizado em uma caixa bem definida, como um botão. À medida que os aplicativos começaram a estar disponíveis em um número crescente de idiomas, essa abordagem tornou-se mais difícil de sustentar porque a largura e a altura do texto traduzido podem variar significativamente entre os idiomas. Para se adaptar, os aplicativos começaram a definir dinamicamente sua interface do usuário para depender do tamanho real renderizado do texto, em vez do contrário.

Para ajudar os aplicativos a concluir essa tarefa, DirectWrite fornece a interface IDWriteTextLayout . Essa API permite que um aplicativo especifique um trecho de texto com características complexas, como diferentes fontes e tamanhos de fonte, sublinhados, tachados, texto bidirecional, efeitos, reticências e até mesmo caracteres não glifo incorporados (como um emoticon ou um ícone de bitmap). O aplicativo pode alterar várias características do texto à medida que determina iterativamente seu layout de interface do usuário. O Exemplo DirectWrite Hello World, que é mostrado na ilustração a seguir e no tópico Tutorial: Introdução ao DirectWrite, mostra muitos desses efeitos.

captura de tela do exemplo

O layout pode posicionar os glifos idealmente com base em suas larguras (como o WPF faz) ou pode ajustar os glifos para as posições de pixel mais próximas (como o GDI faz).

Além de obter medidas de texto, o aplicativo pode testar várias partes do texto. Por exemplo, ele pode querer saber se um hiperlink no texto foi clicado. (Para obter mais informações sobre o teste de clique, consulte o tópico Como executar o teste de clique em um layout de texto).

A interface de layout de texto é desacoplada da API de renderização que o aplicativo usa, como mostra o diagrama a seguir:

layout de texto e diagrama de API gráfica.

Essa separação é possível porque o DirectWrite fornece uma interface de renderização (IDWriteTextRenderer) que os aplicativos podem implementar para renderizar texto usando qualquer API gráfica desejada. O método de retorno de chamada IDWriteTextRenderer::D rawGlyphRun implementado é chamado pelo DirectWrite ao renderizar um layout de texto. É responsabilidade desse método realizar as operações de desenho ou repassá-las.

Para desenhar glifos, o Direct2D fornece ID2D1RenderTarget::DrawGlyphRun para desenho em uma superfície do Direct2D e DirectWrite fornece IDWriteBitmapRenderTarget::DrawGlyphRun para desenhar em uma superfície GDI que pode então ser transferida para uma janela usando GDI. Convenientemente, DrawGlyphRun no Direct2D e no DirectWrite têm parâmetros exatamente compatíveis com o método DrawGlyphRun que o aplicativo implementa em IDWriteTextRenderer.

Após uma separação semelhante, os recursos específicos do texto (como enumeração e gerenciamento de fontes, análise de glifos e assim por diante) são tratados pelo DirectWrite em vez do Direct2D. Os objetos DirectWrite são aceitos diretamente pelo Direct2D. Para ajudar os aplicativos GDI existentes a aproveitar o DirectWrite, ele fornece a interface do método IDWriteGdiInterop com métodos para fazer o seguinte:

Glifos versus texto

O texto é um conjunto de pontos de código Unicode (caracteres), com vários modificadores estilísticos (fontes, pesos, sublinhados, tachados e assim por diante) dispostos em um retângulo. Um glifo, por outro lado, é um índice específico em um arquivo de fonte específico. Um glifo define um conjunto de curvas que podem ser renderizadas, mas não tem nenhum significado textual. Potencialmente, há um mapeamento de muitos para muitos entre glifos e caracteres. Uma sequência de glifos que vêm da mesma Face de Fonte e que são dispostos sequencialmente em uma linha de base é chamada de GlyphRun. O DirectWrite e o Direct2D chamam sua API de renderização de glifos mais precisa DrawGlyphRun e têm assinaturas bem semelhantes. O seguinte é de ID2D1RenderTarget no Direct2D:

STDMETHOD_(void, DrawGlyphRun)(
        D2D1_POINT_2F baselineOrigin,
        __in CONST DWRITE_GLYPH_RUN *glyphRun,
        __in ID2D1Brush *foregroundBrush,
        DWRITE_MEASURING_MODE measuringMode = DWRITE_MEASURING_MODE_NATURAL 
        ) PURE;

E esse método é de IDWriteBitmapRenderTarget no DirectWrite:

STDMETHOD(DrawGlyphRun)(
        FLOAT baselineOriginX,
        FLOAT baselineOriginY,
        DWRITE_MEASURING_MODE measuringMode,
        __in DWRITE_GLYPH_RUN const* glyphRun,
        IDWriteRenderingParams* renderingParams,
        COLORREF textColor,
        __out_opt RECT* blackBoxRect = NULL
        ) PURE;

A versão do DirectWrite mantém a origem da linha de base, o modo de medição e os parâmetros de execução do glifo e inclui parâmetros adicionais.

DirectWrite também permite que você use um renderizador personalizado para glifos implementando a interface IDWriteTextRenderer. Essa interface também tem um método DrawGlyphRun, como mostra o exemplo de código a seguir.

STDMETHOD(DrawGlyphRun)(
        __maybenull void* clientDrawingContext,
        FLOAT baselineOriginX,
        FLOAT baselineOriginY,
        DWRITE_MEASURING_MODE measuringMode,
        __in DWRITE_GLYPH_RUN const* glyphRun,
        __in DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription,
        __maybenull IUnknown* clientDrawingEffect
        ) PURE;

Esta versão inclui mais parâmetros que são úteis quando você implementa um renderizador de texto personalizado. O parâmetro final é usado para efeitos de desenho personalizados implementados pelo aplicativo. (Para obter mais informações sobre efeitos de desenho do cliente, consulte Como adicionar efeitos de desenho do cliente a um layout de texto.

Cada execução de glifo começa em uma origem e é colocada em uma linha a partir dessa origem. Os glifos são alterados pela transformação do mundo atual e pelas configurações de renderização de texto selecionadas no destino de renderização associado. Essa API geralmente é chamada diretamente apenas por aplicativos que fazem seu próprio layout (por exemplo, um processador de texto) ou por um aplicativo que implementou a interface IDWriteTextRenderer.

DirectWrite e Direct2D

Direct2D fornece serviços de renderização em nível de glifo por meio de DrawGlyphRun. No entanto, isso requer que o aplicativo implemente os detalhes da renderização, que basicamente reproduz a funcionalidade da API DrawText do GDI por conta própria.

Portanto, Direct2D fornece APIs que aceitam texto em vez de glifos: ID2D1RenderTarget::DrawTextLayout e ID2D1RenderTarget::DrawText. Ambos os métodos são renderizados em uma superfície Direct2D. Para renderizar em uma superfície GDI, IDWriteBitmapRenderTarget::D rawGlyphRun é fornecido. Mas esse método requer que um renderizador de texto personalizado seja implementado pelo aplicativo. (Para obter mais informações, consulte o tópico Renderizar para uma superfície GDI).

O uso de texto de um aplicativo normalmente começa simples: coloque OK ou Cancelar em um botão de layout fixo, por exemplo. No entanto, com o tempo, torna-se mais complexo à medida que a internacionalização e outros recursos são adicionados. Eventualmente, muitos aplicativos terão de usar os objetos de layout de texto do DirectWrite e implementar o renderizador de texto.

Portanto, o Direct2D fornece APIs em camadas que permitem que um aplicativo inicie de forma simples e se torne mais sofisticado sem precisar retroceder ou abandonar seu código de trabalho. Uma exibição simplificada é mostrada no diagrama a seguir:

diagrama dos aplicativos directwrite e direct2d.

DrawText

DrawText é a mais simples das APIs a ser usada. Ele usa uma cadeia de caracteres Unicode, um pincel de primeiro plano, um único objeto de formato e um retângulo de destino. Ele irá dispor e renderizar toda a cadeia de caracteres dentro do retângulo de layout e, opcionalmente, recortá-la. Isso é útil quando você coloca um texto simples em uma interface do usuário de layout fixo.

DrawTextLayout

Ao criar um objeto IDWriteTextLayout, um aplicativo pode começar a medir e organizar o texto e outros elementos da interface do usuário e dar suporte a várias fontes, estilos, sublinhados e tachados. Direct2D fornece a API DrawTextLayout que aceita diretamente esse objeto e renderiza o texto em um determinado ponto. (A largura e a altura são fornecidas pelo objeto de layout). Além de implementar todos os recursos de layout de texto esperados, Direct2D interpretará qualquer objeto de efeito como um pincel e aplicará esse pincel ao intervalo selecionado de glifos. Ele também chamará todos os objetos em linha. Um aplicativo pode inserir caracteres não glifos (ícones) no texto, se desejar. Outra vantagem de usar um objeto de layout de texto é que as posições do glifo são armazenadas em cache nele. Portanto, um grande ganho de desempenho é possível reutilizando o mesmo objeto de layout para várias chamadas de desenho e evitando recalcular as posições do glifo para cada chamada. Essa capacidade não está presente para DrawText do GDI.

DrawGlyphRun

Por fim, o aplicativo pode implementar a própria interface IDWriteTextRenderer e chamar DrawGlyphRun e FillRectangle ou qualquer outra API de renderização. Toda a interação existente com o objeto Layout de Texto permanecerá inalterada.

Para obter um exemplo de como implementar um renderizador de texto personalizado, consulte o tópico Renderizar usando um renderizador de texto personalizado.

Renderização de glifos

A adição de DirectWrite a um aplicativo GDI existente permite que o aplicativo use a API IDWriteBitmapRenderTarget para renderizar glifos. O método IDWriteBitmapRenderTarget::DrawGlyphRun que DirectWrite fornece será renderizado em cor sólida para um DC de memória sem exigir APIs adicionais como Direct2D.

Isso permite que o aplicativo obtenha recursos avançados de renderização de texto, como os seguintes:

  • O ClearType de subpixel permite que um aplicativo coloque glifos em posições de subpixel para permitir a renderização nítida do glifo e o layout do glifo.
  • A suavização de serrilhado na direção Y permite uma renderização mais suave de curvas em glifos maiores.

Um aplicativo que está migrando para Direct2D também obterá os seguintes recursos:

  • Aceleração de hardware.
  • A capacidade de preencher o texto com um pincel Direct2D arbitrário, como gradientes radiais, gradientes lineares e bitmaps.
  • Mais suporte para camadas e recorte por meio das APIs PushAxisAlignedClip, PushLayer e CreateCompatibleRenderTarget.
  • A capacidade de dar suporte à renderização de texto em Tons de cinza. Isso preenche corretamente o canal alfa de destino de acordo com a opacidade do pincel de texto e a suavização de serrilhado do texto.

Para dar suporte eficiente à aceleração de hardware, Direct2D usa uma aproximação ligeiramente diferente da correção de gama, chamada correção alfa. Isso não exige que o Direct2D inspecione o pixel de cor de destino de renderização ao renderizar texto.

Conclusão

Este tópico explica as diferenças e semelhanças entre Direct2D e DirectWrite e as motivações arquitetônicas para fornecê-las como APIs separadas e cooperativas.