zoukankan      html  css  js  c++  java
  • [翻译.每月一译.每日一段]Exploring Fonts with DirectWrite and Modern C++

    Windows with C++

    Exploring Fonts with DirectWrite and Modern C++

    Kenny Kerr

    DirectWrite is an incredibly powerful text layout API. It powers practically all of the leading Windows applications and technologies, from the Windows Runtime (WinRT) implementation of XAML and Office 2013, to Internet Explorer 11 and more. It's not a rendering engine in itself, but has a close relationship with Direct2D, its sibling in the DirectX family. Direct2D is, of course, the premier hardware-accelerated, immediate-mode graphics API.

    [2013-12-29]DirectWrite是一种功能强大的文字界面API。特别是最新的Windows应用程序和技术中它的功能随处可见,从Windows RT版的XAMLOffice2013IE11等等。它本身并不是一个渲染引擎,但与Direct2D有很近的关系,从属于DirectX家族。当然Direct2D是一种实时硬件加速的图形API

    You can use DirectWrite with Direct2D to provide hardware-accelerated text rendering. To avoid any confusion, I haven't written too much about DirectWrite in the past. I didn't want you to think Direct2D is just the DirectWrite rendering engine. Direct2D is so much more than that. Still, DirectWrite has a lot to offer, and in this month's column I'll show you some of what's possible with DirectWrite and look at how modern C++ can help simplify the programming model.

    [2013-12-30]你可以将DirectWriteDirect2D一起使用提供硬件加速的文字渲染。为了避免混淆,我还没有写太多有关DirectWrite的文章。我不想让你认为Direct2D仅仅是DirectWrite的渲染引擎。Direct2D比那个可强大多了。可是,DirectWrite提供了很多东西,在这个月的专栏里我会向你展示DirectWrite可以怎么使用和现代C++技术是如何帮助我们简化编程模型。

    The DirectWrite API

    I'll use DirectWrite to explore the system font collection. First, I need to get hold of the DirectWrite factory object. This is the starting point for any application that wants to use the impressive typography of DirectWrite. DirectWrite, like so much of the Windows API, relies on the essentials of COM. I need to call the DWriteCreateFactory function to create the DirectWrite factory object. This function returns a COM interface pointing to the factory object:

    [2013-12-30]我会使用DirectWrite展示系统字体集。首先,我会取得DirectWrite类厂对象。这是任何程序使用让人印象深刻的DirectWrite印刷样式的起点。DirectWriteWindows API很像,依赖基础COM。我需要调用DWriteCreateFactory函数创建DirectWrite类厂对象。这个函数返回一个COM接口指向类厂对象。

    1. ComPtr<IDWriteFactory2> factory;

    The IDWriteFactory2 interface is the latest version of the DirectWrite factory interface introduced with Windows 8.1 and DirectX 11.2 earlier this year. IDWriteFactory2 inherits from IDWriteFactory1, which in turn inherits from IDWriteFactory. The latter is the original DirectWrite factory interface that exposes the bulk of the factory's capabilities.

    [2013-12-31]IDWriteFactory2接口是DirectWrite最新的版本的类厂接口,随Windows 8.1DirectX11.2在今年早些时候引入。IDWriteFactory2继承于IDWriteFactory1IDWriteFactory1又继承于IDWriteFactoryIDWriteFactory是原始的暴露出大量工厂功能的DirectWrite类厂接口。

    Given the preceding ComPtr class template, I'll call the DWriteCreateFactory function:

    [2013-12-31]前面已经给出了一个ComPtr类模板,现在我调用DWriteCreateFactory函数:

    1. HR(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED,
    2.   __uuidof(factory),
    3.   reinterpret_cast<IUnknown **>(factory.GetAddressOf())));

    DirectWrite includes a Windows service called the Windows Font Cache Service (FontCache). This first parameter indicates whether the resulting factory contributes font usage to this cross-process cache. The two options are DWRITE_FACTORY_TYPE_SHARED and DWRITE_FACTORY_TYPE_ISOLATED. Both SHARED and ISOLATED factories can take advantage of font data that's already cached. Only SHARED factories contribute font data back to the cache. The second parameter indicates specifically which version of the DirectWrite factory interface I'd like it to return in the third and final parameter.

    [2013-12-31]DirectWrite包含一个叫做Windows Font Cache Service windows系统服务。第一个参数决定工厂怎样使用跨进程缓存。这两个选项是DWRITE_FACTORY_TYPE_SHAREDDWRITE_FACTORY_TYPE_ISOLATEDSHAREDISOLATED两种工厂都可以从已经缓存的字体数据中收益。只有SHARED工厂会向缓存中添加字体数据。第二个参数指示DirectWrite工厂接口的哪一个版本在第三个参数和最后一个参数中返回。

    Given the DirectWrite factory object, I can get right to the point and ask it for the system font collection:

    [2013-12-31]获得DirectWrite工厂对象,我就可以获得正确的入口点并获取系统字体集合。

    1. ComPtr<IDWriteFontCollection> fonts;
    2. HR(factory->GetSystemFontCollection(fonts.GetAddressOf()));

    The GetSystemFontCollection method has an optional second parameter that tells it whether to check for updates or changes to the set of installed fonts. Fortunately, this parameter defaults to false, so I don't have to think about it unless I want to ensure recent changes are reflected in the resulting collection. Given the font collection, I can get the number of font families in the collection as follows:

    [2014-01-01]GetSystemFontCollection方法有第二个可选参数表明是否检查更新或修改已安装字体集合。幸运的是,这个参数默认设置为False,所以我可以不考虑它,除非我想要确保最近的修改确实反映到了结果集中。有了字体集合,我可以像下面一样获得集合中字体族的个数:

    1. unsigned const count = fonts->GetFontFamilyCount();

    Then I use the GetFontFamily method to retrieve individual font family objects using a zero-based index. A font family object represents a set of fonts that share a name—and, of course, a design—but are distinguished by weight, style and stretch:

    [2014-01-01]然后我使用GetFontFamily方法通过从0开始的一个索引获取每个字体族。一个字体族对象代表了共享一个名字和设计的一系列字体集合,但是有不同的字体重量,风格和张力。

    1. ComPtr<IDWriteFontFamily> family;
    2. HR(fonts->GetFontFamily(index, family.GetAddressOf()));

    The IDWriteFontFamily interface inherits from the IDWriteFontList interface, so I can enumerate the individual fonts within the font family. It's reasonable and useful to be able to retrieve the name of the font family. Family names are localized, however, so it's not as straightforward as you might expect. I first need to ask the font family for a localized strings object that will contain one family name per supported locale:

    [2014-01-02]IDWriteFontFamily接口继承于IDWriteFontList接口,我一我可以在一个字体族中枚举每一个字体。可以获取字体族的名字是合理并很实用的。字体族名字都是本地化的,但是,它没有你期待的那么直白。首先我需要询问字体族获取一个保存保存了每个字体族支持语言地区本地化字符串对象:

    1. ComPtr<IDWriteLocalizedStrings> names;
    2. HR(family->GetFamilyNames(names.GetAddressOf()));

    I can also enumerate the family names, but it's common to simply look up the name for the user's default locale. In fact, the IDWriteLocalizedStrings interface provides the FindLocaleName method to retrieve the index of the localized family name. I'll start by calling the GetUserDefaultLocaleName function to get the user's default locale:

    [2014-01-02]我也可以枚举出字体族名字,但是通常做法是简单查询用户默认地区的字体族名字。实际上,IDWriteLocalizedStrings接口提供FindLocalName方法获取本地字体族名字的序号。我会调用GetUserDefaultLocaleName函数获取用户默认的地区信息。

    1. wchar_t locale[LOCALE_NAME_MAX_LENGTH];
    2. VERIFY(GetUserDefaultLocaleName(locale, countof(locale)));

    Then I pass this to the IDWriteLocalizedStrings FindLocaleName method to determine whether the font family has a name localized for the current user:

    [2014-01-02]然后我把这个参数传递给IDWriteLocalizedStrings FindLocaleName方法来决定是当前用户否有一个字体族拥有本地化名称。

    1. unsigned index;
    2. BOOL exists;
    3. HR(names->FindLocaleName(locale, &index, &exists));

    If the requested locale doesn't exist in the collection, I might fall back to some default such as "en-us." Assuming that exists, I can use the IDWriteLocalizedStrings GetString method to get a copy:

    [2014-01-02]如果需要的地区不在集合中,我可能会返回默认值如"en-us"。如果存在,我可以使用IDWriteLocalizedStrings GetString方法获得一份拷贝。

    1. if (exists)
    2. {
    3.   wchar_t name[64];
    4.   HR(names->GetString(index, name, _countof(name)));
    5. }

    If you're worried about the length, you can first call the GetStringLength method. Just make sure you have a large enough buffer. Figure 1 provides a complete listing, showing how this all comes together to enumerate the installed fonts.

    [2014-01-02]如果你担心长度,可以首先调用GetStringLength方法。确保你有一个足够长的缓冲。图1提供了一个完整的列表,展示用这些方法组合在一起如何枚举所有已安装字体。

    Figure 1 Enumerating Fonts with the DirectWrite API

    1. ComPtr<IDWriteFactory2> factory;
    2. HR(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED,
    3.   __uuidof(factory),
    4.   reinterpret_cast<IUnknown **>(factory.GetAddressOf())));
    5. ComPtr<IDWriteFontCollection> fonts;
    6. HR(factory->GetSystemFontCollection(fonts.GetAddressOf()));
    7. wchar_t locale[LOCALE_NAME_MAX_LENGTH];
    8. VERIFY(GetUserDefaultLocaleName(locale, _countof(locale)));
    9. unsigned const count = fonts->GetFontFamilyCount();
    10. for (unsigned familyIndex = 0; familyIndex != count; ++familyIndex)
    11. {
    12.   ComPtr<IDWriteFontFamily> family;
    13.   HR(fonts->GetFontFamily(familyIndex, family.GetAddressOf()));
    14.   ComPtr<IDWriteLocalizedStrings> names;
    15.   HR(family->GetFamilyNames(names.GetAddressOf()));
    16.   unsigned nameIndex;
    17.   BOOL exists;
    18.   HR(names->FindLocaleName(locale, &nameIndex, &exists));
    19.   if (exists)
    20.   {
    21.     wchar_t name[64];
    22.     HR(names->GetString(nameIndex, name, countof(name)));
    23.     wprintf(L"%s ", name);
    24.   }
    25. }

    A Touch of Modern C++

    If you're a regular reader, you'll know I've given DirectX, and Direct2D in particular, the modern C++ treatment. The dx.h header (dx.codeplex.com) also covers DirectWrite. You can use it to simplify the code I've presented thus far quite dramatically. Instead of calling DWriteCreateFactory, I can simply call the CreateFactory function from the DirectWrite namespace:

    [2014-01-03]如果你是一位定期读者,你知道一般我都会给出DirectXDirect2D的现代C++处理方法。Dx.h头文件包含了DirectWrite的功能。你可以使用它来在很大程度上简化我给出的代码。我可以简单的从DirectWrite命名空间中调用CreateFactory函数来代替调用DWriteCreateFactory

    1. auto factory = CreateFactory();

    Getting hold of the system font collection is equally simple:

    [2014-01-03]获得系统字体集合也同样简单:

    1. auto fonts = factory.GetSystemFontCollection();

    Enumerating this collection is where dx.h really shines. I don't have to write a traditional for loop. I don't have to call the GetFontFamilyCount and GetFontFamily methods. I can simply write a modern range-based for loop:

    [2014-01-03]枚举这个集合是dx.h真正的亮点。我不需要写一个传统的循环。我不需要调用GetFontFamilyCountGetFontFamily方法。我可以简单地写一个区间内的for循环:

    1. for (auto family : fonts)
    2. {
    3.   ...
    4. }

    This is really the same code as before. The compiler (with the help of dx.h) is generating it for me and I get to use a far more natural programming model, making it easier to write code that's both correct and efficient. The preceding GetSystemFontCollection method returns a FontCollection class that includes an iterator that will lazily fetch font family objects. This lets the compiler efficiently implement the range-based loop. Figure 2 provides a complete listing. Compare it with the code in Figure 1 to appreciate the clarity and potential for productivity.

    [2014-01-03]这和之前的代码完全一样。编译器(在dx.h的帮助下)为我生成出来并且我可以得到一种更自然的编程模型,这种模型让编写代码更加正确和高效。前面的GetSystemFontCollection方法返回一个包含一个每次获得一个字体族迭代器对象的FontCollection类。这样就让编译器高效的实现了range-based循环。图2给出了一个完整的列表。把它与图1的方法进行对比更清晰和有生产率。

    Figure 2 Enumerating Fonts with dx.h

    1. auto factory = CreateFactory();
    2. auto fonts = factory.GetSystemFontCollection();
    3. wchar_t locale[LOCALE_NAME_MAX_LENGTH];
    4. VERIFY(GetUserDefaultLocaleName(locale, _countof(locale)));
    5. for (auto family : fonts)
    6. {
    7.   auto names = family.GetFamilyNames();
    8.   unsigned index;
    9.   if (names.FindLocaleName(locale, index))
    10.   {
    11.     wchar_t name[64];
    12.     names.GetString(index, name);
    13.     wprintf(L"%s ", name);
    14.   }
    15. }

    A Font Browser with the Windows Runtime

    DirectWrite does much more than just enumerate fonts. I'm going to take what I've shown you thus far, combine it with Direct2D, and create a simple font browser app. In my August 2013 column (msdn.microsoft.com/magazine/dn342867), I showed you how to write the fundamental WinRT app model plumbing in standard C++. In my October 2013 column (msdn.microsoft.com/magazine/dn451437), I showed you how to render inside this CoreWindow-based app with DirectX and specifically Direct2D. Now I'm going to show you how to extend that code to use Direct2D to render text with the help of DirectWrite.

    [2014-01-04]DirectWrite不仅仅是枚举字体。我要用我已经教给你的东西结合Direct2D创造一个字体浏览器程序。在20138月的专栏里我展示过如何通过C++写一个WinRT基础程序。在201310月的专栏里我向你展示过如何用DirectXDirect2D在一个基于窗体内渲染。现在我要向你们演示如何扩展那些代码,并用Direct2DDirectWrite的帮助下渲染文字。

    Since those columns were written, Windows 8.1 was released, and it changed a few things about the manner in which DPI scaling is handled in both modern and desktop apps. I'm going to cover DPI in detail in the future, so I'll leave those changes for the time being. I'm simply going to focus on augmenting the SampleWindow class I began in August and extended in October to support text rendering and simple font browsing.

    [2014-01-04]由于那些专栏已经写了,Windows8.1也已经发布了,它改变了一些DPI行为,缩放同时被现代风格程序和桌面应用处理。我将DPI的详细内容放到以后讲,所以我从开始会忽略那些变化。我仅仅关注讨论8月和10月扩展的SampleWindow类支持文字渲染和简单的字体浏览。

    The first thing to do is to add the DirectWrite Factory2 class as a member variable:

    [2014-01-04]第一件事就是将DirectWrite Factory2类作为一个成员变量在类中添加:

    1. DirectWrite::Factory2 m_writeFactory;

    Inside the SampleWindow CreateDeviceIndependentResources method, I can create the DirectWrite factory:

    SampleWindow CreateDeviceindepedentResources 方法中,我可以创建DirectWrite工厂对象:

    1. m_writeFactory = DirectWrite::CreateFactory();

    I can also get the system font collection and user's default locale there, in preparation for enumerating the font families:

    我也可以得到系统字体集合和用户默认地区,为枚举系统字体族做准备:

    1. auto fonts = m_writeFactory.GetSystemFontCollection();
    2. wchar_t locale[LOCALE_NAME_MAX_LENGTH];
    3. VERIFY(GetUserDefaultLocaleName(locale, _countof(locale)));

    I'm going to make the app cycle through the fonts as the user presses the up and down arrow keys. Rather than continually enumerating the collection via the COM interfaces, I'll just copy the font family names to a standard set container:

    [2014-01-04]我会让应用程序循环显示字体当用户按下上下方向键。而不是通过COM接口不停地枚举字体集合,我会拷贝字体族名称到一个标准集合中:

    1. set<wstring> m_fonts;

    Now I can simply use the same range-based for loop from Figure 2 inside CreateDeviceIndependentResources to add the names to the set:

    [2014-01-05]现在我可以使用range-based for循环在CreateDeviceIndependentResources中向集合添加名字。

    1. m_fonts.insert(name);

    With the set populated, I'll start the app off with an iterator pointing to the beginning of the set. I'll store the iterator as a member variable:

    [2014-01-05]由于在集合中,我会从集合的开始处用迭代器开始。我会将迭代器保存为一个成员变量:

    1. set<wstring>::iterator m_font;

    The SampleWindow CreateDeviceIndependentResources method concludes by initializing the iterator and calling the CreateTextFormat method, which I'll define in a moment:

    [2014-01-05]SampleWindow CreateDeviceIndependentResources 方法最后通过初始化迭代器和调用我马上定义的CreateTextFormat方法:

    1. m_font = begin(m_fonts);
    2. CreateTextFormat();

    Before Direct2D can draw some text for me, I need to create a text format object. To do that, I need both the font family name and the desired font size. I'm going to let the user change the font size with the left and right arrow keys, so I'll start by adding a member variable to keep track of the size:

    [2014-01-05]Direct2D为我绘制出文字前,我需要创建一个文字格式化对象。为了创建这个对象,我需要字体族名字和理想的字体大小。我要让用户可以通过左右光标键改变字体大小,所以这里我需要添加一个成员变量来记录字号:

    1. float m_size;

    The Visual C++ compiler will soon let me initialize nonstatic data members such as this within a class. For now, I need to set it to some reasonable default in the SampleWindow's constructor. Next, I need to define the CreateTextFormat method. It's just a wrapper around the DirectWrite factory method of the same name, but it updates a member variable that Direct2D can use to define the format of the text to be drawn:

    [2014-01-05]Visual C++编译器马上让我初始化非静态成员变量。所以现在,我需要把它在构造函数中设置为一个默认比较合理的值。接下来,我需要定义CreateTextFormat方法。这个方法仅仅是对DirectWrite工厂同名方法的一个封装,但是它会更新一个Direct2D用于定义文字绘图格式的额变量。

    1. TextFormat m_textFormat;

    The CreateTextFormat method then simply retrieves the font family name from the set iterator and combines it with the current font size to create a new text format object:

    [2014-01-05]CreateTextFormat方法然后就很容易的从迭代其中获得字体族名称和一起的当前字号,创建一个新的文字格式化对象。

    1. void CreateTextFormat()
    2. {
    3.   m_textFormat = m_writeFactory.CreateTextFormat(m_font->c_str(),m_size);
    4. }

    I've wrapped it up, so that in addition to calling it initially at the end of CreateDeviceIndependentResources, I can also call it every time the user presses one of the arrow keys to change the font family or size. This leads to the question of how to handle key presses in the WinRT app model. In a desktop application, this involves handling the WM_KEYDOWN message. Fortunately, CoreWindow provides the KeyDown event, which is the modern equivalent of this message. I'll start by defining the IKeyEventHandler interface that my SampleWindow will need to implement:

    1. typedef ITypedEventHandler<CoreWindow *, KeyEventArgs *> IKeyEventHandler;

    I can then simply add this interface to my SampleWindow list of inherited interfaces and update my QueryInterface implementation accordingly. I then just need to provide its Invoke implementation:

    1. auto __stdcall Invoke(
    2.   ICoreWindow *,IKeyEventArgs * args) -> HRESULT override
    3. {
    4.   ...
    5.   return S_OK;
    6. }

    The IKeyEventArgs interface provides much the same information as the LPARAM and WPARAM did for the WM_KEYDOWN message. Its get_VirtualKey method corresponds to the latter's WPARAM, indicating which nonsystem key is pressed:

    1. VirtualKey key;
    2. HR(args->get_VirtualKey(&key));

    Similarly, its get_KeyStatus method corresponds to WM_KEYDOWN's LPARAM. This provides a wealth of information about the state surrounding the key press event:

    1. CorePhysicalKeyStatus status;
    2. HR(args->get_KeyStatus(&status));

    As a convenience to the user, I'll support acceleration when the user presses and holds one of the arrow keys down, to resize the rendered font more rapidly. To support this, I'll need another member variable:

    1. unsigned m_accelerate;

    I can then use the event's key status to determine whether to change the font size by a single increment or an increasing amount:

    1. if (!status.WasKeyDown)
    2. {
    3.   m_accelerate = 1;
    4. }
    5. else
    6. {
    7.   m_accelerate += 2;
    8.   m_accelerate = std::min(20U, m_accelerate);
    9. }

    I've capped it so acceleration doesn't go too far. Now I can simply handle the various key presses individually. First is the left arrow key to reduce the font size:

    1. if (VirtualKey_Left == key)
    2. {
    3.   m_size = std::max(1.0f, m_size - m_accelerate);
    4. }

    I'm careful not to let the font size become invalid. Then there's the right arrow key to increase the font size:

    1. else if (VirtualKey_Right == key)
    2. {
    3.   m_size += m_accelerate;
    4. }

    Next, I'll handle the up arrow key by moving to the previous font family:

    1. if (begin(m_fonts) == m_font)
    2. {
    3.   m_font = end(m_fonts);
    4. }
    5. --m_font;

    Then I carefully loop around to the last font, should the iterator reach the beginning of the sequence. Next, I'll handle the down arrow key by moving to the next font family:

    1. else if (VirtualKey_Down == key)
    2. {
    3.   ++m_font;
    4.   if (end(m_fonts) == m_font)
    5.   {
    6.       m_font = begin(m_fonts);
    7.   }
    8. }

    Here, again, I'm careful to loop around to the beginning this time, should the iterator reach the end of the sequence. Finally, I can simply call my CreateTextFormat method at the end of the event handler to recreate the text format object.

    All that remains is to update my SampleWindow Draw method to draw some text with the current text format. This should do the trick:

    1. wchar_t const text [] = L"The quick brown fox jumps over the lazy dog";
    2. m_target.DrawText(text, _countof(text) - 1,
    3.   m_textFormat,
    4.   RectF(10.0f, 10.0f, size.Width - 10.0f, size.Height - 10.0f),
    5.   m_brush);

    The Direct2D render target's DrawText method directly supports DirectWrite. Now DirectWrite can handle text layout, and with amazingly fast rendering. That's all it takes. Figure 3 gives you an idea of what to expect. I can press the up and down arrow keys to cycle through the font families and press the left and right arrow keys to scale the font size. Direct2D automatically re-renders with the current selection.


    Figure 3 The Font Browser

    Did You Say Color Fonts?

    Windows 8.1 introduced a new feature called color fonts, doing away with a number of suboptimal solutions for implementing multicolored fonts. Naturally, it all comes down to DirectWrite and Direct2D to make it happen. Fortunately, it's as simple as using the D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT constant when calling the Direct2D DrawText method. I can update my SampleWindow's Draw method to use the corresponding scoped enum value:

       

    1. m_target.DrawText(text, _countof(text) - 1,
    2.   m_textFormat,
    3.   RectF(10.0f, 10.0f, size.Width - 10.0f, size.Height - 10.0f),
    4.   m_brush);
    5. DrawTextOptions::EnableColorFont);

    Figure 4 shows the font browser again, this time with some Unicode emoticons.


    Figure 4 Color Fonts

    Color fonts really shine when you realize you can scale them automatically without losing quality. I can press the right arrow key in my font browser app and get a closer look at the detail. You can see the result in Figure 5.


    Figure 5 Scaled Color Fonts

    Giving you color fonts, hardware-accelerated text rendering, and elegant and efficient code, DirectWrite comes to life with the help of Direct2D and modern C++.

    Kenny Kerr is a computer programmer based in Canada, an author for Pluralsight and a Microsoft MVP. He blogs at kennykerr.ca and you can follow him on Twitter at twitter.com/kennykerr.

    Thanks to the following technical expert for reviewing this article: Worachai Chaoweeraprasit (Microsoft)
    Worachai Chaoweeraprasit is the development lead for Direct2D and DirectWrite. He's obsessed with speed and quality of 2D vector graphics as well as onscreen readability of text. In his free time he enjoys being cornered by the two little people at home.

     

  • 相关阅读:
    在python3.x上安装suds 并访问webservice
    numpy nonzero与isnan
    彻底弄清python的切片
    pandas read_sql与read_sql_table、read_sql_query 的区别
    dataframe to sql
    同时替换掉多个字符串
    matplotlib中在for中画出多张图
    MySql 创建/删除数据库
    python3与anaconda2共存
    js调用打印机
  • 原文地址:https://www.cnblogs.com/cartler/p/3498693.html
Copyright © 2011-2022 走看看