如何使用 DrawString 正确左对齐文本?

How to properly left-align text with DrawString?

我正在将文本绘制到屏幕外位图中。不幸的是,文本没有正确左对齐(见下图)。文本应该触及左边距(蓝线)但偏离了几个像素。距离随着文字大小的增加而增加。

如何消除这个距离?

我正在使用 .NET Framework 4.6.1。但它似乎更像是一个我不明白的一般 GDI+ 问题。

用于生成示例的代码:

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);
            }
        }
    }
}

基于@ChrisW 提供的link,我创建了一个改进版本(见下文)。它使用 MeasureCharacterRanges 来测量 DrawString 添加的填充。结果看起来黄油多了:

如您所见,它并不完美。蓝线和字母"C"之间还有一些白色space,因为测量的矩形包括所谓的左方位,即左侧两个字符之间添加的白色space。

所以我仍在寻找更好的解决方案。除了测量的填充外,还可以计算方位并减去方位的一半。希望它适用于 .NET Standard 2.0...

顺便说一句:我测量了几种字体、字体样式、字体大小和分辨率。看起来填充是固定的。它可以计算为:

padding = 0.002312554 × font_size × resolution

(填充像素,字体大小 pt,分辨率 pixels/inch)

举个例子:对于 24pt 字体和 96dpi 的图形分辨率,填充将为 5.33 像素。

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);
            }
        }
    }
}

据说微软在 GDI+ 中添加了填充,以便更容易实现控件。旧的 GDI 没有这个问题。

当 Microsoft 意识到这是一个错误时,他们添加了绕过 GDI+ 并使用更好的 GDI 实现的 TextRenderer class

填充应该在左侧为 1/6 em,在右侧为 1/4 em。

您有两个选择:

  1. 使用TextRenderer.DrawText。但是,它是 Windows 表格 的一部分。所以它在 .NET Standard 和 .NET Core 中都不可用。

  2. 使用Graphics.DrawString with the magic option StringFormat.GenericTypographic。它神奇地删除了填充。

另见: