副标题#e#
DirectWrite 是一种相当强大的文本机关 API。 它支持从 XAML 和 Office 2013 的 Windows 运行 时 (WinRT) 实现到 Internet Explorer 11 和更高版本的险些所有领先 Windows 应用措施和技能。 它 自己并不是泛起引擎,但与 Direct2D 有很近的干系,是 Direct2D 在 DirectX 系列中的同级产物。 虽然,Direct2D 是首要的硬件加快即时模式图形 API。
您可以将 DirectWrite 与 Direct2D 团结利用,以提供硬件加快的文本泛起。 说明一下,之前我 在 DirectWrite 方面的著述并不多。 我不但愿您认为 Direct2D 只是 DirectWrite 泛起引擎。 Direct2D 远不但如此。 DirectWrite 尚有其他许多成果,在本月的专栏中,我将演示一些利用 DirectWrite 可 以完成的任务,看看最新 C++ 是如何辅佐简化编程模子的。
DirectWrite API
我将利用 DirectWrite 探究系统字体集。 首先,我需要获取 DirectWrite 工场工具。 这是编写 任 何要利用 DirectWrite 的精彩排版成果的应用措施的第一步。 与大大都 Windows API 沟通, DirectWrite 也依赖于 COM 基本内容。 我需要挪用 DWriteCreateFactory 函数来建设 DirectWrite 工场工具。 此函数返回一个指向该工场工具的 COM 接口:
ComPtr<IDWriteFactory2> factory;
IDWriteFactory2 接口是本年早些时候随 Windows 8.1 和 DirectX 11.2 推出的最新版本 DirectWrite 工场接口。 IDWriteFactory2 担任自 IDWriteFactory1,而 IDWriteFactory1 担任自 IDWriteFactory。 后者是原始的 DirectWrite 工场接口,它果真了大部门工场成果。
我将基于前面的 ComPtr 类模板挪用 DWriteCreateFactory 函数:
HR(DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED,
__uuidof(factory),
reinterpret_cast<IUnknown **>(factory.GetAddressOf())));
DirectWrite 包括一项名为 Windows 字体缓存处事 (FontCache) 的 Windows 处事。 第一个参数 指 示得到的工场是否参加此跨进程缓存的字体利用。 有 DWRITE_FACTORY_TYPE_SHARED 和 DWRITE_FACTORY_TYPE_ISOLATED 两个选项。 SHARED 和 ISOLATED 工场都可以操作已缓存的字体数据 。 只有 SHARED 工场向缓存返回字体数据。 第二个参数详细指示我但愿在第三个和最后一个参数中返回 哪 个版本的 DirectWrite 工场接口。
在 DirectWrite 工场工具给定的环境下,我可以直接要求其提供系统字体集:
ComPtr<IDWriteFontCollection> fonts;
HR(factory->GetSystemFontCollection(fonts.GetAddressOf()));
GetSystemFontCollection 要领的第二个参数是可选的,指示其是否查抄已安装字体集的更新或更 改 。 幸运的是,这个参数默认为 false,因此,除非要确保功效集反应最近的变动,不然不必思量它。 在字体集给定的环境下,我可以获取荟萃中的字体系列数,如下所示:
unsigned const count = fonts- >GetFontFamilyCount();
然后我利用 GetFontFamily 要领通过从零开始的索引检索单个字体系列工具。 一个字体系列工具 表 示这样一组字体:它们共享一个名称,虽然也是一种设计,但粗细、样式和拉伸并不沟通:
ComPtr<IDWriteFontFamily> family;
HR(fonts->GetFontFamily(index, family.GetAddressOf()));
IDWriteFontFamily 接口担任自 IDWriteFontList 接口,因此,我可以列举该字体系列中的各类字 体。 可以或许检索字体系列名称合乎情理而且很是有用。 不外,系列名称已颠末当地化,所以它并不像您 等候的那样简朴直接。 首先,我需要字体系列提供一个当地化字符串工具,该工具针对每种支持的区 域 配置均包括一个系列名称:
ComPtr<IDWriteLocalizedStrings> names;
HR(family->GetFamilyNames(names.GetAddressOf()));
我也可以列举系列名称,但一般只查找用户默认区域配置对应的名称。 事实上, IDWriteLocalizedStrings 接口提供了 FindLocaleName 要领来检索当地化系列名称的索引。 我从调 用 GetUserDefaultLocaleName 函数以获取用户默认区域配置开始:
wchar_t locale [LOCALE_NAME_MAX_LENGTH];
VERIFY(GetUserDefaultLocaleName(locale, countof(locale)));
然后,我将默认区域配置通报给 IDWriteLocalizedStrings FindLocaleName 要领,确定该字体系 列 是否有针对当前用户当地化的名称:
unsigned index;
BOOL exists;
HR(names->FindLocaleName(locale, &index, &exists));
#p#副标题#e#
假如请求的区域配置在会合不存在,我大概会回到一些默认配置,如“en-us”。假设存 在,就可以利用 IDWriteLocalizedStrings GetString 要领获取副本:
#p#分页标题#e#
if (exists)
{
wchar_t name[64];
HR(names->GetString(index, name, _countof(name)));
}
假如担忧长度,可以先挪用 GetStringLength 要领。 只要确保缓冲区足够大就可以。 图 1 提供了一个完整列表,显示所有内容是如何配合列举已安装字体的。
图 1 利用 DirectWrite API 列举字体
ComPtr<IDWriteFactory2> factory;
HR(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED,
__uuidof(factory),
reinterpret_cast<IUnknown **>(factory.GetAddressOf())));
ComPtr<IDWriteFontCollection> fonts;
HR(factory->GetSystemFontCollection(fonts.GetAddressOf()));
wchar_t locale[LOCALE_NAME_MAX_LENGTH];
VERIFY(GetUserDefaultLocaleName(locale, _countof(locale)));
unsigned const count = fonts->GetFontFamilyCount();
for (unsigned familyIndex = 0; familyIndex != count; ++familyIndex)
{
ComPtr<IDWriteFontFamily> family;
HR(fonts->GetFontFamily(familyIndex, family.GetAddressOf()));
ComPtr<IDWriteLocalizedStrings> names;
HR(family->GetFamilyNames(names.GetAddressOf()));
unsigned nameIndex;
BOOL exists;
HR(names->FindLocaleName(locale, &nameIndex, &exists));
if (exists)
{
wchar_t name[64];
HR(names->GetString(nameIndex, name, countof(name)));
wprintf(L"%s\n", name);
}
}
最新 C++ 浅谈
假如您常常阅读本杂志,就会知道我已经先容过 DirectX,出格是 Direct2D 的最新 C++ 处理惩罚。 dx.h 头文件 (dx.codeplex.com) 也包括 DirectWrite。 可以用它大大简化我在上面提供的代码。 不 必挪用 DWriteCreateFactory,我可以从 DirectWrite 定名空间直接挪用 CreateFactory 函数:
auto factory = CreateFactory();
获取系统字体集同样简朴:
auto fonts = factory.GetSystemFontCollection();
列举此字体集是 dx.h 的真正亮点。 我不必编写传统的 for 轮回, 也不必挪用 GetFontFamilyCount 和 GetFontFamily 要领, 我只需要编写一个最新的基于范畴的 for 轮回:
for (auto family : fonts)
{
…
}
这些代码与以往沟通。 编译器(在 dx.h 的辅佐下)举办生成,我利用了一个更为自然的编程模子 ,编写正确有效的代码更为简朴。 前面的 GetSystemFontCollection 要领返回一个 FontCollection 类,个中包括一个迭代器,它将延迟提取字体系列工具。 这使得编译器可以或许有效地实现基于范畴的循 环 。 图 2 提供了完整列表。 与图 1 中的代码较量,它更为清晰,效率更高。
图 2 利用 dx.h 列举字体
auto factory = CreateFactory();
auto fonts = factory.GetSystemFontCollection();
wchar_t locale[LOCALE_NAME_MAX_LENGTH];
VERIFY(GetUserDefaultLocaleName(locale, _countof(locale)));
for (auto family : fonts)
{
auto names = family.GetFamilyNames();
unsigned index;
if (names.FindLocaleName(locale, index))
{
wchar_t name[64];
names.GetString(index, name);
wprintf(L"%s\n", name);
}
}
字体欣赏器与 Windows 运行时
DirectWrite 的浸染远不可是列举字体。 我将前面这些内容与 Direct2D 团结起来,建设一个简朴 的字体欣赏器应用措施。 在我 2013 年 8 月的专栏 (http://msdn.microsoft.com/zh- cn/magazine/dn342867.aspx) 中,先容了如何用尺度 C++ 编写根基的 WinRT 应用措施模子。 在我 2013 年 10 月的专栏 (msdn.microsoft.com/magazine/dn451437) 中,先容了如何通过 DirectX(具 体 而言是 Direct2D)在此基于 CoreWindow 的应用措施中举办泛起。 此刻先容如安在 DirectWrite 的 帮 助下扩展代码,从而利用 Direct2D 泛起文本。
#p#分页标题#e#
这些专栏颁发后,Windows 8.1 宣布了,它对 DPI 缩放在最新应用措施和桌面应用措施中的处理惩罚方 式有一些窜改。 今后我会具体先容 DPI,这里暂不接头这些变动。 我们只重点扩展我在八月提出,在 十月扩展过的 SampleWindow 类,以便支持文本泛起并简化字体欣赏。
首先,将 DirectWrite Factory2 类添加为成员变量:
DirectWrite::Factory2 m_writeFactory;
在 SampleWindow CreateDeviceIndependentResources 要领内,可以建设 DirectWrite 工场:
m_writeFactory = DirectWrite::CreateFactory();
在这里我还可以获取系统字体集和用户的默认区域配置,为列举字体系列做筹备:
auto fonts = m_writeFactory.GetSystemFontCollection();
wchar_t locale[LOCALE_NAME_MAX_LENGTH];
VERIFY(GetUserDefaultLocaleName(locale, _countof(locale)));
我将使应用措施在用户按下向上和向下箭头键时轮回切换字体。 我不通过 COM 接口不绝列举字体 集 ,而只是将字体系列名称复制到一个尺度 set 容器:
set<wstring> m_fonts;
此刻,我可以在 CreateDeviceIndependentResources 中利用图 2 中基于范畴的 for 轮回将名称 添 加到 set:
m_fonts.insert(name);
set 填充后,利用一个指向 set 开头的迭代器开始应用措施。 将迭代器存储为成员变量:
set<wstring>::iterator m_font;
SampleWindow CreateDeviceIndependentResources 要领初始化迭代器并挪用 CreateTextFormat 方 法,界说如下:
m_font = begin(m_fonts);
CreateTextFormat();
在 Direct2D 绘制文本前,需要建设一个文本名目工具。 这需要字体系列名称和所需字号。 我允 许 用户利用向左和向右箭头键变动字号,因此添加一个成员变量来跟踪字号:
float m_size;
Visual C++ 编译器很快会答允我初始化像这样的类内非静态数据成员。 此刻,需要在 SampleWindow 的结构函数中将它配置为公道的默认值。 接下来,需要界说 CreateTextFormat 要领。 这只是 DirectWrite 工场要领的同名包装,但它更新了一个成员变量,Direct2D 可以利用该变量界说 要绘制的文本的名目:
TextFormat m_textFormat;
然后,CreateTextFormat 要领从 set 迭代器检索字体系列名称,将其与当前字号组合,建设一个 新 的文本名目工具:
void CreateTextFormat()
{
m_textFormat = m_writeFactory.CreateTextFormat(m_font->c_str (),m_size);
}
我已经把它包装起来,因此,除了最初在 CreateDeviceIndependentResources 末端挪用它外,我 还 可以在每次用户按下一个箭头键变动字体系列或字号时挪用它。 这引出了在 WinRT 应用措施模子中如 那里理惩罚按键的问题。 在桌面应用措施中,这与 WM_KEYDOWN 动静处理惩罚有关。 亏得 CoreWindow 提供了 KeyDown 事件,它是这一动静的最新等效项。 我从界说 IKeyEventHandler 接口开始,SampleWindow 需要实现该接口:
typedef ITypedEventHandler<CoreWindow *, KeyEventArgs *> IKeyEventHandler;
然后,可以将此接口添加到担任接口的 SampleWindow 列表中,并相应地更新 QueryInterface 实 现 。 接下来只需提供它的 Invoke 实现:
auto __stdcall Invoke(
ICoreWindow *,IKeyEventArgs * args) -> HRESULT override
{
…
return S_OK;
}
查察本栏目
IKeyEventArgs 接口提供的信息与 LPARAM 和 WPARAM 向 WM_KEYDOWN 动静提供的信息基内情同。 它的 get_VirtualKey 要领对应于后者的 WPARAM,指示按下了哪个非系统键:
VirtualKey key;
HR(args->get_VirtualKey(&key));
与此雷同,它的 get_KeyStatus 要领对应于 WM_KEYDOWN 的 LPARAM。 这样可以提供有关按键事件 状态的富厚信息:
CorePhysicalKeyStatus status;
HR(args->get_KeyStatus(&status));
为利便用户,我支持在用户按住箭头键时加快,以便更快地调解所泛起字体的字号。 为此,需要另 一个成员变量:
unsigned m_accelerate;
然后,可以利用该事件的键状态来确定是将字号变动一个增量照旧增加必然的巨细:
#p#分页标题#e#
if (!status.WasKeyDown)
{
m_accelerate = 1;
}
else
{
m_accelerate += 2;
m_accelerate = std::min(20U, m_accelerate);
}
我配置了上限,因此加快不会过分。 此刻可以别离处理惩罚差异的按键。 首先是向左箭头键,用于缩 小 字号:
if (VirtualKey_Left == key)
{
m_size = std::max(1.0f, m_size – m_accelerate);
}
我很小心,不会使字号无效。 然后是向右箭头键,用于增加字号:
else if (VirtualKey_Right == key)
{
m_size += m_accelerate;
}
接下来,移到上一字体系列,处理惩罚向上箭头键:
if (begin(m_fonts) == m_font)
{
m_font = end(m_fonts);
}
–m_font;
然后,我仔细轮回到最后一个字体,看看迭代器是否会回到序列开头。 接下来,移到下一字体系列 ,处理惩罚向下箭头键:
else if (VirtualKey_Down == key)
{
++m_font;
if (end(m_fonts) == m_font)
{
m_font = begin(m_fonts);
}
}
在这里,再一次仔细轮回到开头,看看迭代器是否会回到序列末了。 最后,我可以在事件处理惩罚措施 末端挪用 CreateTextFormat 要领来从头建设文本名目工具。
剩下的工作是,更新 SampleWindow Draw 要领,用当前文本名目绘制一些文本。 要领如下:
wchar_t const text [] = L"The quick brown fox jumps over the lazy dog";
m_target.DrawText(text, _countof(text) – 1,
m_textFormat,
RectF(10.0f, 10.0f, size.Width – 10.0f, size.Height – 10.0f),
m_brush);
Direct2D 泛起方针的 DrawText 要领直接支持 DirectWrite。此刻 DirectWrite 就可以处理惩罚文本 布 局了,并且泛起速度很是快。这就是该剧本所执行的所有操纵。图 3 是执行结果。 我可以按向上和向下箭头键遍历字体系列,按向左和向右箭头键调解字号。Direct2D 按照当前选择自 动 从头泛起。
图 3 字体欣赏器
彩色字体
Windows 8.1 引入了一个称为彩色字体的新成果,去掉了一些实现彩色字体的次优办理方案。虽然 , 这都是 DirectWrite 和 Direct2D 促成的。令人兴奋的是,挪用 Direct2D DrawText 要领就像利用 D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT 常量一样简朴。我可以将 SampleWindow 的 Draw 要领更新为利用相应范畴的列举值:
m_target.DrawText(text, _countof(text) – 1,
m_textFormat,
RectF(10.0f, 10.0f, size.Width – 10.0f, size.Height – 10.0f),
m_brush);
DrawTextOptions::EnableColorFont);
图 4 也是字体欣赏器,这次是一些 Unicode 心情。
图 4 彩色字体
彩色字体的亮点在于可以自动缩放,而不影响质量。我可以在字体欣赏器应用措施中按向右箭头键 , 更近地查察细节。功效如图 5 所示。
图 5 放大的彩色字 体
通过提供彩色字体、硬件加快文本泛起以及流通有效的代码,DirectWrite 在 Direct2D 和最新 C++ 的辅佐下抖擞出生命力。