直接用 rendertarget 的 DrawText 方法显示文字的话无法为文字增加描边,用路径对象配合字体字形进行显示,并在内部用画刷填充的话可以达到描边的效果。

首先初始化的时候创建字体,要显示中文的话需要选择支持中文的字体,否则会因为找不到字模而显示为”囗”,这里选择了黑体作为样例。
渲染的时候根据要显示的内容获取对应字模,并以此创建路径对象,创建完成后将其绘制到合适的位置,同时用指定颜色填充内部即可。

附上代码:

void Init()
{
    ...

    hr = DWriteCreateFactory(
        DWRITE_FACTORY_TYPE_SHARED,
        __uuidof(m_pDWriteFactory),
        reinterpret_cast<IUnknown **>(&m_pDWriteFactory)
    );
    IDWriteFontFile *pFontFiles{ nullptr };
    hr = m_pDWriteFactory->CreateFontFileReference(
        L"simhei.ttf", NULL, &pFontFiles
    );
    assert(SUCCEEDED(hr));
    IDWriteFontFile *fontFileArray[] = { pFontFiles };
    hr = m_pDWriteFactory->CreateFontFace(
        DWRITE_FONT_FACE_TYPE_TRUETYPE,
        ARRAYSIZE(fontFileArray), // file count
        fontFileArray,
        0,
        DWRITE_FONT_SIMULATIONS_NONE,
        &m_pFontFace
    );
    assert(SUCCEEDED(hr));
    SafeRelease(&pFontFiles);

    ...
}

void DrawText(
    const wchar_t * string,
    unsigned int stringLength,
    IDWriteTextFormat * textFormat,
    const Rectangle & layoutRect
)
{
    UINT* pCodePoints = new UINT[stringLength];
    UINT16* pGlyphIndices = new UINT16[stringLength];
    ZeroMemory(pCodePoints, sizeof(UINT) * stringLength);
    ZeroMemory(pGlyphIndices, sizeof(UINT16) * stringLength);
    for (int i = 0; i < stringLength; ++i)
    {
        pCodePoints[i] = string[i];
    }
    HRESULT hr;
    // 确认字形是否存在
    hr = m_pFontFace->GetGlyphIndicesW(pCodePoints, stringLength, pGlyphIndices);
    // 将文字描边储存至路径几何对象, 18.0f 为字号大小
    hr = m_pDirect2dFactory->CreatePathGeometry(&m_pPathGeometry);
    hr = m_pPathGeometry->Open(&m_pGeometrySink);
    hr = m_pFontFace->GetGlyphRunOutline(18.0f, pGlyphIndices,
        NULL, NULL, stringLength, FALSE, FALSE, m_pGeometrySink
    );
    hr = m_pGeometrySink->Close();

    IDWriteTextLayout* pTextLayout = NULL;
    // 获取文本尺寸 
    hr = m_pDWriteFactory->CreateTextLayout(string, stringLength, textFormat, 0.0f, 0.0f, &pTextLayout);
    DWRITE_TEXT_METRICS textMetrics;
    hr = pTextLayout->GetMetrics(&textMetrics);
    SafeRelease(&pTextLayout);

    D2D1_MATRIX_3X2_F old;
    D2D1_RECT_F bounds;
    m_pRenderTarget->GetTransform(&old);
    m_pPathGeometry->GetBounds(old, &bounds);
    m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Translation(
        layoutRect.GetLeft() + (layoutRect.GetWidth() - bounds.right + bounds.left + textMetrics.left) / 2.0f,
        layoutRect.GetBottom() - (layoutRect.GetHeight() - bounds.bottom + bounds.top) / 2.0f));
    m_pRenderTarget->DrawGeometry(m_pPathGeometry, m_pBlackBrush, 1.0f);
    m_pRenderTarget->FillGeometry(m_pPathGeometry, m_pWhiteBrush);
    m_pRenderTarget->SetTransform(old);

    SafeDeleteArray(&pCodePoints);
    SafeDeleteArray(&pGlyphIndices);
    SafeRelease(&m_pGeometrySink);
    SafeRelease(&m_pPathGeometry);
}