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





5.00/5 (7投票s)
DirectWrite 文本显示和字体枚举简介
目录
示例代码托管在 Github 上。
引言
如今的应用程序必须支持高质量的文本渲染、与分辨率无关的轮廓字体以及完整的 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
,并使用 text
和 textFormat
调用 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_ITALIC
或 DWRITE_FONT_STYLE_OBLIQUE
之间进行选择。 这两种样式有什么区别? 嗯,倾斜只是采用普通字体,并通过应用倾斜变换来倾斜它,而斜体是一种专门为倾斜而设计的字体。 俗话说,一张图胜过千言万语,请参考下面的图片(由 Pariah Burke 的 Advanced Typography Pluralsight 课程提供)。 左侧的文本显示了普通字体和斜体字体的区别,而右侧显示了普通样式和倾斜样式的区别。 如您所见,斜体字符 f、i、e、a 是不同的,而倾斜字符只是倾斜的版本。
文本测量
有时,在显示文本之前,需要测量文本的长度和高度。 例如,在按钮中显示文本时,我们希望调整按钮尺寸以适应文本。 如果给定的 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 不会公开其用户可以从中选择的字体子类型。
不要混合和匹配 DirectWrite 和 GDI+ 字体枚举
如果您使用 DirectWrite 字体枚举,则应使用 DirectWrite 显示文本,而不是 GDI+。 为什么? 你可能会问。 一个很好的理由是,如果您选择从 DirectWrite 字体枚举返回的 Cooper
字体,GDI+ 无法显示该字体,而 DirectWrite 却没有问题。 GDI+ 字体枚举 返回它可以显示的 Cooper Black
字体。 严格来说,Cooper Black
是 Cooper
字体族的粗体子类型。 这是 Microsoft 在两种不同的图形技术下对其字体类型进行分类的怪癖。
历史
- 2023 年 3 月 11 日:在 文本测量 部分添加了有关
GetTextSize()
的size
参数宽度的更多信息。 - 2023 年 1 月 14 日:首次发布