获取 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_CALCRECT
,DrawTextW
的 return 值将始终匹配 RECT
输出,对于我机器上的所有字体.所以我假设使用 DT_CALCRECT
与使用 DrawTextW
.
的 return 值相比不会提供任何额外的价值
对于我机器上的所有字体,这些都是正确的:
HeightChinese == HeightLatin
LOGFONTW.lfHeight == TEXTMETRICW.tmHeight
(1).
对于我机器上的 大多数 字体,这是正确的:
HeightXxx == 2 * TEXTMETRICW.tmHeight
已经和另一个问题中提供的公式相矛盾了(TEXTMETRICW.tmExternalLeading
没有起作用)
例如,带有 LOGFONTW.lfHeight = 36
的“宋体”将有 TEXTMETRICW.tmExternalLeading = 1
和 HeightXxx == 72
(不是 74)。截图和测量像素时的线间距也是72(所以看来return值可以相信)。
同时,带有LOGFONTW.lHeight = 43
的“Segoe UI”将有TEXTMETRICW.tmExternalLeading = 0
和HeightXxx == 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 是指定字体高度的更典型的方式,但在这里有点无关紧要,恰好 [=77=]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
文本规格,但我想这要求太高了。我的猜测是其中某处正在进行浮点转换,偶尔会对某些数字产生舍入误差。
我想知道哪些指标用于计算正确的行高(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_CALCRECT
,DrawTextW
的 return 值将始终匹配 RECT
输出,对于我机器上的所有字体.所以我假设使用 DT_CALCRECT
与使用 DrawTextW
.
对于我机器上的所有字体,这些都是正确的:
HeightChinese == HeightLatin
LOGFONTW.lfHeight == TEXTMETRICW.tmHeight
(1).
对于我机器上的 大多数 字体,这是正确的:
HeightXxx == 2 * TEXTMETRICW.tmHeight
已经和另一个问题中提供的公式相矛盾了(TEXTMETRICW.tmExternalLeading
没有起作用)
例如,带有 LOGFONTW.lfHeight = 36
的“宋体”将有 TEXTMETRICW.tmExternalLeading = 1
和 HeightXxx == 72
(不是 74)。截图和测量像素时的线间距也是72(所以看来return值可以相信)。
同时,带有LOGFONTW.lHeight = 43
的“Segoe UI”将有TEXTMETRICW.tmExternalLeading = 0
和HeightXxx == 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 是指定字体高度的更典型的方式,但在这里有点无关紧要,恰好 [=77=]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
文本规格,但我想这要求太高了。我的猜测是其中某处正在进行浮点转换,偶尔会对某些数字产生舍入误差。