DirectWrite Text Rendering and Font Enumeration

在现代应用程序中,文本渲染的质量至关重要。需要支持高分辨率的字体、完整的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在两种不同的图形技术下对字体类型进行分类的一个怪癖。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485