C# 为什么 Graphics.MeasureString() 返回的数字高于预期?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1203087/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why is Graphics.MeasureString() returning a higher than expected number?
提问by Brandon
I'm generating a receipt and am using the Graphics object to call the DrawString method to print out the required text.
我正在生成收据并使用 Graphics 对象调用 DrawString 方法来打印出所需的文本。
graphics.DrawString(string, font, brush, widthOfPage / 2F, yPoint, stringformat);
This works fine for what I needed it to do. I always knew what I was printing out, so I could manually trim any strings so it would fit properly on 80mm receipt paper. Then I had to add an extra bit of functionality that would make this more flexible. The user could pass in strings that would be added to the bottom.
这适用于我需要它做的事情。我总是知道我要打印什么,所以我可以手动修剪任何字符串,使其适合 80 毫米收据纸。然后我不得不添加一些额外的功能,使其更加灵活。用户可以传入将添加到底部的字符串。
Since I didn't know what they were going to put, I just created my own word wrap function that takes in a number of characters to wrap at and the string itself. In order to find out the number of characters, I was doing something like this:
由于我不知道他们要放什么,我只是创建了自己的自动换行函数,该函数接受多个要换行的字符和字符串本身。为了找出字符数,我正在做这样的事情:
float width = document.DefaultPageSettings.PrintableArea.Width;
int max = (int)(width / graphics.MeasureString("a", font).Width);
Now the width is returning me 283, which in mm is about 72, which makes sense when you account for margins on 80mm paper.
现在宽度返回 283,以毫米为单位约为 72,当您考虑 80 毫米纸上的边距时,这是有道理的。
But the MeasureString method is returning 10.5 on a Courier New 8pt font. So instead of getting around what I expected to be 36 - 40, I'm getting 26, resulting in 2 lines of text being turned into 3-4.
但是 MeasureString 方法在 Courier New 8pt 字体上返回 10.5。因此,我得到了 26,而不是我预期的 36 - 40,结果是 2 行文本变成了 3-4。
The units for PrintableArea.Width are 1/100th of an inch, and the PageUnit for the graphics object is Display (which says is typically 1/100th of an inch for printers). So why am I only getting 26 back?
PrintableArea.Width 的单位是 1/100 英寸,图形对象的 PageUnit 是 Display(对于打印机来说通常是 1/100 英寸)。那为什么我只拿回 26 呢?
采纳答案by Ian Boyd
From WindowsClient.net:
从 WindowsClient.net:
GDI+ adds a small amount (1/6 em) to each end of every string displayed. This 1/6 em allows for glyphs with overhanging ends (such as italic 'f'), and also gives GDI+ a small amount of leeway to help with grid fitting expansion.
The default action of
DrawString
will work against you in displaying adjacent runs:
- Firstly the default StringFormat adds an extra 1/6 em at each end of each output;
- Secondly, when grid fitted widths are less than designed, the string is allowed to contract by up to an em.
To avoid these problems:
- Always pass
MeasureString
andDrawString
a StringFormat based on the typographic StringFormat (GenericTypographic
).
Set the GraphicsTextRenderingHint
toTextRenderingHintAntiAlias
. This rendering method uses anti-aliasing and sub-pixel glyph positioning to avoid the need for grid-fitting, and is thus inherently resolution independent.
GDI+ 在显示的每个字符串的每一端添加少量 (1/6 em)。这个 1/6 em 允许带有悬垂末端的字形(例如斜体“ f”),并且还为 GDI+ 提供了少量余地以帮助网格拟合扩展。
的默认操作
DrawString
将在显示相邻运行时对您不利:
- 首先,默认的 StringFormat 在每个输出的每一端添加一个额外的 1/6 em;
- 其次,当网格拟合宽度小于设计宽度时,绳子最多可以收缩 em。
为避免这些问题:
- 始终传递
MeasureString
和DrawString
基于印刷 StringFormat (GenericTypographic
) 的StringFormat 。
将图形设置TextRenderingHint
为TextRenderingHintAntiAlias
. 这种渲染方法使用抗锯齿和亚像素字形定位来避免网格拟合的需要,因此本质上与分辨率无关。
There are two ways of drawing text in .NET:
在 .NET 中有两种绘制文本的方法:
- GDI+ (
graphics.MeasureString
andgraphics.DrawString
) - GDI (
TextRenderer.MeasureText
andTextRenderer.DrawText
)
- GDI+(
graphics.MeasureString
和graphics.DrawString
) - GDI (
TextRenderer.MeasureText
和TextRenderer.DrawText
)
From Michael Kaplan's (rip) excellent blog Sorting It All Out, In .NET 1.1 everything used GDI+for text rendering. But there were some problems:
来自 Michael Kaplan 的 (rip) 优秀博客Sorting It All Out,在 .NET 1.1 中,一切都使用GDI+进行文本渲染。但是出现了一些问题:
- There are some performance issues caused by the somewhat stateless nature of GDI+, where device contexts would be set and then the original restored after each call.
- The shaping engines for international text have been updated many times for Windows/Uniscribe and for Avalon (Windows Presentation Foundation), but have not been updated for GDI+, which causes international rendering support for new languages to not have the same level of quality.
- GDI+ 的无状态性质会导致一些性能问题,其中设备上下文将被设置,然后在每次调用后恢复原始。
- Windows/Uniscribe 和 Avalon (Windows Presentation Foundation) 的国际文本整形引擎已多次更新,但 GDI+ 尚未更新,这导致对新语言的国际渲染支持质量不同。
So they knew they wanted to change the .NET framework to stop using GDI+'s text rendering system, and use GDI. At first they hoped they could simply change:
所以他们知道他们想要改变 .NET 框架以停止使用GDI+的文本呈现系统,而使用GDI。起初他们希望他们可以简单地改变:
graphics.DrawString
to call the old DrawText
API instead of GDI+. But they couldn't make the text-wrapping and spacing match exactly as what GDI+ did. So they were forced to keep graphics.DrawString
to call GDI+ (compatiblity reasons; people who were calling graphics.DrawString
would suddenly find that their text didn't wrap the way it used to).
调用旧DrawText
API 而不是 GDI+。但是他们无法像 GDI+ 那样使文本换行和间距完全匹配。所以他们被迫继续graphics.DrawString
调用 GDI+(兼容性原因;打电话的人graphics.DrawString
会突然发现他们的文本没有像以前那样换行)。
A new static TextRenderer
class was created to wrap GDI text rendering. It has two methods:
TextRenderer
创建了一个新的静态类来包装 GDI 文本呈现。它有两种方法:
TextRenderer.MeasureText
TextRenderer.DrawText
Note:
TextRenderer
is a wrapper around GDI, whilegraphics.DrawString
is still a wrapper around GDI+.
注意:
TextRenderer
是 GDI 的包装器,同时graphics.DrawString
仍然是 GDI+ 的包装器。
Then there was the issue of what to do with all the existing .NET controls, e.g.:
然后是如何处理所有现有 .NET 控件的问题,例如:
Label
Button
TextBox
Label
Button
TextBox
They wanted to switch them over to use TextRenderer
(i.e. GDI), but they had to be careful. There might be people who depended on their controls drawing like they did in .NET 1.1. And so was born "compatible text rendering".
他们想将它们转换为使用TextRenderer
(即 GDI),但他们必须小心。可能有些人像在 .NET 1.1 中那样依赖他们的控件绘图。于是诞生了“兼容文本渲染”。
By default controls in application behave like they did in .NET 1.1 (they are "compatible").
默认情况下,应用程序中的控件的行为与 .NET 1.1 中的一样(它们是“兼容的”)。
You turn offcompatibility mode by calling:
您可以通过调用关闭兼容模式:
Application.SetCompatibleTextRenderingDefault(false);
This makes your application better, faster, with better international support. To sum up:
这使您的应用程序更好、更快,并获得更好的国际支持。总结:
SetCompatibleTextRenderingDefault(true) SetCompatibleTextRenderingDefault(false)
======================================= ========================================
default opt-in
bad good
the one we don't want to use the one we want to use
uses GDI+ for text rendering uses GDI for text rendering
graphics.MeasureString TextRenderer.MeasureText
graphics.DrawString TextRenderer.DrawText
Behaves same as 1.1 Behaves *similar* to 1.1
Looks better
Localizes better
Faster
It's also useful to note the mapping between GDI+ TextRenderingHint
and the corresponding LOGFONT
Qualityused for GDI font drawing:
注意 GDI+TextRenderingHint
和用于 GDI 字体绘制的相应LOGFONT
质量之间的映射也很有用:
TextRenderingHint mapped by TextRenderer to LOGFONT quality
======================== =========================================================
ClearTypeGridFit CLEARTYPE_QUALITY (5) (Windows XP: CLEARTYPE_NATURAL (6))
AntiAliasGridFit ANTIALIASED_QUALITY (4)
AntiAlias ANTIALIASED_QUALITY (4)
SingleBitPerPixelGridFit PROOF_QUALITY (2)
SingleBitPerPixel DRAFT_QUALITY (1)
else (e.g.SystemDefault) DEFAULT_QUALITY (0)
Samples
样品
Here's some comparisons of GDI+ (graphics.DrawString) verses GDI (TextRenderer.DrawText) text rendering:
下面是 GDI+ (graphics.DrawString) 与 GDI (TextRenderer.DrawText) 文本渲染的一些比较:
GDI+: TextRenderingHintClearTypeGridFit
, GDI: CLEARTYPE_QUALITY
:
GDI+: TextRenderingHintClearTypeGridFit
, GDI: CLEARTYPE_QUALITY
:
GDI+: TextRenderingHintAntiAlias
, GDI: ANTIALIASED_QUALITY
:
GDI+: TextRenderingHintAntiAlias
, GDI: ANTIALIASED_QUALITY
:
GDI+: TextRenderingHintAntiAliasGridFit
, GDI: not supported, uses ANTIALIASED_QUALITY:
GDI+: TextRenderingHintAntiAliasGridFit
, GDI:不支持,使用 ANTIALIASED_QUALITY:
GDI+: TextRenderingHintSingleBitPerPixelGridFit
, GDI: PROOF_QUALITY
:
GDI+: TextRenderingHintSingleBitPerPixelGridFit
, GDI: PROOF_QUALITY
:
GDI+: TextRenderingHintSingleBitPerPixel
, GDI: DRAFT_QUALITY
:
GDI+: TextRenderingHintSingleBitPerPixel
, GDI: DRAFT_QUALITY
:
i find it odd that DRAFT_QUALITY
is identical to PROOF_QUALITY
, which is identical to CLEARTYPE_QUALITY
.
我觉得很奇怪,DRAFT_QUALITY
它与 相同PROOF_QUALITY
,与 相同CLEARTYPE_QUALITY
。
See also
也可以看看
- UseCompatibleTextRendering - Compatible with whaaaaaat?
- Sorting it all out: A quick look at Whidbey's TextRenderer
- MSDN: LOGFONT Structure
- AppCompat Guy: GDI vs. GDI+ Text Rendering Performance
- GDI+ Text, Resolution Independence, and Rendering Methods. Or - Why does my text look different in GDI+ and in GDI?
回答by Elmue
When you create a Font 'Courier New' with Size = 11 you will get an output like in the image above. You see that the height is 14 pixel not including the underline. The width is exactly 14 pixel (7 pixel for each character).
当您创建大小为 11 的字体“Courier New”时,您将获得如上图所示的输出。您会看到高度为 14 像素,不包括下划线。宽度正好是 14 像素(每个字符 7 像素)。
So this font renders 14x14 pixels.
所以这个字体呈现 14x14 像素。
But TextRenderer.MeasureText()
returns a width of 21 pixels instead. If you need exact values this is useless.
但TextRenderer.MeasureText()
返回 21 像素的宽度。如果您需要确切的值,这是没用的。
The solution is the following code:
解决方法如下代码:
Font i_Courier = new Font("Courier New", 11, GraphicsUnit.Pixel);
Win32.SIZE k_Size;
using (Bitmap i_Bmp = new Bitmap(200, 200, PixelFormat.Format24bppRgb))
{
using (Graphics i_Graph = Graphics.FromImage(i_Bmp))
{
IntPtr h_DC = i_Graph.GetHdc();
IntPtr h_OldFont = Win32.SelectObject(h_DC, i_Courier.ToHfont());
Win32.GetTextExtentPoint32(h_DC, "áp", 2, out k_Size);
Win32.SelectObject(h_DC, h_OldFont);
i_Graph.ReleaseHdc();
}
}
k_Size will contain the correct size: 14x14
k_Size 将包含正确的大小:14x14
IMPORTANT:This code measures correctly a regular font. If you need the exact values also for italic fonts (that always have an overhang on the right) you should read the links that are mentioned in this article: http://www.codeproject.com/Articles/14915/Width-of-text-in-italic-font
重要提示:此代码可正确测量常规字体。如果您还需要斜体字体的确切值(总是在右侧有突出部分),您应该阅读本文中提到的链接:http: //www.codeproject.com/Articles/14915/Width-of-斜体文本
APPENDIX:For those who have never used API calls in C# here a hint how to create the class Win32. This is not complete. For more details have a look at http://www.pinvoke.net
附录:对于那些从未在 C# 中使用过 API 调用的人,这里提示如何创建 Win32 类。这是不完整的。有关更多详细信息,请查看http://www.pinvoke.net
using System.Runtime.InteropServices;
public class Win32
{
[StructLayout(LayoutKind.Sequential)]
public struct SIZE
{
public int cx;
public int cy;
}
[DllImport("Gdi32.dll")]
public static extern bool GetTextExtentPoint32(IntPtr hdc, string lpString, int cbString, out SIZE lpSize);
[DllImport("Gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
}
回答by Cheva
Here is an explanation that can help you understand how it works. and what causes the spaces of more or less before and after each character.
这是一个解释,可以帮助您了解它是如何工作的。以及是什么导致每个字符前后或多或少的空格。