nuvegoyora
nuvegoyora

Reputation: 43

Incorrect metrics and sizes of font created by CreateFont()

I trying to render a font into bitmap using WinAPI, but I can't reach needed sizes of font.

Here's how the font is initialized:

HDC dc = ::CreateCompatibleDC(NULL);
::SetMapMode(dc, MM_TEXT);
::SetTextAlign(dc, TA_LEFT | TA_TOP | TA_UPDATECP);
int size_in_pixels = 18;
HFONT font = ::CreateFontA(-size_in_pixels, ..., "Arial");
::SelectObject(dc, font);
::TEXTMETRICW tm = { 0 };
GetTextMetricsW(dc, &tm);

But after it I getting incorrect values both in GetGlyphOutlineW and GetTextMetricsW, it's not size I passed as parameter I know that it's expecting value in logical units, but in MM_TEXT 1 unit should be 1 pixel, don't it?

I expecting that CreateFontA accepting point size when I passing a negative value (like here https://i.sstatic.net/tEt8J.png), but in fact it's wrong. I tried bruteforcing values, and find out proper parameter for a few sizes:

18px = -19; 36px = -39; 73px = -78;

Also I tried formula that provided by Microsoft:

nHeight = -MulDiv(PointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);

But it's also giving me a wrong result, rendered text (using GetGlyphOutlineW) is larger if measure it (for example height of 'j' should have exact size that I passed) Also metrics from GetTextMetricsW are wrong, for example tmAscent. I know that on Windows it's including internal leading, but even if subtract tmInternalLeading from tmAscent it's still incorrect. By the way, values from GetCharABCWidthsW are correct, so a+b+c is width of glyph in pixels (while documentation says it should be in logical units).

Also I should say about DPI, usually I using 125% on Windows 10 scale in settings, but I tried even with 100%, interesting that ::GetDeviceCaps(dc, LOGPIXELSY) not changing with scale I using, it's always 96

Here's example of CreateFontA(-128, ...) with final atlas and metrics: rendered atlas

Question #1: What should I do to pass wanted point size in pixels and receive glyphs in proper size with correct metrics in pixels?

Question #2: What the strange units all these functions are using?

Upvotes: 3

Views: 2018

Answers (1)

Daniel Sęk
Daniel Sęk

Reputation: 2769

When you use ::SetMapMode(dc, MM_TEXT); the font size is specified in device pixels. Negative value excludes internal leading, so for the same absolute value the negative ones produce visually bigger fonts. If you want to get same height from GetTextExtentPoint32 for different fonts, use positive values.

In your example with -128 height, you are requesting font for which, after internal leading exclusion, height is 128 pixels. Font mapper selects 143 which is correct for internal leading of 15 pixels (128+15=143). tmAscent + tmDescent are also correct (115+28=143). You get what you specified.

You should take into account that values in text metric don't state hard bounds. Designer can design fonts so its glyphs sometimes go beyond guiding lines or don't reach them.

for example height of 'j' should have exact size that I passed

Dot over j can go beyond or not reach top line if designer finds it visually plausible to design it that way.

interesting that ::GetDeviceCaps(dc, LOGPIXELSY) not changing with scale I using, it's always 96

Unless you log off and log in, system DPI doesn't change. For per monitor DPI aware application you have to get DPI from monitor parameters or cache value given by WM_DPICHANGED.

Question #1: What should I do to pass wanted point size in pixels and receive glyphs in proper size with correct metrics in pixels?

I think you want to get specific distance between top and bottom lines and this is exactly how you create font HFONT font = ::CreateFontA(-size_in_pixels, ..., "Arial");. The problem lies in your assumption that font design lines are hard boundaries for each glyph, but font's designer don't have to strictly align glyphs to these lines. If you want glyphs strictly aligned, probably there is no way to get it. Maybe check different font.

Question #2: What the strange units all these functions are using?

When mode is set to WM_TEXT, raw device pixels are used. Positive height specifies height including tmInternalLeading, negative excludes it.

For positive value:
tmAscent + tmDescent = requestedHeight
For negative value:
tmAscent + tmDescent - tmInternalLeading = requestedHeight

Bellow I have pasted screen shots with different fonts showing that depending on selected font glyphs could be designed so they don't reach top line or go beyond it and bottom line in most cases also isn't reached.

Seems that for your requirements Arial Unicode MS would be better fit (but j still doesn't reach where you want it).

Arial:
enter image description here

Arial Unicode MS
enter image description here

Input Mono
enter image description here

Trebuched MS
enter image description here

Upvotes: 2

Related Questions