在 WinForms 应用程序中准确测量、呈现、命中测试和打印文本
本文关键字:文本 测试 呈现 打印 测量 WinForms 应用程序 | 更新日期: 2023-09-27 18:35:08
我们需要:
- 准确测量文本。
- 在存在应用于图形上下文的转换和缩放转换的情况下,逐行将文本呈现到屏幕图形上下文。
- 命中测试:允许使用鼠标或通过显示的插入符号精确选择文本。
- 如果需要,请使用打印机打印结果,并尽可能准确。注意:这是次要的。屏幕渲染和命中测试是主要的。
- 在 Windows XP 和更高版本的操作系统上运行。
在 WinForms 应用程序中,该应用程序还将图形和图像呈现到相同的图形上下文。
我们遇到了四种技术。我们尝试使用前两个,并在几个月的时间里遇到了所描述的问题。
GDI+
据称与决议无关的文本。然而,根据这个问题 - 以及其他来源 - 由于质量问题,应避免使用该技术。
MSDN 指出,调用 Graphics.MeasureString 以及 StringFormat.GenericTypographic
和 TextRenderingHint.AntiAlias
会产生准确的字符串度量。然而,根据我们和其他人的经验,情况并非如此 - 我们没有得到准确的字符串测量。
- 优点: 快速
- 缺点:字符串测量不准确。
结果:由于字符串测量不准确而无法使用。
GDI via TextRenderer
引入此功能是为了克服 GDI+ 的局限性。然而,这带来了自己的局限性:
- 非常慢
- 不适用于图形变换
结果:由于这些原因无法使用
GDI via p/invoke
调用GetTextExtentExPoint
进行文本测量,DrawText
/DrawTextEx
/ExtTextOut
进行渲染。
我们还没有尝试过这个。
直接写入
这似乎很有希望,因为它与包括GDI/GDI+在内的其他技术互操作,所以大概我们的其余图形渲染不会改变。但是,它仅适用于Windows Vista和更新的Windows版本。这目前是一个问题,因为Windows XP仍然拥有大量的安装基础。
问题
根据要求,这些技术中的哪一种可以工作?
注意:有很多关于这个话题的错误信息,所以请只在你在这个领域有专业知识的情况下回答这个问题。另外,请不要建议使用 WPF - 这不是我们正在考虑使用的东西。
据说MeasureCharacterRanges
函数比MeasureString
更准确。
如果必须同时面向屏幕和打印机,则需要在决定使用哪种渲染引擎之前对方法做出一些决定。 以下是一些可能性:
- 以屏幕单位布局,并对打印机进行最佳近似。
- 以打印机单元布局,并对屏幕进行最佳近似。
- 在理论上的高分辨率空间中进行布局,并对打印机和屏幕进行最佳近似。
在所有情况下,最佳近似都可以通过每一步非常仔细的近似来完成,这可以为每个设备提供最高的保真度,但代价是匹配的准确性,或者您可以渲染到位图并缩放到设备,这提供了较低的保真度,但对另一个空间的最佳近似
。我已经使用 GDI 完成了硬核打印预览。 这很难,但对于常规布局来说是可行的。 但是,如果您要处理任意变换,尤其是 +/-90 度以外的旋转,这几乎是不可能的。
很容易犯微妙的错误,并相信你得到的测量结果是错误的。
当笔触宽度与 dpi 处于同一数量级时,字体提示会使字体缩放非线性,因此,如果某些文本的宽度
w
,则字体大小加倍时的宽度可能不完全2*w
。字体替换在许多打印机驱动程序中很常见。 即使您选择了 TrueType 或 OpenType 字体,您也可能不会在打印机上获得与屏幕上相同的字体。
非方形像素在某些打印机中很常见。 当您执行不对齐轴的事情时,这些甚至会弄乱体面的光栅器。
字距调整可能令人惊讶。 不要假设 width('a') + width('b') == width("ab")。
舍入错误很容易产生。 有时,您必须知道基础光栅器使用的舍入规则。
在错误的上下文中进行测量太常见了。 不要尝试在屏幕上下文中进行测量并应用转换来获取打印机单位。 如果需要打印机单位,请在打印机上下文中进行测量。
我今天认为,如果您只需要打印预览,那么您应该将打印机单元布局为适合页面大小的位图,并根据 DPI 的比率将位图缩放到屏幕。 这是最简单的事情,它给出了很好的结果。 但目前尚不清楚一个好的硬拷贝和打印预览是否真的是你所追求的。
我有类似的要求,并且在文本渲染和缩放方面遇到了同样的问题。出于多种原因,即使是我对 WPF(.NET 3.5)的尝试也是一场噩梦。
我最终使用了GDI+ graphics.DrawString
,尽管被"弃用"了,但有一个有趣的技巧来获得准确的文本测量。
static public RectangleF MeasureInkBox(Graphics graphics, string text, Font font)
{
var bounds = new RectangleF();
using (var textPath = new GraphicsPath())
{
textPath.AddString(
text,
font.FontFamily,
(int)font.Style,
font.Size,
new PointF(0, 0),
StringFormat.GenericTypographic );
bounds = textPath.GetBounds();
}
return bounds;
}
事实证明,速度和质量令人满意。此外,graphics.DrawString
是使用 winForms 打印文本的推荐方法。
由于字距调整,测量单个字符位置可能很棘手,但我想这种方法的稍微复杂一点的版本可以完成这项工作。例如,对于"Hello",它将测量"H",然后是"He",然后是Hel",依此类推。它不应该显着降低性能,特别是如果您在收到点击单词时在飞行中这样做。