如何将内联对象添加到文本布局

提供有关将内联对象添加到使用 IDWriteTextLayout 接口显示文本的DirectWrite应用程序的简短教程。

本教程的最终产品是一个应用程序,该应用程序显示嵌入了内联图像的文本,如以下屏幕截图所示。

带有嵌入图像的“内联对象示例”的屏幕截图

本教程包含以下部分:

步骤 1:创建文本布局。

首先,需要一个具有 IDWriteTextLayout 对象的应用程序。 如果已有一个使用文本布局显示文本的应用程序,请跳到步骤 2。

若要添加文本布局,必须执行以下操作:

  1. 将指向 IDWriteTextLayout 接口的指针声明为 类的成员。

    IDWriteTextLayout* pTextLayout_;
    
    
  2. 在 CreateDeviceIndependentResources 方法的末尾,通过调用 CreateTextLayout 方法创建 IDWriteTextLayout 接口对象。

    // 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. 然后,必须将 对 ID2D1RenderTarget::D rawText 方法的调用更改为 ID2D1RenderTarget::D rawTextLayout,如以下代码所示。

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

步骤 2:定义派生自 IDWriteInlineObject 接口的类。

IDWriteInlineObject 接口支持 DirectWrite 中的内联对象。 若要使用内联对象,必须实现此接口。 它处理内联对象的绘制,以及向呈现器提供指标和其他信息。

创建一个新的头文件并声明一个名为 InlineImage 的接口,该接口派生自 IDWriteInlineObject

除了从 IUnknown 继承的 QueryInterface、AddRef 和 Release 外,此类还必须具有以下方法:

步骤 3:实现 Inline 对象类。

为类实现创建名为 InlineImage.cpp 的新 C++ 文件。 除了 LoadBitmapFromFile 方法和从 IUnknown 接口继承的方法外,InlineImage 类由以下方法组成。

构造函数。

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

构造函数的第一个参数是内联图像将呈现到的 ID2D1RenderTarget。 存储此存储以供以后在绘图时使用。

呈现器目标 IWICImagingFactory 和文件名 uri 全部传递到 LoadBitmapFromFile 方法,该方法加载位图并将位图大小 (宽度和) 高度存储在 rect_ 成员变量中。

Draw 方法。

Draw 方法是在需要绘制内联对象时由 IDWriteTextRenderer 对象调用的回调。 文本布局不直接调用此方法。

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

在这种情况下,使用 ID2D1RenderTarget::D rawBitmap 方法绘制位图;但是,可以使用任何绘制方法。

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

对于 GetMetrics 方法,将宽度、高度和基线存储在 DWRITE_INLINE_OBJECT_METRICS 结构中。 IDWriteTextLayout 调用此回调函数以获取内联对象的度量值。

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

在这种情况下,不需要过度悬停,因此 GetOverhangMetrics 方法返回所有零。

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

步骤 4:创建 InlineImage 类的实例并将其添加到文本布局。

最后,在 CreateDeviceDependentResources 方法中,创建 InlineImage 类的实例并将其添加到文本布局中。 由于它保存对 ID2D1RenderTarget(与设备相关的资源)的引用,并且 ID2D1Bitmap 是使用呈现器目标创建的,因此 InlineImage 也依赖于设备,并且必须在重新创建呈现器目标时重新创建。

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

DWRITE_TEXT_RANGE textRange = {14, 1};

pTextLayout_->SetInlineObject(pInlineImage_, textRange);

IDWriteTextLayout::SetInlineObject 方法采用文本范围结构。 对象应用于此处指定的区域,并且将禁止显示该区域中的任何文本。 如果文本范围的长度为 0,则不会绘制对象。

在此示例中,有一个星号 (*) 作为图像显示位置的占位符。

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

由于 InlineImage 类依赖于 ID2D1RenderTarget,因此必须在释放呈现器目标时释放它。

SafeRelease(&pInlineImage_);