7

我的问题是关于需要如何更改字体处理才能在 Windows 7 下正常工作。我确信我已经对以前有效但不再有效的东西做出了假设。但我什至不知道从哪里开始寻找!我祈祷有人能帮忙!以下是我了解的详细信息(我也在 Microsoft Windows 开发者论坛上发布了这个问题,但他们没有回答):

是的,我已经落后了(哎呀,我仍然用纯 C 编写 WIN32 代码!)我有一个 10 年前编写的 DLL,它在窗口的客户区内模仿了一个更老的 DOS 屏幕 I/O 库。不用说,它只允许使用固定宽度的字体。当一些使用 DLL 的程序被移到 Windows 7 时,当使用固定宽度的 TRUE TYPE 字体时会出现奇怪的闪烁(位图字体仍然可以正常工作。)我们已经将问题追溯到事实所写的单个字符ExtTextOut比应有的宽。我以三种不同的方式检查了测量值(通过使用GetTextExtentPoint32132 个字符串并除以 132,调用GetTextMetrics甚至使用GetCharABCWidths所有 256 个字符),他们都同意字体的宽度相同。但ExtTextOut正在渲染比字体宽度宽一或两个像素的背景矩形。要么比,要么它开始在参数中给定的位置左侧渲染一个或两个像素[我这样称呼它: ExtTextOut( hdc, r.left, r.top, ETO_OPAQUE, &r, &ch, 1, NULL ).] 请记住,这个 EXACT 代码在 Windows 2000、Windows XP 和Windows 7 上的位图字体——但它不再适用于 Windows 7 下的固定宽度 true type 字体。

对于那些不了解我需要做什么的人:试着想象在一张方格纸上每方写一个字符。每个方块使用相同的字体,但可能有不同的前景色和/或背景色。我使用TA_TOP|TA_LEFT文本对齐,因为它是最简单的并且任何一致应用的对齐都应该适用于固定宽度的字体。

我看到的是 ExtTextOut 发出的背景矩形比我在RECT *参数中指定的要大。由于我提供的矩形是根据报告的字体大小创建的,因此永远不会发生这种情况——而且它从未在 Windows XP 和更早版本上发生过,并且不会发生在 Windows 7 下的位图(即 .FON)字体中,任何一个。但它总是在 Windows 7 下使用固定宽度的 TrueType 字体发生。这与在 Windows 2000、Windows XP 和 Windows 7(32 和 64)上运行的 EXACT SAME EXECUTABLE 相同。虽然我很想说 Windows 7 有一个错误,我更倾向于相信我对 Windows 下的字体处理所做的一些基本假设不再正确(在为 Windows 编写软件 20 年后)。

但我不知道如何或在哪里发现那可能是什么!请,请帮助我!

--- 修正 ---

对于任何感兴趣的人,我已经设法解决了我正在考虑的错误——直到我找到相反的文档。我的解决方法包括对我的库的两个更改:

  1. 使用从GetTextExtentPoint32()'X' 返回的大小而不是来自 的数据TEXTMETRICS
  2. 在所有调用中包含ETO_CLIPPING标志。ExtTextOut()

以前,我使用tmHeight+tmExternalLeading的是连续文本行顶部之间的像素数,如文档所述。我发现从 ) 返回的 size.cy 值GetTextExtentPoint32(不一样,而且看起来更准确。我发现的最糟糕的例子是 OCRB true type 字体。这是我在调试器中为我创建的 OCRB 字体看到的内容(使用系统字体选择对话框):

ocrbtm.tmHeight          = 11
ocrbtm.tmExternalLeading =  7

ocrbsize.cy = 11

因此,出于某种我尚未发现的原因,Windows 忽略了为 OCRB 字体定义的外部前导值。使用大小值而不是 TM 会产生漂亮、整洁、紧凑的文本,这正是我想要的。

ETO_CLIPPING标志对我来说不是必需的,因为我将矩形设置为单个字符的尺寸并ETO_OPAQUE用于填充背景(并覆盖以前的单元格内容。)但是没有剪切标志,单个字符比大小、文本度量或 ABC 宽度都可以表明 - 至少,根据我迄今为止找到的所有文档,这是正确的。

我相信 HEIGHT 问题已经存在了很长时间,但是在我们在 Windows 7 下运行我们的软件之前,其余的都是不必要的。我将这个附加到我的问题中,看看是否有人可以解释我显然不明白的内容。

-- 修正案 2 --

1:我能找到的所有文档都说tmHeight+tmExternalLeading应该产生单行间距的文本。时期。但这并不总是正确的,我找不到说明 Windows 如何确定有时由GetTextExtentPoint32().

2:在Win7(可能是Vista)下ExtTextOut开始填充比应有的更多的背景(通过在右侧添加几个额外的像素),但只有在选择了真正的字体时。即使矩形是字符预期大小的两倍(在两个维度上),它也会这样做。DPI/缩放可能是一个因素,但由于我的系统设置为 100%,Windows 似乎遇到了 1 的问题:1 比例因子,这似乎是一个错误。它只影响真实类型而不影响位图(.FON)字体的事实似乎也排除了缩放(除非缩放系统中的一个错误),因为 Windows 应该尝试缩放所有文本,而不仅仅是其中的一部分。此外,“自定义 DPI 设置”对话框中有一个灰色(但已选中)设置“使用 Windows XP 样式 DPI 缩放”。最后,这整个问题可能是我在 Windows Classic 主题下运行的结果,而不是 Aero 或其他 Win7 原生主题之一。

-- 修正 3 --

简单地调用 SetProcessDPIAware() 对我遇到的问题没有影响。由于我的问题存在于 100% DPI 设置(比例 1:1),如果我的问题DPI 相关,那么我一定在 DPI 虚拟化中发现了一个错误,因为这是 Microsoft 描述该功能的方式:

此功能通过向应用程序提供“虚拟化”系统指标和 UI 元素来工作,就好像它以 96 DPI 运行一样。然后应用程序呈现到 96-DPI 的屏幕外表面,桌面 Windows 管理器缩放生成的应用程序窗口以匹配 DPI 设置。

我的所有设置都显示我在 100% 缩放,并且查看自定义设置框清楚地显示这意味着 96 DPI。所以,如果从 96 DPI 到 96 DPI 的 DPI 虚拟化不适用于我的固定宽度 True Type 字体,那么 Windows 就有问题了,对吧?或者我需要调用一些函数(或停止调用?)以允许 DPI 虚拟器正常工作?

我仍然不相信所谓的缩放问题实际上与我最初认为的字体大小有关。那是因为问题表现在背景矩形被填充ExtTextOut()而不是被发射的文本字符中。当字体为真类型时,背景矩形会放大一点。我现在还验证了无论使用 Windows Classic 主题还是标准 Windows Aero 主题都会出现此问题。现在构建一个简化的示例,以便其他人可以进行试验。

-- 修正案 4 --

我创建了一个最小的演示程序,它显示了我所看到的(以及我在做什么)。Visual Studio 2010 项目/源可以从http://www.svalli.com/files/fwtt.7z下载-- 我故意不包含可执行文件,因为我不想冒传播恶意软件的风险。该程序让您选择一个固定宽度的字体,然后将两个 5x5 字符网格写入客户区,一个使用GetTextExtentPoint32大小创建,一个使用TEXTMETRICMicrosoft 记录的大小。网格采用黑白棋盘格图案,最后在中心写入黄色和红色字符以显示重叠效果(您可能需要缩放实用程序才能清楚地看到它。)该程序还绘制了一个以 5 X 开头的字符串,就在下面网格,从相同的左侧偏移量开始,用作我放置单个字符的方法的比较(我匹配字符串。)菜单允许打开/关闭剪辑ExtTextOut以及选择其他字体。还有一个命令行选项dpiaware(区分大小写)会导致程序SetProcessDPIAware()在启动时调用,因此也可以评估该调用的效果。

通过创建这个,我了解到ExtTextOut正在填充正确的背景矩形,但是使用不透明背景渲染的角色可能比应有的更宽,甚至可能没有从ExtTextOut被告知开始绘制的位置开始!我说“应该”是因为我最终得到的字符间距与ExtTextOut渲染整个字符串时得到的字符间距相匹配。重叠显然可能在给定矩形的任一侧或两侧,例如,OCRB 在字符单元的左侧和右侧添加了一个额外的像素,而我检查过的其他真类型字体在右侧添加了两个像素边缘。

我真的很想以“正确”的方式做到这一点,但我找不到任何文档来说明我做错了什么或遗漏了什么。好吧,我可能在 100% 以外的比例上错过了 DPI Aware 的一些东西,但除此之外,我只是感到困惑。

-- 修正案 5 --

稍微不那么困惑...问题是由ClearType引起的。关闭 ClearType 使所有字体重新工作。在 XP 下打开 ClearType 会导致同样的问题。显然 ClearType 可以默默地(直到有人告诉我如何检测它)将字符水平拉伸几个像素,以便为它添加的阴影像素腾出空间以使事情变得平滑。

剪裁是解决这个问题的唯一方法吗?

-- 修正 6 --

对上面我的剪辑问题的部分回答:创建新字体时,我现在执行以下操作(在伪代码中):

CreateFontIndirect
SelectFont
GetTextMetrics
if( (tmPitchAndFamily & TMPF_TRUETYPE) && Win6.x or above )
   if( SystemParametersInfo( SPI_GETCLEARTYPE ) )
        lfQuality = NONANTIALIASED_QUALITY
        DeleteObject( font )
        CreateFontIndirect

如果不启用剪辑,这几乎总是适用于我正在使用的字体大小,尽管我发现一些仍然在字符单元格的右侧(或左侧)呈现额外的像素。幸运的是,这些似乎是在互联网上找到的免费字体,因此它们的整体质量可能低于专业字体代工厂的标准。

如果有人能找到更好的答案,我真的,真的很想听听!在那之前,我认为这是最好的。感谢您阅读到这里!

4

1 回答 1

4

确保您的代码支持高 DPI,然后告诉操作系统您的进程支持 DPI

如果您不告诉操作系统您知道 DPI,则某些测量功能会撒谎并根据显示 DPI 实际上是 96 dpi 的假设为您提供数字,而不管它实际上是什么。同时,绘图函数将尝试在另一个方向上缩放。对于简单的高级绘图,这种方法通常有效(尽管它经常导致文本模糊)。对于单个字符的小尺寸和精确放置,这通常会导致四舍五入问题,从而导致字体大小不一致等问题。此行为是在 Windows Vista 中引入的。

您可以在 Visual Studio 2010+ 中一直看到它,因为语法高亮显示文本和单词在您键入时会在这里和那里移动几个像素。真他妈烦。

关于修改:

tmExternalLeading只是字体设计师关于在文本行之间放置多少额外空间的建议。MSDN 文档通常说,“应用程序在行之间添加的额外前导(空间)量”。好吧,您是应用程序,因此“正确的做法”是在您自己绘制文本时在行之间添加它,但这完全取决于您。(我怀疑像 DrawText 这样的高级函数会使用它。

GetTextExtentPoint32(和朋友)返回size.cy等于tmHeight和忽略是完全正确的tmExternalLeading。作为程序员,最终导致实际使用多少是您的选择。

您可以通过一些简单的绘图代码看到这一点。选择具有非零 tmExternalLeading 的字体(Arial 适合我)。TextOut使用独特的背景颜色绘制一些文本。然后根据返回的值测量文本GetTextExtentPoint32并绘制​​一些线条。您会看到背景颜色矩形不包括外部前导。外部领导就是:外部。它不在字符单元格的范围内。

  // Draw the sample text with an opaque background.
  assert(::GetMapMode(ps.hdc) == MM_TEXT);
  assert(::GetBkMode(ps.hdc) == OPAQUE);
  assert(::GetTextAlign(ps.hdc) == TA_TOP);
  COLORREF rgbOld = ::SetBkColor(ps.hdc, RGB(0xC0, 0xFF, 0xC0));
  ::TextOutW(ps.hdc, x, y, pszText, cchText);
  ::SetBkColor(ps.hdc, rgbOld);

  // This vertical line at the right side of the text shows that opaque
  // background is exactly the height returned by GetTextExtentPoint32.
  SIZE size = {0};
  if (::GetTextExtentPoint32W(ps.hdc, pszText, cchText, &size)) {
    ::MoveToEx(ps.hdc, x + size.cx, y, NULL);
    ::LineTo(ps.hdc, x + size.cx, y + size.cy);
  }

  // These horizontal lines show the normal line spacing, taking into
  // account tmExternalLeading.
  assert(tm.tmExternalLeading > 0);  // ensure it's an interesting case
  ::MoveToEx(ps.hdc, x, y, NULL);
  ::LineTo(ps.hdc, x + size.cx, y);  // top of this line
  const int yNext = y + tm.tmHeight + tm.tmExternalLeading;
  ::MoveToEx(ps.hdc, x, yNext, NULL);
  ::LineTo(ps.hdc, x + size.cx, yNext);  // top of next line

彩色矩形底部和下一行顶部之间的间隙表示外部前导,它始终位于字符单元之外。

OCR-B专为银行设备中可靠的光学字符识别而设计。对于某些 OCR 应用程序,具有较大的外部前导(相对于实际文本的高度)可能是合适的。对于这种特殊的字体,它可能不是一种审美选择。

于 2013-02-27T00:40:45.880 回答