November 2013

Volume 28 Number 11

Windows with C++ - Exploring Fonts with DirectWrite and Modern C++

By Kenny Kerr | November 2013

Kenny KerrDirectWrite 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.

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.

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:

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 IDWrite­Factory1, which in turn inherits from IDWriteFactory. The latter is the original DirectWrite factory interface that exposes the bulk of the factory’s capabilities.

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

HR(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED,
  __uuidof(factory),
  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.

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

ComPtr<IDWriteFontCollection> fonts;
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:

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:

ComPtr<IDWriteFontFamily> family;
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:

ComPtr<IDWriteLocalizedStrings> names;
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:

wchar_t locale[LOCALE_NAME_MAX_LENGTH];
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:

unsigned index;
BOOL exists;
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:

if (exists)
{
  wchar_t name[64];
  HR(names->GetString(index, name, _countof(name)));
}

If you’re worried about the length, you can first call the GetString­Length 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.

Figure 1 Enumerating Fonts with the 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);
  }
}

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:

auto factory = CreateFactory();

Getting hold of the system font collection is equally simple:

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:

for (auto family : fonts)
{
  ...
}

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.

Figure 2 Enumerating Fonts with 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);
  }
}

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.

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.

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

DirectWrite::Factory2 m_writeFactory;

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

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:

auto fonts = m_writeFactory.GetSystemFontCollection();
wchar_t locale[LOCALE_NAME_MAX_LENGTH];
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:

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:

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:

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:

m_font = begin(m_fonts);
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:

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:

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:

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

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:

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:

auto __stdcall Invoke(
  ICoreWindow *,IKeyEventArgs * args) -> HRESULT override
{
  ...
  return S_OK;
}

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:

VirtualKey key;
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:

CorePhysicalKeyStatus status;
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:

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:

if (!status.WasKeyDown)
{
  m_accelerate = 1;
}
else
{
  m_accelerate += 2;
  m_accelerate = std::min(20U, m_accelerate);
}

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:

if (VirtualKey_Left == key)
{
  m_size = std::max(1.0f, m_size - m_accelerate);
}

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

else if (VirtualKey_Right == key)
{
  m_size += m_accelerate;
}

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

if (begin(m_fonts) == m_font)
{
  m_font = end(m_fonts);
}
--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:

else if (VirtualKey_Down == key)
{
  ++m_font;
  if (end(m_fonts) == m_font)
  {
      m_font = begin(m_fonts);
  }
}

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:

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);

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.

The Font Browser
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:

 

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);

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

Color Fonts
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.

Scaled Color Fonts
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.