Properly draw text using Graphics Path

走远了吗. 提交于 2019-11-29 16:32:46

Seems you are providing wrong measure for font size in the first place and then adding extra thickness to the brush. Try this instead:

using (GraphicsPath path = new GraphicsPath())
{
    path.AddString(
        text,                         
        _fontStyle.FontFamily,      
        (int)_fontStyle.Style,      
        e.Graphics.DpiY * fontSize / 72f,       // em size
        new Point(0, 0),                       // location where to draw text
        string_format);          

    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
    e.Graphics.CompositingMode = CompositingMode.SourceOver;
    e.Graphics.DrawPath(new Pen(Color.Red), path);
}

The GraphicsPath class calculates the size of a Text object in a different way (as already noted in the comments). The Text Size is calculated using the Ems bounding rectangle size.
An Em is a typographic measure that is independent from a destination Device context.
It refers to the rectangle occupied by a font's widest letter, usually the letter "M" (pronounced em).

The destination Em size can be calculated in 2 different ways: one includes a Font descent, the other doesn't include it. The latter is more common (since the "M" has no descent parts).

float EMSize = (Font.Height * [FontFamly].GetCellAscent([FontStyle]) 
                            / [FontFamily].GetEmHeight([FontStyle]

or

float EMSize = (Font.Height * ([FontFamly].GetCellAscent([FontStyle] + 
                               [FontFamily.GetCellDescent([FontStyle])) 
                             / [FontFamily].GetEmHeight([FontStyle]

See the Docs about:
FontFamily.GetEmHeight, FontFamily.GetCellAscent and FontFamily.GetCellDescent

I'm inserting here the figure you can find in the Docs.

Refer to the general informations contained here:
Using Font and Text (MSDN)

This document reports the specifics on how to translate Points, Pixels and Ems:
How to: Obtain Font Metrics (MSDN)


I assume you already have a class object that contains/references the Font settings that come from the UI controls and the required adjustments.
I'm adding one here with some properties that contain a subset of those settings, related to the question.

This class performs some calculations based on the size of a Font selected by the user.
The Font size is usually referenced in Points. This measure is then converted in Pixels, using the current screen DPI resolution (or converted in Points from a Pixel dimension). Each measure is also converted in Ems, which comes in handy if you have to use GraphicsPath to draw the Text.

The Ems size is calculated considering both the Ascent and the Descent of the Font. GraphicsPath works better with this measure, since a mixed text can have both parts and if it doesn't, that part is = 0.

To calculate the container box of a Text drawn with a specific Font and Font size, use the GraphicsPath.GetBounds() method:
([Canvas] is the Control that provides the Paint event's e.Graphics object)

using (GraphicsPath path = new GraphicsPath())
using (StringFormat format = new StringFormat(StringFormatFlags.NoClip | StringFormatFlags.NoWrap))
{
    format.Alignment = [StringAlignment.Center/Near/Far]; //As selected
    format.LineAlignment = [StringAlignment.Center/Near/Far]; //As selected
    //Add the Text to the GraphicsPath
    path.AddString(fontObject.Text, fontObject.FontFamily, 
                   (int)fontObject.FontStyle, fontObject.SizeInEms, 
                   [Canvas].ClientRectangle, format);
    //Ems size (bounding rectangle)
    RectangleF TextBounds = path.GetBounds(null, fontObject.Outline);
    //Location of the Text
    fontObject.Location = TextBounds.Location;
}

Draw the Text on the [Canvas] Device context:

private void Canvas_Paint(object sender, PaintEventArgs e)
{
    using (GraphicsPath path = new GraphicsPath())
    using (StringFormat format = new StringFormat(StringFormatFlags.NoClip | StringFormatFlags.NoWrap))
    {
        format.Alignment = [StringAlignment.Center/Near/Far]; //As selected
        format.LineAlignment = [StringAlignment.Center/Near/Far]; //As selected

        path.AddString(fontObject.Text, fontObject.FontFamily, (int)fontObject.FontStyle, fontObject.SizeInEms, Canvas.ClientRectangle, format);

        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        e.Graphics.CompositingMode = CompositingMode.SourceOver;
        e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
        e.Graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;

        if (fontObject.Outlined) { 
            e.Graphics.DrawPath(fontObject.Outline, path);
        }
        using(SolidBrush brush = new SolidBrush(fontObject.FillColor)) {
            e.Graphics.FillPath(brush, path);
        }
    }
}

Visual effect using this class and the realted methods:

The class object, to use as reference:

public class FontObject
{
    private float CurrentScreenDPI = 0.0F;
    private float m_SizeInPoints = 0.0F;
    private float m_SizeInPixels = 0.0F;
    public FontObject() 
        : this(string.Empty, FontFamily.GenericSansSerif, FontStyle.Regular, 18F) { }
    public FontObject(string Text, Font font) 
        : this(Text, font.FontFamily, font.Style, font.SizeInPoints) { }
    public FontObject(string Text, FontFamily fontFamily, FontStyle fontStyle, float FontSize)
    {
        if (FontSize < 3) FontSize = 3;
        using (Graphics g = Graphics.FromHwndInternal(IntPtr.Zero)) {
            this.CurrentScreenDPI = g.DpiY; 
        }
        this.Text = Text;
        this.FontFamily = fontFamily;
        this.SizeInPoints = FontSize;
        this.FillColor = Color.Black;
        this.Outline = new Pen(Color.Black, 1);
        this.Outlined = false;
    }

    public string Text { get; set; }
    public FontStyle FontStyle { get; set; }
    public FontFamily FontFamily { get; set; }
    public Color FillColor { get; set; }
    public Pen Outline { get; set; }
    public bool Outlined { get; set; }
    public float SizeInPoints {
        get => this.m_SizeInPoints;
        set {  this.m_SizeInPoints = value;
               this.m_SizeInPixels = (value * 72F) / this.CurrentScreenDPI;
               this.SizeInEms = GetEmSize();
        }
    }
    public float SizeInPixels {
        get => this.m_SizeInPixels;
        set {  this.m_SizeInPixels = value;
               this.m_SizeInPoints = (value * this.CurrentScreenDPI) / 72F;
               this.SizeInEms = GetEmSize();
        }
    }

    public float SizeInEms { get; private set; }
    public PointF Location { get; set; }
    public RectangleF DrawingBox { get; set; }

    private float GetEmSize()
    {
        return (this.m_SizeInPoints * 
               (this.FontFamily.GetCellAscent(this.FontStyle) +
                this.FontFamily.GetCellDescent(this.FontStyle))) /
                this.FontFamily.GetEmHeight(this.FontStyle);
    }
}

Edit

ComboBox with font families.

Setup an OwnerDraw ComboBox:

string[] FontList = FontFamily.Families.Where(f => f.IsStyleAvailable(FontStyle.Regular)).Select(f => f.Name).ToArray();

cboFontFamily.DrawMode = DrawMode.OwnerDrawVariable;
cboFontFamily.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
cboFontFamily.AutoCompleteSource = AutoCompleteSource.CustomSource;
cboFontFamily.AutoCompleteCustomSource.AddRange(FontList);
cboFontFamily.DisplayMember = "Name";
cboFontFamily.Items.AddRange(FontList);
cboFontFamily.Text = "Arial";

Event handlers:

private void cboFontFamily_DrawItem(object sender, DrawItemEventArgs e)
{
    TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.VerticalCenter;
    using (FontFamily family = new FontFamily(cboFontFamily.GetItemText(cboFontFamily.Items[e.Index])))
    using (Font font = new Font(family, 10F, FontStyle.Regular, GraphicsUnit.Point)) {
        TextRenderer.DrawText(e.Graphics, family.Name, font, e.Bounds, cboFontFamily.ForeColor, flags);
    }
    e.DrawFocusRectangle();
}

private void cboFontFamily_MeasureItem(object sender, MeasureItemEventArgs e)
{
    e.ItemHeight = (int)this.Font.Height + 4;
}

private void cboFontFamily_SelectionChangeCommitted(object sender, EventArgs e)
{
    fontObject.FontFamily = new FontFamily(cboFontFamily.GetItemText(cboFontFamily.SelectedItem));
    Canvas.Invalidate();
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!