Comment ajouter des effets de dessin client à une disposition de texte

Fournit un court tutoriel sur l’ajout d’effets de dessin client à une application DirectWrite qui affiche du texte à l’aide de l’interface IDWriteTextLayout et d’un convertisseur de texte personnalisé.

Le produit final de ce didacticiel est une application qui affiche du texte qui a des plages de texte avec un effet de dessin de couleur différent sur chacune, comme illustré dans la capture d’écran suivante.

capture d’écran de « exemple d’effet de dessin client ! » dans différentes couleurs

Notes

Ce tutoriel est destiné à être un exemple simplifié de création d’effets de dessin client personnalisés, et non un exemple de méthode simple pour dessiner du texte en couleur. Pour plus d’informations, consultez la page de référence IDWriteTextLayout::SetDrawingEffect .

 

Ce didacticiel contient les parties suivantes :

Étape 1 : Créer une disposition de texte

Pour commencer, vous aurez besoin d’une application avec un objet IDWriteTextLayout . Si vous avez déjà une application qui affiche du texte avec une disposition de texte, ou si vous utilisez l’exemple de code DrawingEffect personnalisé, passez à l’étape 2.

Pour ajouter une disposition de texte, vous devez effectuer les opérations suivantes :

  1. Déclarez un pointeur vers une interface IDWriteTextLayout en tant que membre de la classe.

    IDWriteTextLayout* pTextLayout_;
    
    
  2. À la fin de la méthode CreateDeviceIndependentResources, créez un objet d’interface IDWriteTextLayout en appelant la méthode 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.
            );
    }
    
    
  3. Enfin, n’oubliez pas de libérer la disposition du texte dans le destructeur.

    SafeRelease(&pTextLayout_);
    

Étape 2 : Implémenter une classe d’effet de dessin personnalisée

À part les méthodes héritées d’IUnknown, une interface d’effet de dessin client personnalisé n’a aucune exigence quant à ce qu’elle doit implémenter. Dans ce cas, la classe ColorDrawingEffect contient simplement une valeur D2D1_COLOR_F et déclare des méthodes pour obtenir et définir cette valeur, ainsi qu’un constructeur qui peut définir la couleur initialement.

Une fois qu’un effet de dessin client a été appliqué à une plage de texte dans un objet IDWriteTextLayout , l’effet de dessin est passé à la méthode IDWriteTextRenderer::D rawGlyphRun de toute exécution de glyphe qui doit être rendue. Les méthodes de l’effet de dessin sont ensuite disponibles pour le convertisseur de texte.

Un effet de dessin client peut être aussi complexe que vous le souhaitez, en transportant plus d’informations que dans cet exemple, en plus de fournir des méthodes pour modifier les glyphes, créer des objets à utiliser pour le dessin, etc.

Étape 3 : Implémenter une classe de convertisseur de texte personnalisé

Pour tirer parti d’un effet de dessin client, vous devez implémenter un convertisseur de texte personnalisé. Ce convertisseur de texte applique l’effet de dessin qui lui a été transmis par la méthode IDWriteTextLayout::D raw à l’exécution de glyphe en cours de dessin.

Constructeur

Le constructeur du convertisseur de texte personnalisé stocke l’objet ID2D1Factory qui sera utilisé pour créer des objets Direct2D et la cible de rendu Direct2D sur laquelle le texte sera dessiné.

CustomTextRenderer::CustomTextRenderer(
    ID2D1Factory* pD2DFactory, 
    ID2D1HwndRenderTarget* pRT
    )
:
cRefCount_(0), 
pD2DFactory_(pD2DFactory), 
pRT_(pRT)
{
    pD2DFactory_->AddRef();
    pRT_->AddRef();
}

Méthode DrawGlyphRun

Une exécution de glyphe est un ensemble de glyphes qui partagent le même format, y compris l’effet de dessin du client. La méthode DrawGlyphRun prend en charge le rendu du texte pour une exécution de glyphe spécifiée.

Tout d’abord, créez un ID2D1PathGeometry et un ID2D1GeometrySink, puis récupérez le plan d’exécution du glyphe à l’aide de IDWriteFontFace::GetGlyphRunOutline. Transformez ensuite l’origine de la géométrie à l’aide de la méthode DIRECT2DID2D1Factory::CreateTransformedGeometry , comme indiqué dans le code suivant.

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

Ensuite, déclarez un objet pinceau solide Direct2D .

ID2D1SolidColorBrush* pBrush = NULL;

Si le paramètre clientDrawingEffect n’est pas NULL, interrogez l’objet pour l’interface ColorDrawingEffect . Cela fonctionnera, car vous allez définir cette classe comme effet de dessin client sur les plages de texte de l’objet de disposition de texte.

Une fois que vous avez un pointeur vers l’interface ColorDrawingEffect , vous pouvez récupérer la valeur D2D1_COLOR_F qu’elle stocke à l’aide de la méthode GetColor . Ensuite, utilisez le D2D1_COLOR_F pour créer un ID2D1SolidColorBrush dans cette couleur.

Si le paramètre clientDrawingEffect a la valeur NULL, il suffit de créer un ID2D1SolidColorBrush noir.

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

Enfin, dessinez la géométrie du contour et remplissez-la à l’aide du pinceau de couleur unie que vous venez de créer.

if (SUCCEEDED(hr))
{
    // Draw the outline of the glyph run
    pRT_->DrawGeometry(
        pTransformedGeometry,
        pBrush
        );

    // Fill in the glyph run
    pRT_->FillGeometry(
        pTransformedGeometry,
        pBrush
        );
}

Destructeur

N’oubliez pas de libérer la fabrique Direct2D et la cible de rendu dans le destructeur.

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

Étape 4 : Créer le convertisseur de texte

Dans les ressources CreateDeviceDependent, créez l’objet de convertisseur de texte personnalisé. Il dépend de l’appareil, car il utilise ID2D1RenderTarget, qui est lui-même dépendant de l’appareil.

// Create the text renderer
pTextRenderer_ = new CustomTextRenderer(
                        pD2DFactory_,
                        pRT_
                        );

Étape 5 : instancier les objets d’effet de dessin de couleur

Instancier les objets ColorDrawingEffect en rouge, vert et bleu.

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

Étape 6 : Définir l’effet de dessin pour des plages de texte spécifiques

Définissez l’effet de dessin pour des plages spécifiques de texte à l’aide de la méthode IDWriteTextLayou::SetDrawingEffect et d’un 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);
    }
}

Étape 7 : Dessiner la disposition du texte à l’aide du renderer personnalisé

Vous devez appeler la méthode IDWriteTextLayout::D raw plutôt que les méthodes 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
        );

Étape 8 : Nettoyer

Dans le destructeur DemoApp, relâchez le convertisseur de texte personnalisé.

SafeRelease(&pTextRenderer_);

Après cela, ajoutez du code pour libérer les classes d’effet de dessin client.

SafeRelease(&redDrawingEffect_);
SafeRelease(&blueDrawingEffect_);
SafeRelease(&greenDrawingEffect_);