如何在Windows中使用DrawText找到文本渲染的准确像素高度

10

我希望找到Windows下文本渲染的确切高度。我尝试了GetTextExtentPoint32和使用DT_CALCRECT标记调用DrawText,两者给出的结果都相同。

看起来返回的高度是基于整个单元格高度,而不管实际绘制的文本。

下面的代码是标准Visual Studio 2013 Win32项目的WM_PAINT处理程序。它创建一个(大号)字体并绘制示例文本。文本的最高部分为98像素,但GetTextExtentPoint32返回的值为131。

我知道有些应用程序可能需要全单元格高度,但也有一些应用程序(比如我的)只想要文本使用的实际高度。

是否有人知道如何查找此信息?

是的,我可以呈现到内存DC并向下扫描寻找第一个非背景颜色的像素-但这将变得超级缓慢。

谢谢

case WM_PAINT:
{
    hdc = BeginPaint (hWnd, &ps);

    HFONT hfont = CreateFont (-99, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, VARIABLE_PITCH, L"Segoe UI Semibold");
    auto old_hfont = SelectObject (hdc, hfont);

    wchar_t sample_text[] = L"123 Testing 123";
    size_t sample_text_length = wcslen (sample_text);

    SIZE s;
    GetTextExtentPoint32 (hdc, sample_text, sample_text_length, &s);

    RECT r = {10, 10, 10 + s.cx, 10 + s.cy};

    SetBkColor   (hdc, RGB (80, 120, 160));
    SetTextColor (hdc, RGB (220, 220, 220));

    DrawText (hdc, sample_text, sample_text_length, &r, DT_SINGLELINE | DT_NOPREFIX | DT_LEFT | DT_TOP);

    SelectObject (hdc, old_hfont);
    DeleteObject (hfont);

    EndPaint (hWnd, &ps);
    break;
}

2
在抗锯齿字体上,你的确切高度是多少? - RedX
DrawText操作写入的光栅线总数是多少?我的意思是第一行和最后一行写入的高度。 - Anthony Wild
嗨。是的,我有一个包含水平信息轨道的主窗口。每个轨道都包含一个或多个命名的数据区域。我想在每个区域的左上角显示名称,但目前返回的高度包括未使用的像素,浪费了很多像素,留下的空间不足以显示实际数据。虽然用户可以缩放(水平和垂直),但数据区域通常只有大约20个像素高 - 因此在文本顶部浪费三到四个像素的空白空间并不好。 - Anthony Wild
1
当你尝试解决这些问题时,你会发现自己正在编写代码,该代码表面上可以执行呈现文本所需的所有逻辑,包括所有字距和偏移量以及其他排版魔法(包括在希伯来语和其他语言中发现的重叠字符的所有魔法)。你将花费一生的时间来尝试做到完美。我建议只使用DT_CALCRECT。你的结果将是一致的,周围有干净的填充,并且只需要一行代码而不是数千行。 - cppguy
2
有了像安东尼和马克这样的名字,我猜你是想消除英文文本上方似乎很大的白色空隙。它为重音符号和变音符号预留了位置,在英文文本中(几乎)不存在。在示例代码中占用32个像素,非常显眼。只需从Y位置中减去TEXTMETRICS.tmInternalLeading即可。小心不要在其上方过度绘制任何内容。 - Hans Passant
显示剩余4条评论
4个回答

8

4

您的文本不是直接绘制的,首先它会成为一个path,该路径描述了几何/字形的轮廓。路径由移动、线和曲线(以及与上一个对象相连的结束标志)组成。除曲线外,其他路径段都是最终填充的极点。将曲线转换为线并遍历所有路径段,通过点在水平和垂直维度上找到最小值和最大值,得到与您的文本最接近的拟合矩形。

你可以通过在调用 BeginPath 前和 EndPath 后绘制文本来将其转换为路径。FlattenPath 将曲线转换为直线。GetPath 可以访问上下文中的路径点。最后,AbortPath 可以从上下文中删除路径。
当您未将背景设置为透明时,路径可能是文本周围的背景,而第一个路径段已经是背景/范围矩形线 - 不是您想要的。
为了简化此方法,您可以通过以下方式排除重复字符并将字符分组:1)基线以下 2)中线以上 3)其余。所有这些都比您的“查看像素”尝试更快。
关于文本大小的其他有用来源是字体度量(GetTextMetrics)和字符宽度(GetCharABCWidths)。

3
如果你的样本文本只包含ASCII字母,你可以手动评估一个上升字母(如b),一个下降字母(如g)和一个中间字母(如x)的高度。你可以提前完成这个过程,可能是离线的,所以任何低效的方法都没关系。
接下来(根据字体,有一定的误差范围),计算整体高度就是一个简单的检查你的字符串中是否包含上升部分(bdfhijkltA-Z)和下降部分(gjpqy)。

但这就是问题所在 - 当Windows API将所有字符视为具有相同高度时,您如何“手动评估字母的高度”? - Mark Ransom
1
@MarkRansom 通过不高效地扫描就像原帖中提到的那样。这三个高度将被预先计算一次,甚至可能是离线的,因此低效并不会造成任何伤害。 - klimpergeist

2
如果这是一个TrueType字体,您可以在fmtx表中找到字形相对于“前一个”字母和行的定位大小,如此处所解释。对于OpenType字体,也有类似的信息可用,但推导出有意义的东西看起来更加麻烦。
这并没有给出字形的实际像素大小,但它确实给出了一个更准确的想法,即特定字形的TrueType字体路径相对于“基线”的绘制位置,并且单位与点大小和字体宽度成比例。
在您的情况下,我建议查看FreeType,这是一个第三方库,快速支持光栅字体、TrueType和OpenType字体。它被广泛使用,非常文档完善和支持,并且可以提供准确的度量。

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