Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C++

Direct2D Tutorial Part 5: Text Display and Font Enumeration

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
10 Mar 2023CPOL4 min read 10.6K   273   6   6
Introduction to DirectWrite Text Display and Font Enumeration
In this article, you will learn how to display text, measure text and enumerate installed fonts using DirectWrite.

Table of Contents

The example code is hosted on Github.

demo screenshot

Introduction

Today's applications must support high-quality text rendering, resolution-independent outline fonts, and full Unicode text and layout support. DirectWrite, a DirectX API, provides these features and more. In this article, you will learn how to display text, measure text and enumerate installed fonts using DirectWrite. The demo code is an end credit dialog from my MandyFrenzy photo slideshow app where the user adds an end credit (such as its director or producer) to the slideshow. It is fun to see yourself listed as director/producer at the end of the slideshow video. I have added MandyFrenzy app to the article download so that you can have fun with it.

Text Display

Before displaying the text in Direct2D, the parameters, such as font name, font size, italic and bold, must be specified in a IDWriteTextFormat object. GetTextFormat() creates an IDWriteTextFormat object based on those parameters.

C++
ComPtr<IDWriteTextFormat> textFormat = GetTextFormat(m_FontFamily, 
    m_FontSize * m_DPIScale, m_Italic, m_Bold, m_Centerize, m_Centerize);

DrawText(m_DCTarget.Get(), textFormat.Get(), m_Text);

The DrawText() creates a rect from the size of the render target and calls DrawTextW() with the text and textFormat. There is an overloaded function of DrawTextW() that takes in a point, instead of a rectangle, as a starting location to display the text.

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

As I previously mentioned, the GetTextFormat() must be called to create TextFormat before DrawText().

C++
ComPtr<IDWriteTextFormat> GetTextFormat(const CString& fontname, float fontSize,
    bool italic, bool bold, bool centerHorizontal, bool centerVertical)
{
    ComPtr<IDWriteTextFormat> 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;
}

For the italic text, we can choose between DWRITE_FONT_STYLE_ITALIC or DWRITE_FONT_STYLE_OBLIQUE. What is the difference between these two styles? Well, oblique just takes in normal font and slants it by applying the skew transformation while italic is a font that is specially crafted to be slanted. As the old saying goes, a picture speaks a thousand words, please refer to the image below (Courtesy of Advanced Typography Pluralsight course by Pariah Burke). The text on the left shows the difference between a normal and italic font while the right shows the difference between a normal and oblique style. As you can see, the italic characters f,i,e,a are different and the oblique characters are just slanted versions.

italics_vs_oblique

Text Measurement

Sometimes, it is necessary to measure the length and height of the text before it is displayed. For example, when displaying text in a button, we want to adjust the button dimensions to accommodate the text. If the width of the given size is insufficient to hold the text in a single line, the height will be adjusted to break the text to fit in the next line or multiple lines. Please note this GetTextSize() is not in the source code download. You can copy GetTextSize() from the code snippet below.

C++
HRESULT GetTextSize
(const WCHAR* text, IDWriteTextFormat* pTextFormat, D2D1_SIZE_F& size)
{
    HRESULT hr = S_OK;
    ComPtr<IDWriteTextLayout> pTextLayout;
    float floatDim = static_cast<float>(m_Dim);
    // Create a text layout 
    hr = FactorySingleton::GetDWriteFactory()->CreateTextLayout(text, 
        static_cast<UINT32>(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;
}

Font Enumeration

Whenever we display text with a particular font, we must ensure that the font exists on the user system. That can be accomplished by enumerating the fonts on the system. These are the structures we needed for the font enumeration. For brevity, the constructors, setters and getters of the structures are not shown.

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<FontSubType> m_SubTypes;
    UINT32 m_StartY;
    UINT32 m_Height;
};

The EnumFont() takes care of font enumeration for you.

C++
void EnumFont(std::vector< std::shared_ptr<FontInfo> >& vecFont)
{
    if (vecFont.empty() == false)
        return;

    ComPtr<IDWriteFontCollection> fontCollection;
    HR(FactorySingleton::GetDWriteFactory()->GetSystemFontCollection(
        fontCollection.GetAddressOf(),
        TRUE
    ));

    UINT32 familyCount = fontCollection->GetFontFamilyCount();
    for (UINT32 i = 0; i < familyCount; ++i)
    {
        ComPtr<IDWriteFontFamily> fontFamily;
        HR(fontCollection->GetFontFamily(i, fontFamily.GetAddressOf()));
        if (!fontFamily)
            continue;

        ComPtr < IDWriteLocalizedStrings> names;
        HR(fontFamily->GetFamilyNames(names.GetAddressOf()));
        WCHAR wname[100];
        UINT32 localizedCount = names->GetCount();

        std::shared_ptr<FontInfo> info = std::make_shared<FontInfo>();

        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<FontSubType> vecSubNames;
        vecSubNames.reserve(fontCount);
        for (UINT32 j = 0; j < fontCount; ++j)
        {
            ComPtr<IDWriteFont> font;
            HR(fontFamily->GetFont(j, font.GetAddressOf()));
            if (!font)
                continue;

            ComPtr < IDWriteLocalizedStrings> 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<FontInfo>& a, const std::shared_ptr<FontInfo>& b) 
        {return a->OriginalName() < b->OriginalName(); });
}

The selected font and its subtypes are shown below. Subtypes deals with italic and bold types of a font family. The subtypes are not useful to me because I am unclear about some subtype names. And Microsoft Office does not expose the font subtypes for its user to select from.

font_subtype

font_subtype_arial

Do Not Mix and Match DirectWrite and GDI+ Font Enumeration

If you use DirectWrite font enumeration, you should display the text with DirectWrite, not GDI+. Why? You may ask. One good reason is if you choose Cooper font returned from DirectWrite font enumeration, GDI+ cannot display that font while DirectWrite has no problem. GDI+ font enumeration returns Cooper Black font which it can display. Strictly speaking, Cooper Black is a bold subtype of Cooper font family. This is a quirk of how Microsoft classified its font types under two different graphical technologies.

History

  • 11th March 2023: Added more information about width of size parameter of GetTextSize() in Text Measurement section.
  • 14th January 2023: First release

Articles in the Series

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Singapore Singapore
Shao Voon is from Singapore. His interest lies primarily in computer graphics, software optimization, concurrency, security, and Agile methodologies.

In recent years, he shifted focus to software safety research. His hobby is writing a free C++ DirectX photo slideshow application which can be viewed here.

Comments and Discussions

 
QuestionSo helpful Pin
Steven Kendrick17-Feb-23 19:43
Steven Kendrick17-Feb-23 19:43 
QuestionIDWriteInlineObject Pin
Pedro Gil Valero10-Feb-23 21:37
Pedro Gil Valero10-Feb-23 21:37 
AnswerRe: IDWriteInlineObject Pin
Shao Voon Wong11-Feb-23 20:43
mvaShao Voon Wong11-Feb-23 20:43 
GeneralRe: IDWriteInlineObject Pin
Pedro Gil Valero12-Feb-23 8:18
Pedro Gil Valero12-Feb-23 8:18 
QuestionExcellent! Pin
Pedro Gil Valero17-Jan-23 3:12
Pedro Gil Valero17-Jan-23 3:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.