텍스트 레이아웃에 클라이언트 그리기 효과를 추가하는 방법

IDWriteTextLayout 인터페이스 및 사용자 지정 텍스트 렌더러를 사용하여 텍스트를 표시하는 DirectWrite 애플리케이션에 클라이언트 그리기 효과를 추가하는 방법에 대한 간단한 자습서를 제공합니다.

이 자습서의 최종 곱은 다음 스크린샷과 같이 각각에 다른 색 그리기 효과가 있는 텍스트 범위가 있는 텍스트를 표시하는 애플리케이션입니다.

screen shot of

참고

이 자습서는 색 텍스트를 그리는 간단한 방법의 예가 아니라 사용자 지정 클라이언트 그리기 효과를 만드는 방법의 간단한 예제입니다. 자세한 내용은 IDWriteTextLayout::SetDrawingEffect 참조 페이지를 참조하세요.

 

이 자습서에는 다음 부분이 포함되어 있습니다.

1단계: 텍스트 레이아웃 만들기

시작하려면 IDWriteTextLayout 개체가 있는 애플리케이션이 필요합니다. 텍스트 레이아웃이 있는 텍스트를 표시하는 애플리케이션이 이미 있거나 사용자 지정 DrawingEffect 예제 코드를 사용하는 경우 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. 마지막으로 소멸자에서 텍스트 레이아웃을 해제해야 합니다.

    SafeRelease(&pTextLayout_);
    

2단계: 사용자 지정 그리기 효과 클래스 구현

IUnknown에서 상속된 메서드 외에 사용자 지정 클라이언트 그리기 효과 인터페이스에는 구현해야 하는 내용에 대한 요구 사항이 없습니다. 이 경우 ColorDrawingEffect 클래스는 단순히 D2D1_COLOR_F 값을 보유하며 이 값을 가져와서 설정하는 메서드와 처음에 색을 설정할 수 있는 생성자를 선언합니다.

IDWriteTextLayout 개체의 텍스트 범위에 클라이언트 그리기 효과가 적용된 후 그리기 효과는 렌더링할 문자 모양 실행의 IDWriteTextRenderer::D rawGlyphRun 메서드에 전달됩니다. 그리기 효과의 메서드는 텍스트 렌더러에서 사용할 수 있습니다.

클라이언트 그리기 효과는 이 예제보다 더 많은 정보를 전달하고 문자 모양을 변경하고 그리기에 사용할 개체를 만드는 메서드를 제공하는 등 원하는 만큼 복잡할 수 있습니다.

3단계: 사용자 지정 텍스트 렌더러 클래스 구현

클라이언트 그리기 효과를 활용하려면 사용자 지정 텍스트 렌더러를 구현해야 합니다. 이 텍스트 렌더러는 IDWriteTextLayout::D raw 메서드에 전달된 그리기 효과를 그리는 문자 모양 실행에 적용합니다.

생성자

사용자 지정 텍스트 렌더러의 생성자는 Direct2D 개체를 만드는 데 사용할 ID2D1Factory 개체 와 텍스트를 그릴 Direct2D 렌더링 대상을 저장합니다.

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

DrawGlyphRun 메서드

문자 모양 실행은 클라이언트 그리기 효과를 포함하여 동일한 형식을 공유하는 문자 모양 집합입니다. DrawGlyphRun 메서드는 지정된 문자 모양 실행에 대한 텍스트 렌더링을 처리합니다.

먼저 ID2D1PathGeometryID2D1GeometrySink를 만든 다음 IDWriteFontFace::GetGlyphRunOutline을 사용하여 문자 모양 실행 개요를 검색합니다. 그런 다음, 다음 코드와 같이 Direct2DID2D1Factory::CreateTransformedGeometry 메서드를 사용하여 기하 도형의 원본을 변환합니다.

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

다음으로 Direct2D 단색 브러시 개체를 선언합니다.

ID2D1SolidColorBrush* pBrush = NULL;

clientDrawingEffect 매개 변수가 NULL이 아닌 경우 ColorDrawingEffect 인터페이스에 대한 개체를 쿼리합니다. 이 클래스는 텍스트 레이아웃 개체의 텍스트 범위에 대한 클라이언트 그리기 효과로 설정하기 때문에 작동합니다.

ColorDrawingEffect 인터페이스에 대한 포인터가 있으면 GetColor 메서드를 사용하여 저장하는 D2D1_COLOR_F 값을 검색할 수 있습니다. 그런 다음 D2D1_COLOR_F 사용하여 해당 색으로 ID2D1SolidColorBrush 를 만듭니다.

clientDrawingEffect 매개 변수가 NULL이면 검은색 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);
    }
}

마지막으로 윤곽선 기하 도형을 그리고 방금 만든 단색 브러시를 사용하여 채웁니다.

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

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

소멸자

Direct2D 팩터리를 해제하고 소멸자에서 대상을 렌더링해야 합니다.

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

4단계: 텍스트 렌더러 만들기

CreateDeviceDependent 리소스에서 사용자 지정 텍스트 렌더러 개체를 만듭니다. 디바이스 자체에 종속된 ID2D1RenderTarget을 사용하기 때문에 디바이스에 종속됩니다.

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

5단계: 색 그리기 효과 개체 인스턴스화

ColorDrawingEffect 개체를 빨간색, 녹색 및 파란색으로 인스턴스화합니다.

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

6단계: 특정 텍스트 범위에 대한 그리기 효과 설정

IDWriteTextLayou::SetDrawingEffect 메서드 및 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);
    }
}

7단계: 사용자 지정 렌더러를 사용하여 텍스트 레이아웃 그리기

ID2D1RenderTarget::D rawText 또는 ID2D1RenderTarget::D rawTextLayout 메서드가 아닌 IDWriteTextLayout::D raw 메서드를 호출해야 합니다.

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

8단계: 정리

DemoApp 소멸자에서 사용자 지정 텍스트 렌더러를 해제합니다.

SafeRelease(&pTextRenderer_);

그런 다음 코드를 추가하여 클라이언트 그리기 효과 클래스를 해제합니다.

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