Graphics.DrawString vs TextRenderer.DrawText?Which can Deliver Better Quality
i'm going to cross-post my answer from over here, just so that the information gets around.
There are two ways of drawing text in .NET:
- GDI+:
graphics.MeasureString
andgraphics.DrawString
- GDI:
TextRenderer.MeasureText
andTextRenderer.DrawText
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.
In Windows Forms 2.0, we added support for drawing GDI text. At first we had grandiose plans of poking and prodding at the DrawText API such that we could make it match up exactly how GDI+'s DrawString API works. I actually think we got pretty close, but there are fundamental differences in word wrapping and character spacing that as mere consumers of the both APIs, Windows Forms could not solve.
So now we're presented with a problem: we want to switch everyone over to the new TextRenderer APIs so text will look better, localize better, draw more consistently with other dialogs in the operating system... ...but we dont want to break folks counting on GDI+ measure string for calculations of where their text should line up.
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). From MSDN:
The GDI based TextRenderer class was introduced in the .NET Framework 2.0 to improve performance, make text look better, and improve support for international fonts. In earlier versions of the .NET Framework, the GDI+ based Graphics class was used to perform all text rendering. GDI calculates character spacing and word wrapping differently from GDI+. In a Windows Forms application that uses the Graphics class to render text, this could cause the text for controls that use TextRenderer to appear different from the other text in the application. To resolve this incompatibility, you can set the
UseCompatibleTextRendering
property to true for a specific control. To setUseCompatibleTextRendering
to true for all supported controls in the application, call the Application.SetCompatibleTextRenderingDefault method with a parameter of true.
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?
TextBox's Text is moving slightly when toggling Enabled: Font and Font size changes
A few changes to the text rendering should get you close to what you're expecting.
- Use TextRenderer to draw the text
- Add
TextFormatFlags.TextBoxControl
andTextFormatFlags.NoPadding
to the TextFormatFlags used inTextRenderer.DrawText()
(see the Docs about these two) - Override
OnBorderStyleChanged()
to replace theFixedSingle
style withFixed3D
when the Control is custom-drawn. This is what the base class (TextBoxBase) does, otherwise you get the default internal border that shrinks the Win32 Control's Client area. - Inflate the Control's ClientRectangle by
-1
pixel whenBorderStyle = BorderStyle.Fixed3D
.
I added UpdateStyles() after the SetStyle()
call: this forces the new Styles and also causes the Control to repaint itself.
Note that setting a Font that doesn't support all CodePoint (simplified: the Unicode chars in your Text) used when setting the Text of a control, may cause a Font Fallback.
In practice, the System will select a mapped surrogate Font that replaces the Control's Font. This happens transparently and without notice.
A different Graphic renderer can behave in a different manner in similar situations.
See the notes here:
How can a Label control display Japanese characters properly when the Font used doesn't support this language?
Somewhat different in a RichTextBox Control (the .Net base class is the same, but the Win32 Control is quite different):
Some Alt keys changes my RichTextBox font
Also, quite recently, all Asian Font rendering and UI presentation has been modified to better handle its characteristics. So the .Net version in use can also change the behavior.
The code here is tested with .Net Framework 4.8
.
public partial class CustomEnabledStateTextBox : TextBox
{
public CustomEnabledStateTextBox() => this.AutoSize = false;
protected override void OnEnabledChanged(EventArgs e)
{
SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, !Enabled);
UpdateStyles();
base.OnEnabledChanged(e);
}
protected override void OnBorderStyleChanged(EventArgs e)
{
base.OnBorderStyleChanged(e);
if (BorderStyle == BorderStyle.FixedSingle) BorderStyle = BorderStyle.Fixed3D;
}
private TextFormatFlags flags = TextFormatFlags.TextBoxControl |
TextFormatFlags.NoPadding;
protected override void OnPaint(PaintEventArgs e)
{
var rect = ClientRectangle;
if (BorderStyle != BorderStyle.None) rect.Inflate(-1, -1);
var alignment = TextAlign == HorizontalAlignment.Left
? TextFormatFlags.Left : (TextFormatFlags)((int)TextAlign^ 3);
flags |= alignment;
TextRenderer.DrawText(e.Graphics, Text, Font, rect, ForeColor, flags);
base.OnPaint(e);
}
}
Render antialiased text using TextRenderer C#
private void panel1_Paint(object sender, PaintEventArgs e)
{
//GDI (i.e. TextRenderer)
String s = "The quick brown fox jumped over the lazy dog";
Point origin = new Point(11, 11);
Font font = SystemFonts.IconTitleFont;
e.Graphics.TextRenderingHint = TextRenderingHint.SingleBitPerPixel;
TextRenderer.DrawText(e.Graphics, s, font, origin, SystemColors.InfoText);
}
Full demo showing that it does work: https://mega.nz/file/E3xREYIR#kuDxyac_0jxlX7wuTVmZmJgClEicdaCj0YpnE83Wq9k
Why is Graphics.MeasureString() returning a higher than expected number?
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 string format (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?
DataGridView CellPainting drawing text with ampersand displays weirdly
You want to use the TextRenderer version since DrawString should really only be used for printing:
TextRenderer.DrawText(e.Graphics, e.Value.ToString(),
e.CellStyle.Font, e.CellBounds, e.CellStyle.ForeColor,
TextFormatFlags.NoPrefix | TextFormatFlags.VerticalCenter);
The NoPrefix flag will show the ampersand correctly.
Related Topics
Can't Get SQL Server Compact 3.5/4 to Work with Asp .Net MVC 2
Path.Combine Absolute with Relative Path Strings
Exclude Property on Update in Entity Framework
What's the Difference Between Application.Run() and Form.Showdialog()
Can't Convert Value Type Array to Params Object[]
Loading a Full Hierarchy from a Self Referencing Table with Entityframework.Core
Should I Use Two "Where" Clauses or "&&" in My Linq Query
How to Ignore Null Values for All Source Members During Mapping in Automapper 6
How to Extract Text from Ms Office Documents in C#
What's the Difference Between the Webconfigurationmanager and the Configurationmanager
How to Return a Custom Http Status Code from a Wcf Rest Method
How to Serialize an Exception Object in C#
Convert System.Drawing.Icon to System.Media.Imagesource
How to Read All Files Inside Particular Folder
Directory.Getfiles of Certain Extension
How to Programmatically Choose a Constructor During Deserialization
Why Is Httpcontext.Current Null
How to Execute Task in the Wpf Background While Able to Provide Report and Allow Cancellation