问题
I'm generating a receipt and am using the Graphics object to call the DrawString method to print out the required text.
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.
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.
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.
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?
回答1:
From 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.
There are two ways of drawing text in .NET:
- GDI+ (
graphics.MeasureString
andgraphics.DrawString
) - GDI (
TextRenderer.MeasureText
andTextRenderer.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:
- 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.
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:
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).
A new static TextRenderer
class was created to wrap GDI text rendering. It has two methods:
TextRenderer.MeasureText
TextRenderer.DrawText
Note:
TextRenderer
is a wrapper around GDI, whilegraphics.DrawString
is still a wrapper around GDI+.
Then there was the issue of what to do with all the existing .NET controls, e.g.:
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".
By default controls in application behave like they did in .NET 1.1 (they are "compatible").
You turn off compatibility 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 Quality used for GDI font drawing:
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+: TextRenderingHintClearTypeGridFit
, GDI: CLEARTYPE_QUALITY
:
GDI+: TextRenderingHintAntiAlias
, GDI: ANTIALIASED_QUALITY
:
GDI+: TextRenderingHintAntiAliasGridFit
, GDI: not supported, uses ANTIALIASED_QUALITY:
GDI+: TextRenderingHintSingleBitPerPixelGridFit
, GDI: PROOF_QUALITY
:
GDI+: TextRenderingHintSingleBitPerPixel
, GDI: DRAFT_QUALITY
:
i find it odd that DRAFT_QUALITY
is identical to PROOF_QUALITY
, which is identical to 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?
回答2:
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).
So this font renders 14x14 pixels.
But TextRenderer.MeasureText()
returns a width of 21 pixels instead. If you need exact values this is useless.
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
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
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
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);
}
回答3:
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.
GDI DrawString Configurator App
Screen Capture
来源:https://stackoverflow.com/questions/1203087/why-is-graphics-measurestring-returning-a-higher-than-expected-number