c++winapidirectwrite

Calculate font "size" in DIPs of a system font


I have few BS_OWNERDRAWN buttons and using Direct2D and Direct Write to draw them.

I also need to draw button text within button rectangle, for this I use IDWriteTextFormat which requires to specify "font size" in DIPs (device independent pixels).

I want font size in those buttons to be of same size as other non owner drawn common controls or same as system font that is present in window caption bar.

Following code is "chopped out" version to present my workaround which of course doesn't give expected results because I get LOGFONT structure of a font in caption bar which gives me the width of a font (single character) but not font size that the IDWriteTextFormat expects to specify font size in DIPs.

class CustomControl
{
protected:
    /** Caption text format used to draw text */
    CComPtr<IDWriteTextFormat> mpTextFormat;

    /** Caption font size (button text size) */
    float mFontSize;
};

// Calculate caption bar (default) font size of a top level window
void CustomControl::CalculateFontSize()
{
    NONCLIENTMETRICSW metrics;
    metrics.cbSize = sizeof(NONCLIENTMETRICSW);
    SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, metrics.cbSize, &metrics, 0);

    LOGFONTW font = metrics.lfCaptionFont;
    mFontSize = static_cast<float>(font.lfHeight);
}

// Create text format that is of same font size as default system font
HRESULT CustomControl::CreateTextFormat()
{
    HRESULT hr = S_OK;

    if (!mpTextFormat)
    {
        CalculateFontSize();

        hr = mpWriteFactory->CreateTextFormat(
            L"Arial",
            NULL,
            DWRITE_FONT_WEIGHT_NORMAL,
            DWRITE_FONT_STYLE_NORMAL,
            DWRITE_FONT_STRETCH_NORMAL,
            mFontSize,  // <-- Specifies font size in DIP's..
            L"en-us",
            &mpTextFormat);
    }

    return hr;
}

Here is test program that shows the differences in font size between default system font in window caption and custom button text below.

enter image description here

I need help in figuring out how to correctly calculate font size for IDWriteTextFormat parameter to be of same size as text in other common controls that are not BS_OWNERDRAW or in this example default font in window caption.

EDIT:

I figured out issue is in my CalculateFontSize() I wrote mFontSize = static_cast<float>(font.lfHeight); but this is negative number so appending - sign gives the expected result:

mFontSize = static_cast<float>(-font.lfHeight);

Why negative? I'm not sure yet but this answer helped:

How to set font size using CreateFontA?

Now my question remains in that how should I update CalculateFontSize() so that it gets font size of common controls that are not BS_OWNERDRAW instead of a window caption bar font size?


Solution

  • I figured out to calculate font size of other common controls to be used for IDWriteTextFormat the formula is simple:

    float CustomControl::CalculateFontSize()
    {
        const long units = GetDialogBaseUnits();
        const DWORD height = HIWORD(units);
        return static_cast<float>(height);
    }
    

    Only problem with this is if you use custom font for common controls, or if your dialog uses different font then you need to update your CalculateFontSize() to take these changes into account.

    However for your TextFormat to be truly consistent with native common controls you also need to apply font weight (boldness), for example after you create TextLayout (by using your TextFormat):

        std::size_t caption_len = 0;
        StringCchLengthW(mCaption, STRSAFE_MAX_CCH, &caption_len);
    
        DWRITE_TEXT_RANGE range = { 0u, caption_len };
    
        mpTextLayout->SetFontSize(mFontSize, range);
        mpTextLayout->SetFontWeight(DWRITE_FONT_WEIGHT::DWRITE_FONT_WEIGHT_BOLD, range);