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

3
我希望了解计算正确行高(即相邻两行文本基线之间的垂直距离)所使用的指标。在这里,“正确”将被任意定义为“DrawTextW所做的任何事情”。看起来这里接受的答案遵循了MSDN文章中提供的图表内容
TEXTMETRICW.tmHeight + TEXTMETRICW.tmExternalLeading;

但这似乎不正确。使用两个由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);

期望的返回值应该是 2 * <SomethingUnknown>
一个观察结果是,如果使用了 DT_CALCRECT,则 DrawTextW 的返回值将始终与 RECT 输出匹配,对于我机器上拥有的所有字体都是如此。因此,我假设使用 DT_CALCRECT 不会比使用 DrawTextW 的返回值提供任何附加值。
对于我机器上的所有字体,以下条件成立:
  • HeightChinese == HeightLatin
  • LOGFONTW.lfHeight == TEXTMETRICW.tmHeight (1)
对于我机器上的大多数字体,以下条件成立:
  • HeightXxx == 2 * TEXTMETRICW.tmHeight
这已经与其他问题中提供的公式相矛盾TEXTMETRICW.tmExternalLeading 不起作用)。
例如,"Arial" 字体的 LOGFONTW.lfHeight = 36 将具有 TEXTMETRICW.tmExternalLeading = 1HeightXxx == 72(而不是74)。在截图并测量像素时,行之间的距离也为 72(因此似乎可以信任返回值)。
同时,“Segoe UI” 字体的 LOGFONTW.lHeight = 43 将具有 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

有时返回值比计算值大2,有时小2。
我查看了TEXTMETRICW中的其他值,也查看了OUTLINETEXTMETRICW中可用的额外数据,但我找不到任何可以解释这些观察结果的模式。
那么,正确的度量标准是什么来计算行高?我知道我可以使用DT_CALCRECT调用DrawTextW来获取此值,但我想了解此信息来自何处(因此,字体设计师如何以可预测的方式控制它)。 这里有一个完整的Windows应用程序的Gist,演示了这个问题。所有有趣的东西都在WM_PAINT中。搜索@EDIT以获取一些有趣的代码开关和断点。 发布此问题时,我的GitHub帐户已被标记,Gist暂时不可用。我希望这能尽快解决。

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

2个回答

3

正如Jonathan Potter所指出的,公式TEXTMETRICW.tmHeight应该是正确的,如果设置了DT_EXTERNALLEADING标志,则为TEXTMETRICW.tmHeight + TEXTMETRICW.tmExternalLeading

我使用Ghidra对DrawTextExW进行了反向工程,发现有时数字不准确并不是由于DrawTextExW本身。 DrawTextExW内部使用了DT_InitDrawTextInfo,而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;
}

例如,在Segoe UI字体中,LogFont->lfHeight的值为43。

因此,TextMetric->tmHeight也将是43,你可能会认为这在某种程度上是有意义的。

然而:

如果你将此LogFont选择到HDC中,并使用GetTextMetricsW,则结果并非如此:

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

即使LogFont->lfHeight == 43TextMetric->tmHeight的值仍为42。

换句话说,不能信任EnumFontFamiliesExW回调的TEXTMETRICW参数所提供的值。虽然你可以争论错误出现在其他地方,选择一个LogFont->lfHeight == 43字体应该真正产生一个TextMetric->tmHeight == 43文本度量,但我认为这太过苛求了。我猜测在这里发生了浮点数转换,并且偶尔会对某些数字产生舍入误差。


2

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

行高公式基本上是:

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

虽然这对我确实非常有帮助(我发誓我检查了标志 - 一定是漏掉了这个),但它并没有解释我观察到的差异来自哪里。有趣的是,如果您想让文本看起来像字体设计师预期的那样,似乎基本上应该始终包括此标志... - dialer
1
我使用Ghidra对DrawTextExW进行了逆向工程,并找到了问题所在。你写的是正确的。这似乎是EnumFontFamiliesExW中的一个“bug”。我将发布一份更详细的答案,附带一些额外的信息。 - dialer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接