CreateFont() 创建的字体规格和大小不正确

Incorrect metrics and sizes of font created by 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);

但之后我在 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:所有这些函数使用的奇怪单位是什么?

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

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

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

for example height of 'j' should have exact size that I passed

j 上的点可以超出或达不到顶线,如果设计师认为这样设计在视觉上是合理的。

interesting that ::GetDeviceCaps(dc, LOGPIXELSY) not changing with scale I using, it's always 96

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

给出的监视器参数或缓存值中获取 DPI

Question #1: What should I do to pass wanted point size in pixels and receive glyphs in proper size with correct metrics in pixels?

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

Question #2: What the strange units all these functions are using?

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

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

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

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

宋体:

Arial Unicode MS

输入单声道

Trebuched MS