Come aggiungere oggetti inline a un layout di testo

Viene fornita una breve esercitazione sull'aggiunta di oggetti inline a un'applicazione DirectWrite che visualizza il testo usando l'interfaccia IDWriteTextLayout.

Il prodotto finale di questa esercitazione è un'applicazione che visualizza testo con un'immagine inline incorporata, come illustrato nella schermata seguente.

screenshot di

Questa esercitazione contiene le parti seguenti:

Passaggio 1: Creare un layout di testo.

Per iniziare, è necessaria un'applicazione con un oggetto IDWriteTextLayout . Se è già disponibile un'applicazione che visualizza il testo con un layout di testo, passare al passaggio 2.

Per aggiungere un layout di testo, è necessario eseguire le operazioni seguenti:

  1. Dichiarare un puntatore a un'interfaccia IDWriteTextLayout come membro della classe .

    IDWriteTextLayout* pTextLayout_;
    
    
  2. Alla fine del metodo CreateDeviceIndependentResources creare un oggetto interfaccia IDWriteTextLayout chiamando il metodo 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. È quindi necessario modificare la chiamata al metodo ID2D1RenderTarget::D rawText in ID2D1RenderTarget::D rawTextLayout, come illustrato nel codice seguente.

    pRT_->DrawTextLayout(
        origin,
        pTextLayout_,
        pBlackBrush_
        );
    
    

Passaggio 2: Definire una classe derivata dall'interfaccia IDWriteInlineObject.

Il supporto per gli oggetti inline in DirectWrite viene fornito dall'interfaccia IDWriteInlineObject. Per usare oggetti inline, è necessario implementare questa interfaccia. Gestisce il disegno dell'oggetto inline, oltre a fornire metriche e altre informazioni al renderer.

Creare un nuovo file di intestazione e dichiarare un'interfaccia denominata InlineImage, derivata da IDWriteInlineObject.

Oltre a QueryInterface, AddRef e Release ereditato da IUnknown, questa classe deve avere i metodi seguenti:

Passaggio 3: Implementare la classe oggetto Inline.

Creare un nuovo file C++, denominato InlineImage.cpp, per l'implementazione della classe. Oltre al metodo LoadBitmapFromFile e ai metodi ereditati dall'interfaccia IUnknown, la classe InlineImage è costituita dai metodi seguenti.

Costruttore.

InlineImage::InlineImage(
    ID2D1RenderTarget *pRenderTarget, 
    IWICImagingFactory *pIWICFactory,
    PCWSTR uri
    )
{
    // Save the render target for later.
    pRT_ = pRenderTarget;

    pRT_->AddRef();

    // Load the bitmap from a file.
    LoadBitmapFromFile(
        pRenderTarget,
        pIWICFactory,
        uri,
        &pBitmap_
        );
}

Il primo argomento del costruttore è l'ID2D1RenderTarget su cui verrà eseguito il rendering dell'immagine inline. Questo viene archiviato per usarlo in un secondo momento durante il disegno.

La destinazione di rendering, IWICImagingFactory e l'URI del nome file vengono tutti passati al metodo LoadBitmapFromFile che carica la bitmap e archivia le dimensioni della bitmap (larghezza e altezza) nella variabile membro rect_.

Metodo Draw.

Il metodo Draw è un callback chiamato dall'oggetto IDWriteTextRenderer quando è necessario disegnare l'oggetto inline. Il layout di testo non chiama direttamente questo metodo.

HRESULT STDMETHODCALLTYPE InlineImage::Draw(
    __maybenull void* clientDrawingContext,
    IDWriteTextRenderer* renderer,
    FLOAT originX,
    FLOAT originY,
    BOOL isSideways,
    BOOL isRightToLeft,
    IUnknown* clientDrawingEffect
    )
{
    float height    = rect_.bottom - rect_.top;
    float width     = rect_.right  - rect_.left;
    D2D1_RECT_F destRect  = {originX, originY, originX + width, originY + height};

    pRT_->DrawBitmap(pBitmap_, destRect);

    return S_OK;
}

In questo caso, il disegno della bitmap viene eseguito usando il metodo ID2D1RenderTarget::D rawBitmap ; tuttavia, è possibile utilizzare qualsiasi metodo per il disegno.

Metodo GetMetrics.

HRESULT STDMETHODCALLTYPE InlineImage::GetMetrics(
    __out DWRITE_INLINE_OBJECT_METRICS* metrics
    )
{
    DWRITE_INLINE_OBJECT_METRICS inlineMetrics = {};
    inlineMetrics.width     = rect_.right  - rect_.left;
    inlineMetrics.height    = rect_.bottom - rect_.top;
    inlineMetrics.baseline  = baseline_;
    *metrics = inlineMetrics;
    return S_OK;
}

Per il metodo GetMetrics , archiviare la larghezza, l'altezza e la linea di base in una struttura DWRITE_INLINE_OBJECT_METRICS . IDWriteTextLayout chiama questa funzione di callback per ottenere la misurazione dell'oggetto inline.

Metodo GetOverhangMetrics.

HRESULT STDMETHODCALLTYPE InlineImage::GetOverhangMetrics(
    __out DWRITE_OVERHANG_METRICS* overhangs
    )
{
    overhangs->left      = 0;
    overhangs->top       = 0;
    overhangs->right     = 0;
    overhangs->bottom    = 0;
    return S_OK;
}

In questo caso, non è necessario alcun overhang, quindi il metodo GetOverhangMetrics restituisce tutti gli zeri.

Metodo GetBreakConditions.

HRESULT STDMETHODCALLTYPE InlineImage::GetBreakConditions(
    __out DWRITE_BREAK_CONDITION* breakConditionBefore,
    __out DWRITE_BREAK_CONDITION* breakConditionAfter
    )
{
    *breakConditionBefore = DWRITE_BREAK_CONDITION_NEUTRAL;
    *breakConditionAfter  = DWRITE_BREAK_CONDITION_NEUTRAL;
    return S_OK;
}

Passaggio 4: Creare un'istanza della classe InlineImage e aggiungerla al layout di testo.

Infine, nel metodo CreateDeviceDependentResources creare un'istanza della classe InlineImage e aggiungerla al layout di testo. Poiché contiene un riferimento a ID2D1RenderTarget, ovvero una risorsa dipendente dal dispositivo e ID2D1Bitmap viene creato usando la destinazione di rendering, anche InlineImage è dipendente dal dispositivo e deve essere ricreato se la destinazione di rendering viene ricreata.

// Create an InlineObject.
pInlineImage_ = new InlineImage(pRT_, pWICFactory_, L"img1.jpg");

DWRITE_TEXT_RANGE textRange = {14, 1};

pTextLayout_->SetInlineObject(pInlineImage_, textRange);

Il metodo IDWriteTextLayout::SetInlineObject accetta una struttura dell'intervallo di testo. L'oggetto si applica all'intervallo specificato qui e qualsiasi testo nell'intervallo verrà eliminato. Se la lunghezza dell'intervallo di testo è 0, l'oggetto non verrà disegnato.

In questo esempio è presente un asterisco (*) come segnaposto nella posizione in cui verrà visualizzata l'immagine.

// The string to display.  The '*' character will be suppressed by our image.
wszText_ = L"Inline Object * Example";
cTextLength_ = wcslen(wszText_);

Poiché la classe InlineImage dipende da ID2D1RenderTarget, è necessario rilasciarla quando si rilascia la destinazione di rendering.

SafeRelease(&pInlineImage_);