【问题标题】:Incorrect metrics and sizes of font created by CreateFont()CreateFont() 创建的字体的度量和大小不正确
【发布时间】:2019-12-19 07:11:38
【问题描述】:

我尝试使用 WinAPI 将字体渲染为位图,但无法达到所需的字体大小。

这是字体的初始化方式:

HDC dc = ::CreateCompatibleDC(NULL);
::SetMapMode(dc, MM_TEXT);
::SetTextAlign(dc, TA_LEFT | TA_TOP | TA_UPDATECP);
int size_in_pixels = 18;
HFONT font = ::CreateFontA(-size_in_pixels, ..., "Arial");
::SelectObject(dc, font);
::TEXTMETRICW tm = { 0 };
GetTextMetricsW(dc, &tm);

但之后我在GetGlyphOutlineWGetTextMetricsW 中都得到了不正确的值,这不是我作为参数传递的大小 我知道它的期望值是逻辑单位,但在 MM_TEXT 中,1 单位应该是 1 像素,不是吗?

我希望 CreateFontA 在传递负值时接受点大小(例如这里的 https://i.stack.imgur.com/tEt8J.png),但实际上这是错误的。 我尝试了暴力破解值,并找出了一些尺寸的合适参数:

18px = -19; 36px = -39; 73px = -78;

我也试过微软提供的公式:

nHeight = -MulDiv(PointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);

但这也给了我一个错误的结果,如果测量它,渲染的文本(使用GetGlyphOutlineW)会更大(例如,'j' 的高度应该具有我通过的确切大小) 来自GetTextMetricsW 的指标也是错误的,例如tmAscent。我知道在 Windows 上它包括内部领先,但即使从 tmAscent 中减去 tmInternalLeading 它仍然不正确。 顺便说一句,GetCharABCWidthsW 中的值是正确的,因此 a+b+c 是以像素为单位的字形宽度(而文档中说它应该是逻辑单位)。

另外我应该说一下 DPI,通常我在 Windows 10 的设置中使用 125%,但我什至尝试使用 100%,有趣的是 ::GetDeviceCaps(dc, LOGPIXELSY) 不会随着我使用的比例而改变,它总是 96

下面是 CreateFontA(-128, ...) 的示例,其中包含最终图集和指标: rendered atlas

问题 #1:我应该怎么做才能传递所需的点大小(以像素为单位)并接收具有正确大小的字形(以像素为单位)?

问题 #2:所有这些函数都使用了哪些奇怪的单位?

【问题讨论】:

  • MM_TEXT:每个逻辑单元映射到一个设备像素。为什么DPI总是96,因为你没有将进程的默认DPI感知设置为系统DPI感知。见here。你最好提供更多代码来说明问题。有时一行代码比十句话更有效。

标签: c++ winapi fonts rendering


【解决方案1】:

当您使用::SetMapMode(dc, MM_TEXT); 时,字体大小以设备像素为单位指定。负值不包括内部前导,因此对于相同的绝对值,负值会产生视觉上更大的字体。如果您想从GetTextExtentPoint32 获得不同字体的相同高度,请使用正值。

-128 高度的示例中,您请求的字体在内部前导排除后高度为 128 像素。字体映射器选择 143,这对于 15 像素 (128+15=143) 的内部前导是正确的。 tmAscent + tmDescent 也是正确的 (115+28=143)。你得到你指定的。

您应该考虑到文本度量中的值不表示硬边界。设计师可以设计字体,使其字形有时超出指导线或达不到指导线。

例如,'j' 的高度应该与我通过的精确尺寸一致

j 上的点可能会超出或无法达到顶线,如果设计师认为以这种方式设计它在视觉上是合理的。

有趣的是::GetDeviceCaps(dc, LOGPIXELSY) 不会随着我使用的比例而改变,它总是 96

除非您注销并登录,否则系统 DPI 不会改变。对于每个监视器 DPI 感知应用程序,您必须从监视器参数或 WM_DPICHANGED 给出的缓存值中获取 DPI。

问题 #1:我应该怎么做才能传递所需的点大小(以像素为单位)并接收具有正确大小的字形(以像素为单位)?

我认为您希望获得顶线和底线之间的特定距离,这正是您创建字体HFONT font = ::CreateFontA(-size_in_pixels, ..., "Arial"); 的方式。问题在于您假设字体设计线是每个字形的硬边界,但字体设计者不必严格将字形与这些线对齐。如果您希望字形严格对齐,可能没有办法得到它。也许检查不同的字体。

问题 #2:所有这些函数都使用了哪些奇怪的单位?

当模式设置为WM_TEXT 时,将使用原始设备像素。正高度指定高度包括tmInternalLeading,负不包括它。

对于正值:
tmAscent + tmDescent = requestedHeight
对于负值:
tmAscent + tmDescent - tmInternalLeading = requestedHeight

下面我粘贴了不同字体的屏幕截图,显示可以根据所选字体设计字形,使其不会达到顶线或超出顶线,并且在大多数情况下也不会达到底线。

似乎 Arial Unicode MS 更适合您的要求(但 j 仍然无法到达您想要的位置)。

宋体:

Arial Unicode MS

输入单声道

投石机MS

【讨论】:

  • 感谢您的回答!因为它以像素为单位,所以对我来说没问题。我看到的差异是由不同的字体引起的。我认为它是同一个,但是 ethalon 渲染器的“Arial”是完全从文件加载的(它是 3.0 版),而 CreateFontA 的“Arial”是从系统加载的(它是 7.0 版),即使我将它添加为资源.无论如何,您的帖子回答了很多问题,再次感谢您。
猜你喜欢
  • 2013-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-15
  • 2016-04-09
  • 2013-03-12
  • 1970-01-01
相关资源
最近更新 更多