3

我想知道是否有一种简单的方法来计算字符串的文本范围(类似于GetTextExtentPoint32),但允许我指定要在计算中使用的 DPI。换句话说,是否有一个函数可以完全完成GetTextExtentPoint32的工作,但允许我将 DPI 作为参数传递,或者是一种“欺骗” GetTextExtentPoint32使用我可以指定的 DPI 的方法?

在你问“你到底为什么要这样做?”之前,我会尝试解释一下,但请耐心等待,这个请求背后的原因有些牵涉。

最终,这是一个自定义的自动换行算法,它将长字符串分解成更小的文本块,这些文本块需要整齐地适合具有复杂文本布局要求的 Crystal Report(它模仿警察用来提交刑事投诉的纸质表格,所以国家负责布局,而不是我们,它必须几乎完全匹配纸质表格)。

如果没有帮助,Crystal Reports 不可能正确布置此文本(文本必须适合一页上的一个小框,如果文本溢出小块,则后面是“标准大小”的续页),所以我编写了代码将文本分成多个“块”,这些“块”存储在报告数据库中,然后由报告单独呈现。

给定所需的尺寸(以逻辑英寸为单位)和字体信息,代码首先通过插入换行符使文本适合所需的宽度,然后根据文本高度将其分成正确大小的块。该算法使用 VB6 的TextHeightTextWidth函数来计算范围,这将返回与GetTextExtentPoint32函数相同的结果(我检查过)。

当显示设置为 96dpi 时,此代码效果很好,但在 120 DPI 时会中断:有些行最终会包含更多的单词,而它们在 96 DPI 时会出现。

例如,“The quick brown fox jumps over the lazy dog”可能会中断如下:

96 DPI

敏捷的棕狐跳过
懒狗

120 DPI

敏捷的棕狐跳过
懒狗

此文本随后被 Crystal Reports 进一步分解,因为第一行不再适合报表上的相应文本字段,因此实际报表输出如下所示:


敏捷的棕狐跳过
懒狗

起初,我认为我可以通过将TextHeightTextWidth的结果缩小 25% 来弥补这一点,但显然生活并不那么简单:似乎有许多舍入错误(可能还有其他因素?),所以与 96 DPI 相比,任何给定字符串的文本范围在 120 DPI 下永远不会大25%。我没想到它可以完美缩放,但有时甚至没有接近(在一项测试中,120 DPI 的宽度仅比 96 DPI 的宽度大 18% 左右)。

这不会发生在由 Crystal Report 直接处理的报表上的任何文本上:它似乎在缩放所有内容方面做得很好,因此报表在 96 DPI 和 120 DPI(甚至 144 DPI)。即使在打印报告时,文本也会完全按照屏幕上显示的方式打印(即看起来确实是所见即所得)。

鉴于所有这些,因为我知道我的代码在 96 DPI 下工作,我应该能够通过以 96 DPI 计算所有文本范围来解决问题,即使 Windows 当前使用不同的 DPI 设置。

换句话说,我希望我的FitTextToRect函数的结果在任何 DPI 设置下都返回相同的输出,方法是强制使用 96 DPI 计算文本范围。这一切都应该解决,因为我将范围转换回英寸,然后将它们与所需的宽度和高度(以英寸为单位)进行比较。我认为在像素和英寸之间来回转换时,96 DPI 产生的结果比 120 DPI 更准确。

我一直在研究 Windows字体和文本函数,看看是否可以滚动我自己的函数来计算给定 DPI 下的文本范围,查看GetTextMetrics和其他函数,看看这可能是多么容易或困难。如果有更简单的方法来实现这一点,在我开始创建自己的现有 Windows API 函数版本之前,我很想知道!

4

2 回答 2

1

我找到了一个更简单的解决方案。我花了一段时间才说服自己这确实是有道理的。解决方案是如此明显,我觉得在这里发布它几乎是愚蠢的。

最终,我真正想要的是我的FitTextToRect函数在任何DPI 设置下生成相同的文本布局。事实证明,为了实现这一点,以像素为单位测量所有内容实际上更容易。由于根据定义,任何其他度量单位都会考虑当前的 DPI 设置,因此使用像素以外的任何东西都会使事情变得糟糕。

然后,“技巧”是强制所有计算得出与在 96 DPI 时相同的结果。但是,如果您暂时缩小字体大小,则不是先计算文本范围然后缩小它们,这会给计算带来很大的误差在计算任何文本范围之前。请注意,在打印预览和打印输出中仍使用原始字体大小。

这是因为 Windows 在内部测量字体大小设备单位测量字体大小,对于屏幕来说,这意味着像素。字体的“磅值”由允许您选择字体的应用程序使用当前 DPI 设置转换为像素:

font_height_in_pixels = (point_size * current_dpi / 72)

也就是说,Windows 从不直接处理字体的磅值:它总是处理像素。因此,它也以像素为单位计算文本范围。

这意味着您可以根据当前 DPI 和字体点大小计算出一个缩放因子,该因子会将字体缩小到一个新的点大小,在任何 DPI 下始终得出相同数量的像素(我使用 96 作为我的“基线" DPI):

scaled_point_size = (point_size * 96 / current_dpi)

通过有效地强制字体在任何 DPI 下适应相同数量的像素,这可以确保文本范围在任何 DPI 下都是相同的,因此该算法将在任何 DPI 下以相同的方式布置文本。

我唯一需要做的另一件事是确保传递给函数的高度和宽度参数(以英寸为单位)正确转换为像素。我不能使用 VB6 现有的英寸到像素转换函数,因为它考虑了当前 DPI(这会产生不一致的结果,因为文本高度和宽度被“标准化”为 96dpi),所以我只是乘以高度和宽度乘以 96,这会将它们转换为 96 DPI 时的像素测量值。

于 2010-10-17T07:00:13.100 回答
1

GetTextMetrics 接受 DC。它使用来自该 DC 的 DPI 设置(例如,您不可能使用屏幕设置并期望数据以可接受的格式输出以供打印机使用)。

因此,您需要做的就是为 DC 提供正确的 DPI。 我认为您也许可以直接控制图元文件 DC 的 DPI。

元文件是矢量图形,因此它们甚至看起来都不像具有 DPI。

您可以使用 控制 DPI CreateDIBitmap,但无法获得匹配的 DC。如果您将该 DIB 选择到内存 DC ( CreateCompatibleDC) 中,您可以查看 DPI 是否更改。

或者您可以使用 GDI+,创建具有所需 DPI 的位图,使用对图像进行操作的 Graphics 构造函数,然后该 Graphics 将具有正确的 DPI,因此 GDI+ 文本测量功能将使用您选择的 DPI。

于 2010-10-16T04:07:33.700 回答