Hinzufügen von Clientzeichnungseffekten zu einem Textlayout

Enthält ein kurzes Tutorial zum Hinzufügen von Clientzeichnungseffekten zu einer DirectWrite-Anwendung, die Text mithilfe der IDWriteTextLayout-Schnittstelle und eines benutzerdefinierten Textrenderers anzeigt.

Das Endprodukt dieses Tutorials ist eine Anwendung, die Text mit Textbereichen mit unterschiedlichen Farbzeichnungseffekten anzeigt, wie im folgenden Screenshot gezeigt.

Screenshot des Beispiels "Client drawing effect!" in verschiedenen Farben

Hinweis

Dieses Tutorial soll ein vereinfachtes Beispiel für das Erstellen benutzerdefinierter Clientzeichnungseffekte sein, nicht ein Beispiel für eine einfache Methode zum Zeichnen von Farbtext. Weitere Informationen finden Sie auf der Referenzseite IDWriteTextLayout::SetDrawingEffect.

Dieses Tutorial enthält die folgenden Teile:

Schritt 1: Erstellen eines Textlayouts

Zunächst benötigen Sie eine Anwendung mit einem IDWriteTextLayout-Objekt. Wenn Sie bereits über eine Anwendung verfügen, die Text mit einem Textlayout anzeigt, oder wenn Sie den Beispielcode "Custom DrawingEffect" verwenden, fahren Sie mit Schritt 2 fort.

Gehen Sie wie folgt vor, um ein Textlayout hinzuzufügen:

  1. Deklarieren Sie einen Zeiger auf eine IDWriteTextLayout-Schnittstelle als Member der -Klasse.

    IDWriteTextLayout* pTextLayout_;
    
    
  2. Erstellen Sie am Ende der CreateDeviceIndependentResources-Methode ein IDWriteTextLayout-Schnittstellenobjekt, indem Sie die CreateTextLayout-Methode aufrufen.

    // 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. Denken Sie abschließend daran, das Textlayout im Destruktor frei zu geben.

    SafeRelease(&pTextLayout_);
    

Schritt 2: Implementieren einer benutzerdefinierten Drawing Effect-Klasse

Abgesehen von den von IUnknown geerbten Methoden hat eine benutzerdefinierte Schnittstelle zum Zeichnen von Effekten für Clients keine Anforderungen an das, was sie implementieren muss. In diesem Fall enthält die ColorDrawingEffect-Klasse einfach einen D2D1 _ COLOR _ F-Wert und deklariert Methoden zum Erhalten und Festlegen dieses Werts sowie einen Konstruktor, der die Farbe anfänglich festlegen kann.

Nachdem ein Clientzeichnungseffekt auf einen Textbereich in einem IDWriteTextLayout-Objekt angewendet wurde, wird der Zeichnungseffekt an die IDWriteTextRenderer::D rawGlyphRun-Methode aller Glyphenläufe übergeben, die gerendert werden sollen. Die Methoden des Zeichnungseffekts sind dann für den Textrenderer verfügbar.

Ein Clientzeichnungseffekt kann so komplex sein, wie Sie ihn erstellen möchten. Er enthält mehr Informationen als in diesem Beispiel und stellt Methoden zum Ändern von Glyphen, zum Erstellen von Objekten für das Zeichnen und so weiter zur Verfügung.

Schritt 3: Implementieren einer benutzerdefinierten Textrendererklasse

Um einen Clientzeichnungseffekt nutzen zu können, müssen Sie einen benutzerdefinierten Textrenderer implementieren. Dieser Textrenderer verwendet den Zeichnungseffekt, der von der IDWriteTextLayout::D raw-Methode übergeben wird, auf die gezeichnete Glyphen-Ausführung.

Der Konstruktor

Der Konstruktor für den benutzerdefinierten Textrenderer speichert das ID2D1Factory-Objekt, das zum Erstellen von Direct2D-Objekten verwendet wird, und das Direct2D-Renderziel, auf das der Text gezeichnet wird.

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

Die DrawGlyphRun-Methode

Eine Glyphen-Ausführung ist ein Satz von Glyphen, die das gleiche Format haben, einschließlich des Clientzeichnungseffekts. Die DrawGlyphRun-Methode übernimmt das Textrendering für eine angegebene Glyphenlauf.

Erstellen Sie zunächst eine ID2D1PathGeometry und eine ID2D1GeometrySink,und rufen Sie dann die Ausführungsgliederung des Glyphen mithilfe von IDWriteFontFace::GetGlyphRunOutlineab. Transformieren Sie dann den Ursprung der Geometrie mithilfe der Direct2D ID2D1Factory::CreateTransformedGeometry-Methode, wie im folgenden Code gezeigt.

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

Deklarieren Sie als Nächstes ein Direct2D-Vollbildpinselobjekt.

ID2D1SolidColorBrush* pBrush = NULL;

Wenn der clientDrawingEffect-Parameter nicht NULL ist, fragen Sie das -Objekt für die ColorDrawingEffect-Schnittstelle ab. Dies funktioniert, da Sie diese Klasse als Clientzeichnungseffekt für Textbereiche des Textlayoutobjekts festlegen.

Sobald Sie über einen Zeiger auf die ColorDrawingEffect-Schnittstelle verfügen, können Sie den D2D1 _ COLOR _ F-Wert abrufen, den sie mit der GetColor-Methode speichert. Verwenden Sie dann D2D1 _ COLOR _ F, um einen ID2D1SolidColorBrush in dieser Farbe zu erstellen.

Wenn der clientDrawingEffect-Parameter NULL ist, erstellen Sie einfach einen schwarzen ID2D1SolidColorBrush.

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

Zeichnen Sie abschließend die Konturgeometrie, und füllen Sie sie mit dem gerade erstellten Volltonfarbpinsel aus.

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

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

Der Destruktor

Vergessen Sie nicht, die Direct2D-Factory frei zu geben und das Ziel im Destruktor zu rendern.

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

Schritt 4: Erstellen des Textrenderers

Erstellen Sie in den CreateDeviceDependent-Ressourcen das benutzerdefinierte Textrendererobjekt. Es ist geräteabhängig, da es id2D1RenderTarget verwendet, das selbst geräteabhängig ist.

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

Schritt 5: Instanziieren der Farbzeichnungseffekt-Objekte

Instanziieren Sie ColorDrawingEffect-Objekte in Rot, Grün und Blau.

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

Schritt 6: Festlegen des Zeichnungseffekts für bestimmte Textbereiche

Legen Sie den Zeichnungseffekt für bestimmte Textbereiche fest, indem Sie die IDWriteTextLayou::SetDrawingEffect-Methode und eine DWRITE _ TEXT _ RANGE-Struktur verwenden.

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

Schritt 7: Zeichnen des Textlayouts mit dem benutzerdefinierten Renderer

Sie müssen anstelle der Methoden ID2D1RenderTarget::D rawText oder ID2D1RenderTarget::D rawTextLayout die IDWriteTextLayout::D raw-Methode aufrufen.

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

Schritt 8: Bereinigt

Geben Sie im DemoApp-Destruktor den benutzerdefinierten Textrenderer frei.

SafeRelease(&pTextRenderer_);

Fügen Sie anschließend Code hinzu, um die Zeichnungseffektklassen des Clients frei zu geben.

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