Reputation: 78825
I'm drawing text to an off-screen bitmap. Unfortunately, the text is not properly left aligned (see image below). The text should touch the left margin (blue line) but is off by a few pixel. The distance grows with the text size.
How can I get rid of this distance?
I'm using .NET Framework 4.6.1. But it seems to more a general GDI+ issue that I don't understand.
Code used to generate the sample:
using System.Drawing;
using System.Drawing.Imaging;
namespace LeftAlignment
{
class Program
{
static void Main(string[] args)
{
const int LeftMargin = 10;
// create off-screen bitmap
using (Bitmap bitmap = new Bitmap(300, 100))
{
// create graphics context
using (Graphics graphics = Graphics.FromImage(bitmap))
{
// clear bitmap
graphics.FillRectangle(Brushes.White, 0, 0, bitmap.Width, bitmap.Height);
// draw border and left margin
graphics.DrawRectangle(Pens.Gray, new Rectangle(0, 0, bitmap.Width - 1, bitmap.Height - 1));
graphics.DrawLine(Pens.Blue, LeftMargin, 8, LeftMargin, 92);
// draw string at 24 pt
Font font = new Font("Arial", 24);
graphics.DrawString("Cool water", font, Brushes.Black, LeftMargin, 8);
// draw string at 36 pt
font = new Font("Arial", 36);
graphics.DrawString("Cool water", font, Brushes.Black, LeftMargin, 44);
}
// save result as PNG
bitmap.Save("alignment.png", ImageFormat.Png);
}
}
}
}
Upvotes: 2
Views: 3577
Reputation: 636
The story goes that Microsoft added the padding in GDI+ to make it easier to implement controls. The old GDI didn't have that problem.
When Microsoft realized it was a mistake, they added the TextRenderer class that bypasses GDI+ and uses the better GDI implementation.
The padding is supposedly 1/6 em on the left and 1/4 em on the right hand side.
You have two options:
Use TextRenderer.DrawText. However, it's part of Windows Forms. So it won't be available neither in .NET Standard nor .NET Core.
Use Graphics.DrawString with the magic option StringFormat.GenericTypographic. It magically removes the padding.
Also see:
Upvotes: 8
Reputation: 78825
Based on the link provided by @ChrisW, I've created an improved version (see below). It uses MeasureCharacterRanges
to measure the padding added by DrawString
. The result looks much butter:
As you can see, it's not perfect. There is still some white space between the blue line and the letter "C" because the measured rectangle includes the so called left-side bearing, i.e. the left side of the white space added between two characters.
So I'm still looking for an even better solution. It might be possible to compute the bearing and subtract half of the bearing in addition to the measured padding. Hopefully, it is doable with .NET Standard 2.0...
BTW: I've measured several fonts, font styles, font sizes and resolutions. It looks as if the padding is fixed. It can be computed as:
padding = 0.002312554 × font_size × resolution
(Padding in pixels, font size in pt, resolution in pixels/inch)
So as an example: for a 24pt font and graphics resolution of 96dpi, the padding would be 5.33 pixels.
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
namespace LeftAlignment
{
class Program
{
static void ImprovedDrawString(Graphics graphics, string text, Font font, float x, float y)
{
// measure left padding
StringFormat sf = new StringFormat(StringFormatFlags.NoClip);
sf.SetMeasurableCharacterRanges(new CharacterRange[] { new CharacterRange(0, 1) });
Region[] r = graphics.MeasureCharacterRanges(text, font, new RectangleF(0, 0, 1000, 100), sf);
float leftPadding = r[0].GetBounds(graphics).Left;
// draw string
sf = new StringFormat(StringFormatFlags.NoClip);
graphics.DrawString(text, font, Brushes.Black, x - leftPadding, y, sf);
}
static void Main(string[] args)
{
const int LeftMargin = 10;
const string Text = "Cool water";
// create off-screen bitmap
using (Bitmap bitmap = new Bitmap(300, 100))
{
// create graphics context
using (Graphics graphics = Graphics.FromImage(bitmap))
{
// enable high-quality output
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
// clear bitmap
graphics.FillRectangle(Brushes.White, 0, 0, bitmap.Width, bitmap.Height);
// draw border and left margin
graphics.DrawRectangle(Pens.Gray, new Rectangle(0, 0, bitmap.Width - 1, bitmap.Height - 1));
graphics.DrawLine(Pens.Blue, LeftMargin, 8, LeftMargin, 92);
// draw string at 24 pt
Font font = new Font("Arial", 24);
ImprovedDrawString(graphics, Text, font, LeftMargin, 8);
// draw string at 36 pt
font = new Font("Arial", 36);
ImprovedDrawString(graphics, Text, font, LeftMargin, 44);
}
// save result as PNG
bitmap.Save("alignment.png", ImageFormat.Png);
}
}
}
}
Upvotes: 2