首页 > 解决方案 > CreateFont() 创建的字体的度量和大小不正确

问题描述

我尝试使用 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);

GetGlyphOutlineW但是在它之后我在和中都得到了不正确GetTextMetricsW的值,这不是我作为参数传递的大小我知道它期望逻辑单位的值,但是在 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 上它包括内部领先,但即使减去它仍然是不正确的tmInternalLeadingtmAscent顺便说一句,来自的值GetCharABCWidthsW是正确的,所以 a+b+c 是以像素为单位的字形宽度(而文档说它应该是逻辑单位)。

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

这是CreateFontA(-128, ...)最终图集和指标的示例: 渲染图集

问题#1:我应该怎么做才能传递所需的像素大小并接收具有正确大小的字形和正确的像素度量?

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

标签: c++winapifontsrendering

解决方案


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

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

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

例如,“j”的高度应该具有我通过的确切尺寸

j如果设计师发现以这种方式设计它在视觉上是合理的,那么Dot over可能会超出或无法达到顶线。

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

除非您注销并登录,否则系统 DPI 不会更改。对于每个监视器 DPI 感知应用程序,您必须从监视器参数或由WM_DPICHANGED.

问题#1:我应该怎么做才能传递所需的像素大小并接收具有正确大小的字形和正确的像素度量?

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

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

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

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

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

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

宋体:
在此处输入图像描述

宋体 Unicode MS
在此处输入图像描述

输入单声道
在此处输入图像描述

投掷 MS
在此处输入图像描述


推荐阅读