Поделиться через


Добавление встроенных объектов в текстовый макет

Краткое руководство по добавлению встроенных объектов в приложение DirectWrite, которое отображает текст с помощью интерфейса IDWriteTextLayout.

Конечным продуктом этого руководства является приложение, которое отображает текст со встроенным изображением, как показано на следующем снимке экрана.

Снимок экрана с примером встроенного объекта с внедренным изображением

Это руководство содержит следующие части:

Шаг 1. Создание макета текста.

Для начала потребуется приложение с объектом IDWriteTextLayout . Если у вас уже есть приложение, отображающее текст с макетом текста, перейдите к шагу 2.

Чтобы добавить макет текста, необходимо выполнить следующие действия.

  1. Объявите указатель на интерфейс IDWriteTextLayout в качестве члена класса .

    IDWriteTextLayout* pTextLayout_;
    
    
  2. В конце метода CreateDeviceIndependentResources создайте объект интерфейса IDWriteTextLayout , вызвав метод 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. Затем необходимо изменить вызов метода ID2D1RenderTarget::D rawText на ID2D1RenderTarget::D rawTextLayout, как показано в следующем коде.

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

Шаг 2. Определите класс, производный от интерфейса IDWriteInlineObject.

Поддержка встроенных объектов в DirectWrite обеспечивается интерфейсом IDWriteInlineObject. Чтобы использовать встроенные объекты, необходимо реализовать этот интерфейс. Он обрабатывает рисование встроенного объекта, а также предоставляет отрисовщику метрики и другие сведения.

Создайте новый файл заголовка и объявите интерфейс InlineImage, производный от IDWriteInlineObject.

Помимо QueryInterface, AddRef и Release, унаследованных от IUnknown, этот класс должен иметь следующие методы:

Шаг 3. Реализация класса встроенных объектов.

Создайте новый файл C++ с именем InlineImage.cpp для реализации класса. Помимо метода 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_);