获取 DrawText [Ex] 解释的 GDI HFONT 行高

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

我想知道哪些指标用于计算正确的行高(2个相邻文本行基线之间的垂直距离)。 “正确”应任意定义为“DrawTextW所做的一切”。

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

TEXTMETRICW.tmHeight + TEXTMETRICW.tmExternalLeading;

但这似乎不正确。使用 2 段文本进行一些测试,每段文本由 2 行组成:

// 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);

预期的 return 值应为 2 * <SomethingUnknown>

一个观察结果是,如果使用 DT_CALCRECTDrawTextW 的 return 值将始终匹配 RECT 输出,对于我机器上的所有字体.所以我假设使用 DT_CALCRECT 与使用 DrawTextW.

的 return 值相比不会提供任何额外的价值

对于我机器上的所有字体,这些都是正确的:

对于我机器上的 大多数 字体,这是正确的:

已经和另一个问题中提供的公式相矛盾了TEXTMETRICW.tmExternalLeading没有起作用)

例如,带有 LOGFONTW.lfHeight = 36 的“宋体”将有 TEXTMETRICW.tmExternalLeading = 1HeightXxx == 72(不是 74)。截图和测量像素时的线间距也是72(所以看来return值可以相信)。

同时,带有LOGFONTW.lHeight = 43的“Segoe UI”将有TEXTMETRICW.tmExternalLeading = 0HeightXxx == 84(不是86)。

这是我系统上所有异常字体的列表:

"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

有时return值比计算值大2,有时比计算值小2。

我查看了 TEXTMETRICW 中的其他值,并且还查看了可用的额外数据 int OUTLINETEXTMETRICW,但我找不到任何可以解释这些观察结果的模式。

那么,计算行高的正确指标是什么?我知道我可以用 DT_CALCRECT 调用 DrawTextW 来获取这个值,但我想了解这些信息的来源(以及字体设计师如何以可预测的方式控制它)。

Here is a gist with a complete Windows application that demonstrates this。所有有趣的东西都在 WM_PAINT 中。搜索 @EDIT 以获得一些有趣的代码开关和断点。 在发布此问题时,我的 GitHub 帐户已被标记,Gist 暂时不可用。希望能尽快解决。

(1) 我正在使用 EnumFontFamiliesEx 枚举所有字体,它恰好提供 LOGFONTW 结构 lfHeight 值。这意味着我使用的是 单元格高度 而不是 字符高度 。虽然 character height 是指定字体高度的更典型的方式,但在这里有点无关紧要,恰好 [=7​​7=]cell height 等于TEXTMETRICW.tmHeight,但 字符高度 不是。计算的相关值是 TEXTMETRICW.tmHeight,而不是 LOGFONTW.lfHeight

DrawText() 仅在调用时设置了 DT_EXTERNALLEADING 标志时才使用 TEXTMETRIC.tmExternalLeading - 您似乎没有考虑到这一点。

行高公式基本为:

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

正如 Jonathan Potter 指出的那样,公式 TEXTMETRICW.tmHeight 应该 是正确的,如果设置了 DT_EXTERNALLEADING 标志,那么它是 TEXTMETRICW.tmHeight + TEXTMETRICW.tmExternalLeading.

我用 Ghidra 对 DrawTextExW 进行了逆向工程,数字有时不对的原因不是 DrawTextExW 本身。 DrawTextExW内部使用了一个DT_InitDrawTextInfo,它又使用了GetTextMetricsW,并根据上面的公式计算出行高。

但是,请考虑使用此代码来探测所有字体:

LOGFONTW Probe = {};
Probe.lfCharSet = DEFAULT_CHARSET;
EnumFontFamiliesExW(hdc, &Probe, InitializeFontInfo_EnumFontFamiliesCallback, NULL, 0);

static int CALLBACK InitializeFontInfo_EnumFontFamiliesCallback(const LOGFONTW *LogFont, const TEXTMETRICW *TextMetric, DWORD FontType, LPARAM lParam)
{
    FONT_INFO tmp = {};
    tmp.LogFont = *LogFont;
    tmp.TextMetric = *TextMetric;
    FontInfo.push_back(tmp);
    return 1;
}

这里以SegoeUI字体为例,LogFont->lfHeight就是43.

因此,TextMetric->tmHeight 也将是 43,您会认为这在某种程度上是有道理的。

然而:

如果你继续 select 这个 LogFont 变成 HDC,然后使用 GetTextMetricsW,像这样:

HFONT Font = CreateFontIndirectW(LogFont);
SelectObject(hdc, Font);
TEXTMETRICW TextMetric = {};
GetTextMetricsW(hdc, &TextMetric);

然后 TextMetric->tmHeight == 42 即使 LogFont->lfHeight == 43.

换句话说,提供给 EnumFontFamiliesExW 回调的 TEXTMETRICW 参数的值不可信。尽管您可能会争辩说错误出在其他地方,并且 select 使用 LogFont->lfHeight == 43 字体实际上也应该产生 TextMetric->tmHeight == 43 文本规格,但我想这要求太高了。我的猜测是其中某处正在进行浮点转换,偶尔会对某些数字产生舍入误差。