【问题标题】:Properly draw text using GraphicsPath使用 GraphicsPath 正确绘制文本
【发布时间】:2019-04-03 06:40:06
【问题描述】:

如下图所示,PictureBox 上的文本与 TextBox 中的文本不同。如果我使用Graphics.DrawString(),它工作正常,但是当我使用图形路径时,它会截断并且不显示整个文本。您认为我的代码有什么问题?

这是我的代码:

public LayerClass DrawString(LayerClass.Type _text, string text, RectangleF rect, Font _fontStyle, PaintEventArgs e)
{
    using (StringFormat string_format = new StringFormat())
    {
        rect.Size = e.Graphics.MeasureString(text, _fontStyle);
        rect.Location = new PointF(Shape.center.X - (rect.Width / 2), Shape.center.Y - (rect.Height / 2));
        if(isOutlinedOrSolid)
        {
            using (GraphicsPath path = new GraphicsPath())
            {
                path.AddString(text, _fontStyle.FontFamily, (int)_fontStyle.Style, rect.Size.Height, rect, string_format);
                e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
                e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
                e.Graphics.CompositingMode = CompositingMode.SourceOver;
                e.Graphics.DrawPath(new Pen(Color.Red, 1), path);
            }
        }
        else
        {
            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
            e.Graphics.CompositingMode = CompositingMode.SourceOver;
            e.Graphics.DrawString(text,_fontStyle,Brushes.Red, rect.Location);
        }
    }

    this._Text = text;
    this._TextRect = rect;
    return new LayerClass(_text, this, text, rect);
}

【问题讨论】:

  • Graphics.MeasureString() 只能告诉你用 Graphics.DrawString() 绘制的字符串的大小,预测 Graphics.DrawPath() 会做什么是没有用的。考虑使用 Pen 的 GraphicsPath.GetBounds() 重载。

标签: c# .net winforms graphics gdi+


【解决方案1】:

GraphicsPath 类以不同的方式计算 Text 对象的大小(如 cmets 中所述)。文本大小是使用Ems 边界矩形大小计算的。
Em 是一种排版度量,独立于目标设备上下文。
它指的是字体最宽的字母占据的矩形,通常是字母“M”(读作em)。

目标Em 的大小可以通过两种不同的方式计算:一种包括Font descent,另一种不包括它。

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

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

请参阅以下文档:
FontFamily.GetEmHeightFontFamily.GetCellAscentFontFamily.GetCellDescent

我在此处插入您可以在文档中找到的图。

请参阅此处包含的一般信息:
Using Font and Text (MSDN)

本文档详细介绍了如何翻译点、像素和 Ems:
How to: Obtain Font Metrics (MSDN)


我假设您已经有一个类对象,其中包含/引用来自 UI 控件的字体设置和所需的调整。
我在这里添加了一些属性,其中包含与问题相关的这些设置的子集。

此类根据用户选择的字体大小执行一些计算。
字体大小通常以磅为单位。然后使用当前屏幕DPI 分辨率将这个度量转换为像素(或从像素维度转换为点)。每个度量也转换为Ems,如果您必须使用GraphicsPath 来绘制文本,它会派上用场。

Ems 大小的计算同时考虑了字体的AscentDescent
GraphicsPath 类在此度量下效果更好,因为混合文本可以包含两个部分,如果没有,则该部分为 = 0

要计算使用特定字体和字体大小绘制的文本的容器框,请使用GraphicsPath.GetBounds() 方法:
[Canvas] 是提供Paint 事件的e.Graphics 对象的控件)

using (var path = new GraphicsPath())
using (var 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;
}

[Canvas] 设备上下文上绘制文本:

private void Canvas_Paint(object sender, PaintEventArgs e)
{
    using (var path = new GraphicsPath())
    using (var 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;
        // When text is rendered directly
        e.Graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
        // The composition properties are useful when drawing on a composited surface
        // It has no effect when drawing on a Control's plain surface
        e.Graphics.CompositingMode = CompositingMode.SourceOver;
        e.Graphics.CompositingQuality = CompositingQuality.HighQuality;

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

使用这个类和相关方法的视觉效果:

类对象,用作参考:

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)) {
            currentScreenDPI = g.DpiY; 
        }
        Text = text;
        FontFamily = fontFamily;
        SizeInPoints = FontSize;
        FillColor = Color.Black;
        Outline = new Pen(Color.Black, 1);
        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 => m_SizeInPoints;
        set {  m_SizeInPoints = value;
               m_SizeInPixels = (value * 72F) / currentScreenDPI;
               SizeInEms = GetEmSize();
        }
    }
    public float SizeInPixels {
        get => m_SizeInPixels;
        set {  m_SizeInPixels = value;
               m_SizeInPoints = (value * currentScreenDPI) / 72F;
               SizeInEms = GetEmSize();
        }
    }

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

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

带有字体系列的组合框
创建一个自定义 ComboBox 并设置其 DrawMode = OwnerDrawVariable:

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";

事件处理程序:

private void cboFontFamily_DrawItem(object sender, DrawItemEventArgs e)
{
    if ((cboFontFamily.Items.Count == 0) || e.Index < 0) return;
    e.DrawBackground();
   
    var flags = TextFormatFlags.Left | TextFormatFlags.VerticalCenter;
    using (var family = new FontFamily(cboFontFamily.GetItemText(cboFontFamily.Items[e.Index])))
    using (var 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();
}

【讨论】:

  • 谢谢@Jimi!!!现在我学到了一些东西。这是我的输出imgur.com/a/C0FLWcs
  • @untargeted 是的。 GraphicsPath 是一个很棒的工具。经常被低估。但是,一旦您了解了它的特殊性,您就可以创造奇迹(好吧,好吧,我是粉丝 :)。看看实际的Ems 计算。 Em 的大小不同于 Point。如果您将PointsGraphicsPath 一起使用,您的措施可能会关闭。不多,但关闭。
  • ,我还有一个问题。您是如何在组合框中使用自己的字体样式加载字体系列的?
  • 您使用的是SelectionChangeCommitted 还是SelectedIndexChangedIndex = -1 在第一次设置 SelectedIndex 时发生,当您填写项目列表时。它总是-1。这就是我使用SelectionChangeCommitted 的原因。此事件仅在有人手动选择某些内容后引发。如果您在SelectedIndexChanged 中有其他代码,请在DrawItemreturn 中添加检查,如果e.Index &lt; 0
  • 我不知道。 SelectedindexChanged 中有代码吗?如果是这样,如果它们有不同的逻辑要执行,您可以决定保留两者。我通常只有SelectionChangeCommitted。但这就是我,我的习惯。无论如何,只需检查当前项目 Index &gt;= 0,如果没有其他问题,您就可以开始了。
【解决方案2】:

似乎您首先为字体大小提供了错误的度量,然后为画笔添加了额外的厚度。试试这个:

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

【讨论】:

  • 让我们做那个72f,好吗?
  • 还有@TaW。你总是潜伏在阴影中。哈哈
猜你喜欢
  • 2019-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-26
  • 2023-01-13
  • 2019-12-21
相关资源
最近更新 更多