Representación mediante un representador de texto personalizado

Un diseño de DirectWritetext puede dibujarse mediante un representador de texto personalizado derivado de IDWriteTextRenderer. Se requiere un representador personalizado para aprovechar algunas características avanzadas de DirectWrite, como la representación en un mapa de bits o una superficie GDI, objetos insertados y efectos de dibujo de cliente. En este tutorial se describen los métodos de IDWriteTextRenderer y se proporciona una implementación de ejemplo que usa Direct2D para representar texto con un relleno de mapa de bits.

Este tutorial contiene las siguientes partes:

El representador de texto personalizado debe implementar los métodos heredados de IUnknown además de los métodos enumerados en la página de referencia IDWriteTextRenderer y a continuación.

Para obtener el código fuente completo del representador de texto personalizado, consulte los archivos CustomTextRenderer.cpp y CustomTextRenderer.h del ejemplo de DirectWrite Hola mundo.

The Constructor

El representador de texto personalizado necesitará un constructor. En este ejemplo se usan pinceles Direct2D sólidos y de mapa de bits para representar el texto.

Por este motivo, el constructor toma los parámetros que se encuentran en la tabla siguiente con descripciones.

Parámetro Descripción
pD2DFactory Puntero a un objeto ID2D1Factory que se usará para crear los recursos de Direct2D necesarios.
Prt Puntero al objeto ID2D1HwndRenderTarget al que se representará el texto.
pOutlineBrush Puntero al id2D1SolidColorBrush que se usará para dibujar el esquema del texto.
pFillBrush Puntero al id2D1BitmapBrush que se usará para rellenar el texto.

 

El constructor los almacenará como se muestra en el código siguiente.

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()

El método DrawGlyphRun es el método de devolución de llamada principal del representador de texto. Se pasa una serie de glifos que se van a representar además de información como el origen de línea base y el modo de medición. También pasa un objeto de efecto de dibujo de cliente que se va a aplicar a la ejecución del glifo. Para obtener más información, vea el tema How to Add Client Drawing Effects to a Text Layout (Cómo agregar efectos de dibujo de cliente a un diseño de texto ).

Esta implementación del representador de texto representa las ejecuciones de glifo mediante su conversión en geometrías de Direct2D y, a continuación, dibuja y rellena las geometrías. Esto consta de los pasos siguientes.

  1. Cree un objeto ID2D1PathGeometry y, a continuación, recupere el objeto ID2D1GeometrySink mediante el 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. El DWRITE_GLYPH_RUN que se pasa a DrawGlyphRun contiene un objeto IDWriteFontFace , denominado fontFace, que representa la cara de fuente para toda la ejecución del glifo. Coloque el esquema del glifo en el receptor de geometría mediante el método IDWriteFontFace:: GetGlyphRunOutline , como se muestra en el código siguiente.

    // 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. Después de rellenar el receptor de geometría, ciérralo.

    // Close the geometry sink
    if (SUCCEEDED(hr))
    {
        hr = pSink->Close();
    }
    
  4. El origen de la ejecución del glifo debe traducirse para que se represente desde el origen de línea base correcto, como se muestra en el código siguiente.

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

    BaselineOriginX y baselineOriginY se pasan como parámetros al método de devolución de llamada DrawGlyphRun.

  5. Cree la geometría transformada mediante el método ID2D1Factory::CreateTransformedGeometry y pase la geometría de ruta de acceso y la matriz de traducción.

    // Create the transformed geometry
    ID2D1TransformedGeometry* pTransformedGeometry = NULL;
    if (SUCCEEDED(hr))
    {
        hr = pD2DFactory_->CreateTransformedGeometry(
            pPathGeometry,
            &matrix,
            &pTransformedGeometry
            );
    }
    
  6. Por último, dibuje el contorno de la geometría transformada y llene con los métodos ID2D1RenderTarget::D rawGeometry e ID2D1RenderTarget::FillGeometry y los pinceles direct2D almacenados como variables de miembro.

        // Draw the outline of the glyph run
        pRT_->DrawGeometry(
            pTransformedGeometry,
            pOutlineBrush_
            );
    
        // Fill in the glyph run
        pRT_->FillGeometry(
            pTransformedGeometry,
            pFillBrush_
            );
    
  7. Ahora que ha terminado de dibujar, no olvide limpiar los objetos que se crearon en este método.

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

DrawUnderline() y DrawStrikethrough()

IDWriteTextRenderer también tiene devoluciones de llamada para dibujar el subrayado y el tachado. En este ejemplo se dibuja un rectángulo simple para un subrayado o tachado, pero se pueden dibujar otras formas.

Dibujar un subrayado mediante Direct2D consta de los pasos siguientes.

  1. En primer lugar, cree una estructura D2D1_RECT_F del tamaño y la forma del subrayado. La estructura DWRITE_UNDERLINE que se pasa al método de devolución de llamada DrawUnderline proporciona el desplazamiento, el ancho y el grosor del subrayado.

    D2D1_RECT_F rect = D2D1::RectF(
        0,
        underline->offset,
        underline->width,
        underline->offset + underline->thickness
        );
    
  2. A continuación, cree un objeto ID2D1RectangleGeometry mediante el método ID2D1Factory::CreateRectangleGeometry y la estructura de D2D1_RECT_F inicializada.

    ID2D1RectangleGeometry* pRectangleGeometry = NULL;
    hr = pD2DFactory_->CreateRectangleGeometry(
            &rect, 
            &pRectangleGeometry
            );
    
  3. Al igual que con la ejecución del glifo, el origen de la geometría de subrayado debe traducirse, en función de los valores de origen de línea base, mediante el 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 último, dibuje el contorno de la geometría transformada y llene con los métodos ID2D1RenderTarget::D rawGeometry e ID2D1RenderTarget::FillGeometry y los pinceles direct2D almacenados como variables de miembro.

        // Draw the outline of the glyph run
        pRT_->DrawGeometry(
            pTransformedGeometry,
            pOutlineBrush_
            );
    
        // Fill in the glyph run
        pRT_->FillGeometry(
            pTransformedGeometry,
            pFillBrush_
            );
    
  5. Ahora que ha terminado de dibujar, no olvide limpiar los objetos que se crearon en este método.

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

El proceso para dibujar un tachado es el mismo. Sin embargo, el tachado tendrá un desplazamiento diferente y probablemente un ancho y un grosor diferentes.

Ajuste de píxeles, píxeles por DIP y transformación

IsPixelSnappingDisabled()

Se llama a este método para determinar si el ajuste de píxeles está deshabilitado. El valor predeterminado recomendado es FALSE y es el resultado de este ejemplo.

*isDisabled = FALSE;

GetCurrentTransform()

Este ejemplo se representa en un destino de representación de Direct2D, por lo que reenvía la transformación desde el destino de representación mediante ID2D1RenderTarget::GetTransform.

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

GetPixelsPerDip()

Se llama a este método para obtener el número de píxeles por píxel independiente del dispositivo (DIP).

float x, yUnused;

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

DrawInlineObject()

Un representador de texto personalizado también tiene una devolución de llamada para dibujar objetos insertados. En este ejemplo, DrawInlineObject devuelve E_NOTIMPL. Una explicación de cómo dibujar objetos insertados está fuera del ámbito de este tutorial. Para obtener más información, vea el tema How to Add Inline Objects to a Text Layout (Cómo agregar objetos insertados a un diseño de texto ).

The Destructor

Es importante liberar los punteros usados por la clase de representador de texto personalizado.

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

Uso del representador de texto personalizado

Se representa con el representador personalizado mediante el método IDWriteTextLayout::D raw , que toma una interfaz de devolución de llamada derivada de IDWriteTextRenderer como argumento, como se muestra en el código siguiente.

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

El método IDWriteTextLayout::D raw llama a los métodos de la devolución de llamada del representador personalizado que proporcione. Los métodos DrawGlyphRun, DrawUnderline, DrawInlineObject y DrawStrikethrough descritos anteriormente realizan las funciones de dibujo.