在现代应用程序中,文本渲染的质量至关重要。需要支持高分辨率的字体、完整的Unicode文本和布局支持。DirectWrite是DirectX API的一部分,它提供了这些功能以及更多。本文将指导如何使用DirectWrite来显示文本、测量文本以及枚举已安装的字体。示例代码是一个来自MandyFrenzy照片幻灯片应用程序的结束字幕对话框,用户可以在幻灯片结束时添加结束字幕(例如导演或制片人)。看到自己在幻灯片视频结束时被列为导演/制片人是很有趣的事情。已经将MandyFrenzy应用程序添加到文章下载中,以便可以体验它。
在Direct2D中显示文本之前,必须在IDWriteTextFormat对象中指定参数,例如字体名称、字体大小、斜体和粗体。GetTextFormat()函数基于这些参数创建一个IDWriteTextFormat对象。
C++
ComPtr 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,其大小基于渲染目标,并调用DrawTextW()函数,传入文本和textFormat。还有一个DrawTextW()的重载函数,它接受一个点而不是矩形作为显示文本的起始位置。
C++
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。
C++
ComPtr GetTextFormat(
const CString& fontname,
float fontSize,
bool italic,
bool bold,
bool centerHorizontal,
bool centerVertical)
{
ComPtr 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的高级排版Pluralsight课程)。左侧的文本显示了普通和斜体字体之间的区别,右侧显示了普通和斜体风格之间的区别。如所见,斜体字符f,i,e,a是不同的,而斜体字符只是倾斜的版本。
有时,在显示文本之前,需要测量文本的长度和高度。例如,当在按钮中显示文本时,希望调整按钮的尺寸以容纳文本。如果给定的size宽度不足以容纳单行文本,高度将被调整以将文本换行以适应下一行或多行。请注意,GetTextSize()不在源代码下载中。可以从下面的代码片段中复制GetTextSize()。
C++
HRESULT GetTextSize(
const WCHAR* text, IDWriteTextFormat* pTextFormat, D2D1_SIZE_F& size)
{
HRESULT hr = S_OK;
ComPtr pTextLayout;
float floatDim = static_cast(m_Dim);
// Create a text layout
hr = FactorySingleton::GetDWriteFactory()->CreateTextLayout(text,
static_cast(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;
}
每当使用特定字体显示文本时,必须确保该字体存在于用户系统上。这可以通过枚举系统上的字体来实现。以下是字体枚举所需的结构。为简洁起见,未显示结构的构造函数、setter和getter。
C++
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 m_SubTypes;
UINT32 m_StartY;
UINT32 m_Height;
};
EnumFont()函数负责为进行字体枚举。
C++
void EnumFont(
std::vector>& vecFont)
{
if (!vecFont.empty())
return;
ComPtr fontCollection;
HR(FactorySingleton::GetDWriteFactory()->GetSystemFontCollection(
fontCollection.GetAddressOf(),
TRUE));
UINT32 familyCount = fontCollection->GetFontFamilyCount();
for (UINT32 i = 0; i < familyCount; ++i)
{
ComPtr fontFamily;
HR(fontCollection->GetFontFamily(i, fontFamily.GetAddressOf()));
if (!fontFamily)
continue;
ComPtr names;
HR(fontFamily->GetFamilyNames(names.GetAddressOf()));
WCHAR wname[100];
UINT32 localizedCount = names->GetCount();
std::shared_ptr info = std::make_shared();
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 vecSubNames;
vecSubNames.reserve(fontCount);
for (UINT32 j = 0; j < fontCount; ++j)
{
ComPtr font;
HR(fontFamily->GetFont(j, font.GetAddressOf()));
if (!font)
continue;
ComPtr 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& a,
const std::shared_ptr& b)
{
return a->OriginalName() < b->OriginalName();
});
}
下面显示了选定的字体及其子类型。子类型处理字体系列的斜体和粗体类型。子类型对没有用,因为对一些子类型名称不清楚。而且Microsoft Office不向用户公开字体子类型供其选择。
如果使用DirectWrite字体枚举,应该使用DirectWrite而不是GDI+来显示文本。为什么呢?可能会问。一个好的原因是,如果选择从DirectWrite字体枚举返回的Cooper字体,GDI+无法显示该字体,而DirectWrite没有问题。
GDI+字体枚举返回Cooper Black字体,它可以显示。严格来说,Cooper Black是Cooper字体系列的粗体子类型。这是Microsoft在两种不同的图形技术下对字体类型进行分类的一个怪癖。