在Win7中,某些字体不像在Win2K/XP中那样工作

Ste*_*ere 7 windows fonts truetype font-size

我的问题是如何更改字体处理以便在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 ).]请记住,这个确切的代码工作完全的Windows 2000,Windows XP中,并与Windows 7上的位图字体 - 但在Windows 7下它不再适用于固定宽度的真实字体.

对于那些没有理解我需要做的事情的人:试着想象在一张方格纸上每个方格写一个字符.每个方块使用相同的字体,但可能具有不同的前景色和/或背景色.我使用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有一个bug,我更倾向于相信我在Windows下对字体处理做出的一些基本假设已不再适用(在为Windows编写软件20年之后).

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

---修改---

对于任何有兴趣的人,我已经设法解决了我正在考虑的错误 - 直到我找到相反的文档.我的解决方法包括对库进行两处更改:

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

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

ocrbtm.tmHeight          = 11
ocrbtm.tmExternalLeading =  7

ocrbsize.cy = 11
Run Code Online (Sandbox Code Playgroud)

所以,由于某种原因我还没有发现,Windows忽略了为OCRB字体定义的外部领先值.使用大小值而不是TM会产生漂亮,整洁,密集的文本,这正是我想要的.

ETO_CLIPPING,因为我的矩形设置为单个字符的准确尺寸和标志使用应该没有必要为我ETO_OPAQUE填写的背景(并覆盖前一个单元格的内容).但是,如果没有裁剪标志,单个字符宽于大小,文本度量或ABC宽度都表明 - 至少,基于我到目前为止找到的所有文档都是如此.

我相信HEIGHT问题已经存在了很长时间,但是在我们在Windows 7下运行我们的软件之前,其他问题是不必要的.我将此问题附加到我的问题上,看看是否有人可以解释我明显不理解的内容.

- 修订2 -

1:我能找到的所有文档都说tmHeight+tmExternalLeading应该产生单行间隔的文本.期.但这并非总是如此,我找不到文档,指出Windows如何确定有时返回的不同值GetTextExtentPoint32().

2:在Win7(可能是Vista)下ExtTextOut开始填充比应该更多的背景(通过在右边添加几个额外的像素),但仅在选择了真正的类型字体时.即使矩形是字符的预期大小的两倍(在BOTH维度中),它也会这样做.DPI/Scaling可能是一个因素,但由于我的系统设置为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虚拟化不能用于我的固定宽度真实类型字体,那么Windows有问题,对吧?或者是否需要调用(或停止调用?)以便允许DPI虚拟器正常工作?

我仍然不相信所谓的缩放问题实际上与我最初想的字体SIZE有很大关系.那是因为问题表现在填充的背景矩形ExtTextOut()而不是发出的文本字符.当字体为真类型时,背景矩形会稍微放大.我现在也验证了无论是使用Windows经典主题还是标准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增加了额外的像素到两个字符单元的左边和右边,而我已经检查了其他True Type字体添加两个像素的权利边缘.

我真的想以"正确"的方式做到这一点,但是我找不到任何能证明我做错了什么或遗失的文档.好吧,我可能在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
Run Code Online (Sandbox Code Playgroud)

没有启用剪辑,这几乎总是适用于我正在使用的字体大小,虽然我发现一些仍然在角色单元的右侧(或左侧)渲染一个额外的像素.幸运的是,这些似乎是互联网上的免费字体,因此它们的整体质量可能低于专业字体代工厂的标准.

如果任何人都可以找到一个更好的答案,我真的,真的喜欢听!在那之前,我认为这是最好的.感谢您阅读这篇文章!

Adr*_*thy 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
Run Code Online (Sandbox Code Playgroud)

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

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