Search code examples
windowswinapifontsgdi

Get GDI HFONT line height as interpreted by DrawText[Ex]


I want to know which metrics are used to calculate the correct line height (vertical distance between the baselines of 2 adjacent lines of text). "Correct" shall arbitrarily defined as "whatever DrawTextW does".

The accepted answer here appears to follow what the graph provided in this MSDN article says:

TEXTMETRICW.tmHeight + TEXTMETRICW.tmExternalLeading;

But that does not appear to be correct. Some testing with 2 pieces of text, each of which consists of 2 lines:

// RECT rc is more than large enough to fit any text
int HeightChinese = DrawTextW(hdc, L"中\r\n文", -1, &rc, 0);
int HeightLatin = DrawTextW(hdc, L"Latin,\r\nlatin!", -1, &rc, 0);

The expected return values should be 2 * <SomethingUnknown>.

One observation is that the return value of DrawTextW will always match the RECT output if DT_CALCRECT were used, for all fonts that I have on my machine. So I will assume that using DT_CALCRECT does not provide any additional value over using the return value of DrawTextW.

For all fonts on my machine, these are true:

  • HeightChinese == HeightLatin
  • LOGFONTW.lfHeight == TEXTMETRICW.tmHeight (1).

For most fonts on my machine, this is true:

  • HeightXxx == 2 * TEXTMETRICW.tmHeight

Which already contradicts the formula provided in the other question (TEXTMETRICW.tmExternalLeading does not play a role).

For example, "Arial" with LOGFONTW.lfHeight = 36 will have TEXTMETRICW.tmExternalLeading = 1, and HeightXxx == 72 (not 74). The distance between the lines when taking a screenshot and measuing the pixels is also 72 (so it appears that the return value can be trusted).

At the same time, "Segoe UI" with LOGFONTW.lHeight = 43 will have TEXTMETRICW.tmExternalLeading = 0, and HeightXxx == 84 (not 86).

This is a list of all anomalous fonts on my system:

"FontName" -- "DrawText return value" vs "2 * TEXTMETRICW.tmHeight"

Ebrima -- 84 vs 86
Leelawadee UI -- 84 vs 86
Leelawadee UI Semilight -- 84 vs 86
Lucida Sans Unicode -- 96 vs 98
Malgun Gothic -- 84 vs 86
Malgun Gothic Semilight -- 84 vs 86
Microsoft Tai Le -- 80 vs 82
Microsoft YaHei -- 82 vs 84
Microsoft YaHei UI Light -- 82 vs 84
MS Gothic -- 66 vs 64
MS UI Gothic -- 66 vs 64
MS PGothic -- 66 vs 64
Nirmala UI -- 84 vs 86
Nirmala UI Semilight -- 84 vs 86
Palatino Linotype -- 84 vs 86
Segoe UI -- 84 vs 86
Segoe UI Black -- 84 vs 86
Segoe UI Historic -- 84 vs 86
Segoe UI Light -- 84 vs 86
Segoe UI Semibold -- 84 vs 86
Segoe UI Semilight -- 84 vs 86
Segoe UI Symbol -- 84 vs 86
SimSun -- 66 vs 64
NSimSun -- 66 vs 64
SimSun-ExtB -- 66 vs 64
Verdana -- 76 vs 78
Webdings -- 62 vs 64
Yu Gothic UI -- 84 vs 86
Yu Gothic UI Semibold -- 84 vs 86
Yu Gothic UI Light -- 84 vs 86
Yu Gothic UI Semilight -- 84 vs 86
MS Mincho -- 66 vs 64
MS PMincho -- 66 vs 64
Ubuntu Mono -- 62 vs 64

Sometimes the return value is 2 bigger, sometimes it is 2 smaller than the calculated value.

I have looked at the other values in TEXTMETRICW, and I've also looked at the extra data available int OUTLINETEXTMETRICW, but I could not find any pattern that would explain the observations.

So then, what are the correct metrics to calculate line height? I understand that I could call DrawTextW with DT_CALCRECT to get this value, but I want to understand where this information comes from (and thus, how a font designer could control it in a predictable way).

Here is a gist with a complete Windows application that demonstrates this. All the interesting stuff is in WM_PAINT. Search for @EDIT for some interesting code switches and breakpoints. At the time of posting this question, my GitHub account has been flagged, and the Gist is temporarily unavailable. I hope this can be resolved quickly.

(1) I am using EnumFontFamiliesEx to enumerate all fonts, and it happens to provide LOGFONTW structs with positive lfHeight values. That means I am using cell height rather than character height. While character height is the more typical way of specifying font height, that is sort of irrelevant here, it just so happens that cell height is equal to TEXTMETRICW.tmHeight, but character height isn't. The relevant value for calculations is TEXTMETRICW.tmHeight, and not LOGFONTW.lfHeight.


Solution

  • DrawText() only uses TEXTMETRIC.tmExternalLeading if the DT_EXTERNALLEADING flag is set when you call it - you don't seem to have taken that into account.

    The line height formula is basically:

    int iLineHeight = tm.tmHeight + ((format & DT_EXTERNALLEADING) ? tm.tmExternalLeading : 0);