有没有一种简单的方法可以强制 Windows 使用固定的 DPI 值而不是当前的 DPI 设置来计算文本范围?

发布于 2024-09-28 09:16:51 字数 2337 浏览 5 评论 0原文

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

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

最终,这是用于自定义自动换行算法,该算法将长字符串分解为较小的文本块,这些文本块需要整齐地适合具有复杂文本布局要求的水晶报告(它模仿警察用于提交刑事投诉的纸质表格,因此,国家负责布局,而不是我们,并且它必须与纸质表格几乎完全匹配)。

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

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

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

例如,“敏捷的棕色狐狸跳过了懒惰的狗”可能会中断如下:

在 96 DPI

敏捷的棕色狐狸跳过
懒狗

120 DPI

敏捷的棕色狐狸跳过
懒狗

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

敏捷的棕色狐狸跳过


懒狗

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

这种情况不会发生在由 Crystal Report 直接处理的报表上的任何文本上:它似乎在缩放所有内容方面做得非常好,因此报表在 96 DPI 和 120 DPI(甚至 144 DPI)下的布局完全相同。深度PI)。即使在打印报告时,打印的文本也与屏幕上显示的完全一样(即看起来确实是所见即所得)。

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

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

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

I am wondering if there is an easy way to calculate the text extent of a string (similar to GetTextExtentPoint32), but allows me to specify the DPI to use in the calcuation. In other words, is there a function that does exactly what GetTextExtentPoint32 does, but allows me to pass the DPI as a parameter, or a way to "trick" GetTextExtentPoint32 into using a DPI that I can specify?

Before you ask "Why on earth do you want to do that?", I'll try to explain, but bear with me, the reasons behind this request are somewhat involved.

Ultimately, this is for a custom word-wrap algorithm that breaks a long string into smaller blocks of text that need to fit neatly on a Crystal Report with complex text layout requirements (it mimics the paper form used by police officers to file criminal complaints, so the state is in charge of the layout, not us, and it has to match the paper form almost exactly).

It's impossible for Crystal Reports to lay this text out properly without help (the text has to fit inside a small box on one page, followed by "standard-sized" continuation pages if the text overflows the small block), so I wrote code to break the text into multiple "blocks" that are stored in the reporting database and then rendered individually by the report.

Given the required dimensions (in logical inches), and the font information, the code first fits the text to the required width by inserting line breaks, then breaks it into correctly-size blocks based on the text height. The algorithm uses VB6's TextHeight and TextWidth functions to calculate extents, which returns the same results that the GetTextExtentPoint32 function would (I checked).

This code works great when the display is set to 96dpi, but breaks at 120 DPI: Some lines end up with more words in them they would have had at 96 DPI.

For example, "The quick brown fox jumps over the lazy dog" might break as follows:

At 96 DPI

The quick brown fox jumps over
the lazy dog

At 120 DPI

The quick brown fox jumps over the
lazy dog

This text is then further broken up by Crystal Reports, since the first line no longer fits in the corresponding text field on the report, so the actual report output looks like this:

The quick brown fox jumps over
the
lazy dog

At first, I thought I could compensate for this by scaling the results of TextHeight and TextWidth down by 25%, but apparently life isn't so simple: it seems numerous rounding errors creep in (and possibly other factors?), so that the text extent of any given string is never exactly 25% larger at 120 DPI compared to 96 DPI. I didn't expect it to scale perfectly, but it's not even close at times (in one test, the width at 120 DPI was only about 18% bigger than at 96 DPI).

This doesn't happen to any text on the report that is handled directly by Crystal Report: it seems to do a very good job of scaling everything so that the report is laid out exactly the same at 96 DPI and 120 DPI (and even 144 DPI). Even when printing the report, the text is printed exactly as it appears on the screen (i.e. it truly seems to be WYSIWYG).

Given all of this, since I know my code works at 96 DPI, I should be able to fix the problem by calculating all my text extents at 96 DPI, even if Windows is currently using a different DPI setting.

Put another way, I want the result of my FitTextToRect function to return the same output at any DPI setting, by forcing the text extents to be calculated using 96 DPI. This should all work out since I'm converting the extents back to inches and then comparing them against required width and height in inches. I think it just so happens that 96 DPI produces more accurate results than 120 DPI when converting back and forth between pixels and inches.

I've been pouring over the Windows Font and Text Functions, seeing if could roll my own function to calculate text extent at a given DPI, looking at GetTextMetrics and other functions to see how easy or difficult this might be to do. If there is an easier way to accomplish this, I'd love to know before I start creating my own versions of existing Windows API functions!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

对你而言 2024-10-05 09:16:51

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

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

那么“技巧”就是强制所有计算得出与 96 DPI 时相同的结果。但是,如果您暂时缩放 ,则无需先计算文本范围,然后缩小它们,这会给计算带来很大的误差,而是可以获得更准确的结果(即在任何 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 时的像素测量值。

I found a much simpler solution. It took me awhile to convince myself that it really does makes sense. The solution was so obvious I feel almost silly posting it here.

Ultimately, what I really want is for my FitTextToRect function to produce the same text layout at any DPI setting. It turns out, in order to make this happen, it's actually easier to measure everything in pixels. Since any other unit of measure will by definition take the current DPI setting into account, using anything other than pixels can throw things off.

The "trick" then is to force all the calculations to work out to the same result they would have had at 96 DPI. However, instead of calculating text extents first and then scaling them down, which adds significant error to the calculations, you can get more accurate results (i.e. the results will be equal or near equal at any DPI) if you temporarily scale the font size down before calculating any text extents. Note that that original font size is still used in the print preview and in the printed output.

This works because of the fact that Windows internally measures font size in device units, which for the screen, means pixels. The font's "point size" is converted to pixels by the application that let you select the font, using the current DPI setting:

font_height_in_pixels = (point_size * current_dpi / 72)

That is, Windows never deals directly with the font's point size: it's always dealing with pixels. As a result, it calculates text extents in terms of pixels as well.

This means you can work out a scaling factor based on the current DPI and font point size that will scale the font down to a new point size which always comes out to the same number of pixels at any DPI (I used 96 as my "baseline" DPI):

scaled_point_size = (point_size * 96 / current_dpi)

By effectively forcing the font to fit the same number of pixels at any DPI, this ensures that text extents will be the same at any DPI, and therefore the algorithm will lay the text out the same way at any DPI.

The only other thing I needed to do was ensure that the height and width parameters passed to the function, which are measured in inches, got converted to pixels correctly. I couldn't use VB6's existing inches-to-pixels conversion function, since it takes the current DPI into account (which would create inconsistent results, since the text height and width is "normalized" to 96dpi), so instead I just multiplied the height and width by 96, which converts them to what their pixel measurements would be at 96 DPI.

还在原地等你 2024-10-05 09:16:51

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

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

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

您可以使用 CreateDIBitmap 控制 DPI,但是那么就没有办法获得匹配的DC。如果您将该 DIB 选择到内存 DC (CreateCompatibleDC),您可以看到 DPI 是否发生变化。

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

GetTextMetrics accepts a DC. It uses the DPI settings from that DC (for example, you couldn't possibly use screen settings and expect data to come out formatted acceptably for a printer).

So all you need to do is supply a DC with the right DPI. I think you might be able to directly control the DPI of a metafile DC.

Metafiles are vector graphics so they don't even look like they have DPI.

You can control DPI with CreateDIBitmap, but then there's no way to get a matching DC. You could see if the DPI changes if you select that DIB into a memory DC (CreateCompatibleDC).

Or you could use GDI+, create a Bitmap with the desired DPI, use the Graphics constructor that operates on an Image, and then that Graphics will have the right DPI so GDI+ text measurement functions would then use your DPI of choice.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文