2

The CreateTextFormat method expects a fontFamilyName parameter. How do I create an IDWriteTextFormat that uses the default UI font?

Alex K.
  • 171,639
  • 30
  • 264
  • 288
eyelash
  • 3,197
  • 25
  • 33
  • Do you mean the system font with "default UI font" or the font which is used in your application? – realvictorprm Jan 06 '17 at 12:00
  • I mean the system font (the font that is used for UI elements on native windows applications) – eyelash Jan 06 '17 at 12:05
  • 3
    Call SystemParametersInfo w/ SPI_GETNONCLIENTMETRICS and use the message box font returned therein. – Alex K. Jan 06 '17 at 12:05
  • Just wanted to say that too – realvictorprm Jan 06 '17 at 12:08
  • This looks like the thing I was looking for. If you can make it an answer instead of a comment I can accept it – eyelash Jan 06 '17 at 12:10
  • Look whether it gives you a idea how to implement it – realvictorprm Jan 06 '17 at 12:35
  • 2
    I assume you already have the UI font from `SystemParametersInfo()`; I'm not sure what UI font you were using before. That being said, once you have the `LOGFONT`, pass it to [`IDWriteGdiInterop::CreateFontFromLOGFONT()`](https://msdn.microsoft.com/en-us/library/windows/desktop/dd371187(v=vs.85).aspx) to get an IDWriteFont. From there, you can extract most of the parameters for `CreateTextFormat()`. The only thing it does not give you is the font size, which is a bit more complicated because the font size is stored in a device-*dependent* way in the `LOGFONT` structure. – andlabs Jan 06 '17 at 13:07
  • I thought I figured this out already for the case of the `SPI_GETNONCLIENTMETRICS` fonts, but I don't remember how... – andlabs Jan 06 '17 at 13:07
  • Okay judging from .net it seems `LOGPIXELSY` is taken from the screen DC (`GetDC(NULL)`). [The code seems to use 96 instead of 72 in the standard lfHeight calculation, though](https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/SystemFonts.cs); is that a WPF-ism? Very weird... Not sure if this is fully correct, but `GetDC(NULL)` seems the most reasonable. @eyelash since you marked the answer as an answer already, this is a highlight to bring your attention to the methods that already exist in DirectWrite to use LOGFONTs. – andlabs Jan 06 '17 at 14:28
  • @andlabs for my purpose I need a custom size anyway so my question is already answered but thanks for digging and I hope it will help someone else. – eyelash Jan 06 '17 at 14:31
  • [System.Drawing](https://referencesource.microsoft.com/#System.Drawing/commonui/System/Drawing/SystemFonts.cs,72c054a9b537283e) also uses `GetDC(NULL)`, so I guess that *is* correct. Not sure if GDI+ uses 72 or 96, though, but I imagine it uses the former assuming 96 is really a WPF-ism. I'd need to write more tests to make sure. – andlabs Jan 06 '17 at 15:13

1 Answers1

3

Please note, that all code here is done without any checks (too many methods here return HRESULT, would blow this example up!).

For acquiring the system wide font you should use this:

(This is from another stackoverflow question!)

NONCLIENTMETRICS ncm;
ncm.cbSize = sizeof(ncm);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0);
HFONT hFont = CreateFontIndirect(&(ncm.lfMessageFont)); //

for paint now use this:

HDC hdc = BeginPaint(...); //Creates a device context
SelectObject(hdc, hFont);
//Your font is now set for the current device context
//do something

DeleteObject(hFont); //Don't forget to do this at the end!

A bit changed from this question!

This solution is really raw and in my opinion ugly.

Alternative solution do get the IDWriteFont (looks ugly but is fine):

//just the same as above except the hfont, instead use
NONCLIENTMETRICS ncm;

ncm.cbSize = sizeof(ncm);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0);

IDWriteFactory *dwriteFactory_;
DWriteCreateFactory(
    DWRITE_FACTORY_TYPE_SHARED,
    __uuidof(IDWriteFactory),
    reinterpret_cast<IUnknown**>(&dwriteFactory_)
);

IDWriteGdiInterop* gdiInterop = NULL;
dwriteFactory_->GetGdiInterop(&gdiInterop);

IDWriteFont* sys_font = nullptr;
gdiInterop->CreateFontFromLOGFONT(&ncm.lfMessageFont, &sys_font); //Now we have it!

//The text format can now be aquired like this
//We need the font family of our font
IDWriteFontFamily* family = nullptr;
sys_font->GetFontFamily(&family);

//Now we have to get the "localized" name of our family
IDWriteLocalizedStrings* font_family_name = nullptr;
family->GetFamilyNames(&font_family_name);
UINT32 index = 0;
UINT32 length = 0;
BOOL exists = false;
font_family_name->FindLocaleName(L"en-us", &index, &exists);
font_family_name->GetStringLength(index, &length);
wchar_t* name = new wchar_t[length + 1];
font_family_name->GetString(index, name, length + 1);
wprintf(L"%s\n", name);

//Some user defined stuff
DWRITE_FONT_WEIGHT font_weight = DWRITE_FONT_WEIGHT_BLACK;
DWRITE_FONT_STYLE font_style = DWRITE_FONT_STYLE_ITALIC;
DWRITE_FONT_STRETCH font_stretch = DWRITE_FONT_STRETCH_EXPANDED;

IDWriteTextFormat* text_format = nullptr;
dwriteFactory_->CreateTextFormat(name, nullptr, font_weight, font_style, font_stretch, 10.0, L"en-us", &text_format);

Even without checks, code runs on my computer without any problems and gives me the same result as the first solution (Windows 10, Font family name is Segoe UI).

Sources: General Microsoft DirectWrite API documentation

CreateIndirectFont Documentation

How to enumerate font families, Microsoft documentation

Community
  • 1
  • 1
realvictorprm
  • 614
  • 6
  • 20
  • 2
    This does not answer the question; that uses GDI and not DirectWrite. – andlabs Jan 06 '17 at 13:01
  • The first half of the answer is still right, but now I wonder why the OP wasn't aware of it... Using this with DirectWrite is a bit more complicated. – andlabs Jan 06 '17 at 13:05
  • Yeah I saw too. Then I have to correct the last halft, thx for info – realvictorprm Jan 06 '17 at 13:06
  • I'm new to Windows development and before porting to Direct2D and DirectDraw I was using `Gdiplus::FontFamily::GenericSansSerif()` – eyelash Jan 06 '17 at 13:21
  • One little thing missing from this answer to be complete is how to create an IDWriteTextFormat from the IDWriteFont – eyelash Jan 06 '17 at 14:43
  • done complete. Just don't forget to always make checks. I haven't done such.And yes this stuff is definitely really hard to understand because it's sooooo long to write D: – realvictorprm Jan 06 '17 at 15:11
  • You should probably use the `IDWriteFontCollection` from the `IDWriteFontFamily` object. I'm not sure if this will ever not be the system font collection for the nonclient metrics fonts, but better safe than sorry. – andlabs Jan 06 '17 at 15:14
  • Haha @andlabs still following this stuff here :D Wouldn't it be really strange if the font of the system isn't in the system font collection? And I want to mention. I looked it self up, didn't read your comment, just thought as I read now "whuat there is the solution too xD". I'll give some sources where the information about this stuff is from. – realvictorprm Jan 06 '17 at 15:17
  • 1
    I just read documentation again. With this method there must be a system font collection with this font family because the method `CreateFontFromLOGFONT(...)` relies on that. So this is fine! – realvictorprm Jan 06 '17 at 15:22
  • 1
    The IDWriteGdiInterop::CreateFontFromLOGFONT documentation is pretty light on details about what assumptions is makes when converting the lfHeight and lfWidth fields in the LOGFONT from logical units to DIPs (and whether those assumptions work regardless of the process's DPI awareness setting). – Adrian McCarthy Jan 06 '17 at 19:04