65.9K
CodeProject 正在变化。 阅读更多。
Home

Direct2D 教程 第 5 部分:文本显示和字体枚举

starIconstarIconstarIconstarIconstarIcon

5.00/5 (7投票s)

2023 年 1 月 14 日

CPOL

4分钟阅读

viewsIcon

13114

downloadIcon

428

DirectWrite 文本显示和字体枚举简介

目录

示例代码托管在 Github 上。

demo screenshot

引言

如今的应用程序必须支持高质量的文本渲染、与分辨率无关的轮廓字体以及完整的 Unicode 文本和布局支持。 DirectWrite (一个 DirectX API) 提供了这些功能及更多功能。 在本文中,您将学习如何使用 DirectWrite 显示文本、测量文本以及枚举已安装的字体。 演示代码是我 MandyFrenzy 照片幻灯片应用 中的一个结束字幕对话框,用户可以在幻灯片中添加结束字幕(例如导演或制片人)。 看到自己被列为幻灯片视频的导演/制片人是一件很有趣的事情。 我已将 MandyFrenzy 应用添加到文章下载中,以便您可以使用它。

文本显示

在 Direct2D 中显示文本之前,必须在 IDWriteTextFormat 对象中指定参数,例如字体名称、字体大小、斜体和粗体。 GetTextFormat() 基于这些参数创建一个 IDWriteTextFormat 对象。

ComPtr<IDWriteTextFormat> textFormat = GetTextFormat(m_FontFamily, 
    m_FontSize * m_DPIScale, m_Italic, m_Bold, m_Centerize, m_Centerize);

DrawText(m_DCTarget.Get(), textFormat.Get(), m_Text);

DrawText() 从渲染目标的大小创建一个 rect,并使用 texttextFormat 调用 DrawTextW()DrawTextW() 有一个重载函数,它接受一个点(而不是矩形)作为显示文本的起始位置。

void TextDisplayStatic::DrawText(ID2D1RenderTarget* target, 
        IDWriteTextFormat* textFormat, const CString& text)
{
    auto size = target->GetSize();

    auto rect = RectF(0.0f, 0.0f, size.width, size.height);
    target->DrawTextW(text, text.GetLength(), textFormat, rect, m_BlackBrush.Get());
}

正如我之前提到的,在 DrawText() 之前,必须调用 GetTextFormat() 来创建 TextFormat

ComPtr<IDWriteTextFormat> GetTextFormat(const CString& fontname, float fontSize,
    bool italic, bool bold, bool centerHorizontal, bool centerVertical)
{
    ComPtr<IDWriteTextFormat> textFormat;

    DWRITE_FONT_STYLE fontStyle = italic ? 
                      DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
					  
    DWRITE_FONT_WEIGHT fontWeight = bold ? 
                       DWRITE_FONT_WEIGHT_BOLD : DWRITE_FONT_WEIGHT_NORMAL;

    HR(FactorySingleton::GetDWriteFactory()->CreateTextFormat((LPCTSTR)fontname,
        nullptr, fontWeight, fontStyle,
        DWRITE_FONT_STRETCH_NORMAL, fontSize, L"",
        textFormat.GetAddressOf()));

    if (centerHorizontal)
        textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);

    if (centerVertical)
        textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);

    return textFormat;
}

对于斜体文本,我们可以在 DWRITE_FONT_STYLE_ITALICDWRITE_FONT_STYLE_OBLIQUE 之间进行选择。 这两种样式有什么区别? 嗯,倾斜只是采用普通字体,并通过应用倾斜变换来倾斜它,而斜体是一种专门为倾斜而设计的字体。 俗话说,一张图胜过千言万语,请参考下面的图片(由 Pariah Burke 的 Advanced Typography Pluralsight 课程提供)。 左侧的文本显示了普通字体和斜体字体的区别,而右侧显示了普通样式和倾斜样式的区别。 如您所见,斜体字符 f、i、e、a 是不同的,而倾斜字符只是倾斜的版本。

italics_vs_oblique

文本测量

有时,在显示文本之前,需要测量文本的长度和高度。 例如,在按钮中显示文本时,我们希望调整按钮尺寸以适应文本。 如果给定的 size 的宽度不足以将文本容纳在一行中,则高度将被调整以将文本分成下一行或多行。 请注意,此 GetTextSize() 不在源代码下载中。 您可以从下面的代码片段中复制 GetTextSize()

HRESULT GetTextSize
(const WCHAR* text, IDWriteTextFormat* pTextFormat, D2D1_SIZE_F& size)
{
    HRESULT hr = S_OK;
    ComPtr<IDWriteTextLayout> pTextLayout;
    float floatDim = static_cast<float>(m_Dim);
    // Create a text layout 
    hr = FactorySingleton::GetDWriteFactory()->CreateTextLayout(text, 
        static_cast<UINT32>(wcslen(text)), 
        pTextFormat, floatDim, floatDim, pTextLayout.GetAddressOf());

    if (SUCCEEDED(hr))
    {
        // Get text size  
        DWRITE_TEXT_METRICS textMetrics;
        hr = pTextLayout->GetMetrics(&textMetrics);
        size = D2D1::SizeF(ceil(textMetrics.widthIncludingTrailingWhitespace), 
           ceil(textMetrics.height));
    }
    return hr;
}

字体枚举

每当我们使用特定字体显示文本时,我们必须确保该字体存在于用户系统中。 这可以通过枚举系统上的字体来实现。 这些是字体枚举所需的结构。 为了简洁起见,没有显示结构的构造函数、设置器和获取器。

struct FontSubType
{
private:
    std::wstring m_SubName;
    DWRITE_FONT_STRETCH m_Stretch;
    DWRITE_FONT_STYLE m_Style;
    DWRITE_FONT_WEIGHT m_Weight;
};

struct FontInfo
{
private:
    std::wstring m_OriginalName;
    std::wstring m_LocalizedName;
    std::vector<FontSubType> m_SubTypes;
    UINT32 m_StartY;
    UINT32 m_Height;
};

EnumFont() 会为您处理字体枚举。

void EnumFont(std::vector< std::shared_ptr<FontInfo> >& vecFont)
{
    if (vecFont.empty() == false)
        return;

    ComPtr<IDWriteFontCollection> fontCollection;
    HR(FactorySingleton::GetDWriteFactory()->GetSystemFontCollection(
        fontCollection.GetAddressOf(),
        TRUE
    ));

    UINT32 familyCount = fontCollection->GetFontFamilyCount();
    for (UINT32 i = 0; i < familyCount; ++i)
    {
        ComPtr<IDWriteFontFamily> fontFamily;
        HR(fontCollection->GetFontFamily(i, fontFamily.GetAddressOf()));
        if (!fontFamily)
            continue;

        ComPtr < IDWriteLocalizedStrings> names;
        HR(fontFamily->GetFamilyNames(names.GetAddressOf()));
        WCHAR wname[100];
        UINT32 localizedCount = names->GetCount();

        std::shared_ptr<FontInfo> info = std::make_shared<FontInfo>();

        HR(names->GetString(localizedCount - 1, wname, _countof(wname)));
        info->LocalizedName(wname);
        HR(names->GetString(0, wname, _countof(wname)));
        info->OriginalName(wname);

        UINT32 fontCount = fontFamily->GetFontCount();
        std::vector<FontSubType> vecSubNames;
        vecSubNames.reserve(fontCount);
        for (UINT32 j = 0; j < fontCount; ++j)
        {
            ComPtr<IDWriteFont> font;
            HR(fontFamily->GetFont(j, font.GetAddressOf()));
            if (!font)
                continue;

            ComPtr < IDWriteLocalizedStrings> faceNames;
            font->GetFaceNames(faceNames.GetAddressOf());

            WCHAR wface[100];
            HR(faceNames->GetString(0, wface, _countof(wface)));

            vecSubNames.push_back(FontSubType(wface, font->GetStretch(), 
                                  font->GetStyle(), font->GetWeight()));
        }
        info->SubTypes(std::move(vecSubNames));
        vecFont.push_back(info);
    }
    
    std::sort(vecFont.begin(), vecFont.end(),
        [](const std::shared_ptr<FontInfo>& a, const std::shared_ptr<FontInfo>& b) 
        {return a->OriginalName() < b->OriginalName(); });
}

所选字体及其子类型如下所示。 子类型处理字体的斜体和粗体类型。 子类型对我来说没有用,因为我不清楚某些子类型的名称。 并且 Microsoft Office 不会公开其用户可以从中选择的字体子类型。

font_subtype

font_subtype_arial

不要混合和匹配 DirectWrite 和 GDI+ 字体枚举

如果您使用 DirectWrite 字体枚举,则应使用 DirectWrite 显示文本,而不是 GDI+。 为什么? 你可能会问。 一个很好的理由是,如果您选择从 DirectWrite 字体枚举返回的 Cooper 字体,GDI+ 无法显示该字体,而 DirectWrite 却没有问题。 GDI+ 字体枚举 返回它可以显示的 Cooper Black 字体。 严格来说,Cooper BlackCooper 字体族的粗体子类型。 这是 Microsoft 在两种不同的图形技术下对其字体类型进行分类的怪癖。

历史

  • 2023 年 3 月 11 日:在 文本测量 部分添加了有关 GetTextSize()size 参数宽度的更多信息。
  • 2023 年 1 月 14 日:首次发布

系列文章

© . All rights reserved.