使用 GraphicsPath 正确绘制文本
Properly draw text using GraphicsPath
如下图所示,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);
}
看来您一开始就提供了错误的字体大小测量方法,然后又为画笔添加了额外的粗细。试试这个:
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);
}
GraphicsPath class 以不同的方式计算 Text 对象的大小(如评论中所述)。文本大小是使用 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.GetEmHeight, FontFamily.GetCellAscent and FontFamily.GetCellDescent
我在这里插入您可以在文档中找到的图。
参考此处包含的一般信息:
Using Font and Text (MSDN)
本文档报告了有关如何转换点、像素和 Ems 的细节:
How to: Obtain Font Metrics (MSDN)
我假设您已经有一个 class 对象,其中 contains/references 来自 UI 控件的字体设置和所需的调整。
我在这里添加一个,其中包含一些属性,这些属性包含与问题相关的这些设置的子集。
此 class 根据用户选择的字体大小执行一些计算。
字体大小通常以磅为单位引用。然后使用当前屏幕 DPI
分辨率(或从像素维度转换为点),将此度量转换为像素。每个小节也在 Ems
中转换,如果您必须使用 GraphicsPath
绘制文本,这会派上用场。
Ems
大小是考虑到字体的 Ascent
和 Descent
计算的。
GraphicsPath
class 使用此度量效果更好,因为混合文本可以包含两个部分,如果没有,则该部分为 = 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);
}
}
}
使用此class及相关方法的视觉效果:
class 对象,用作参考:
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();
}
如下图所示,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);
}
看来您一开始就提供了错误的字体大小测量方法,然后又为画笔添加了额外的粗细。试试这个:
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);
}
GraphicsPath class 以不同的方式计算 Text 对象的大小(如评论中所述)。文本大小是使用 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.GetEmHeight, FontFamily.GetCellAscent and FontFamily.GetCellDescent
我在这里插入您可以在文档中找到的图。
参考此处包含的一般信息:
Using Font and Text (MSDN)
本文档报告了有关如何转换点、像素和 Ems 的细节:
How to: Obtain Font Metrics (MSDN)
我假设您已经有一个 class 对象,其中 contains/references 来自 UI 控件的字体设置和所需的调整。
我在这里添加一个,其中包含一些属性,这些属性包含与问题相关的这些设置的子集。
此 class 根据用户选择的字体大小执行一些计算。
字体大小通常以磅为单位引用。然后使用当前屏幕 DPI
分辨率(或从像素维度转换为点),将此度量转换为像素。每个小节也在 Ems
中转换,如果您必须使用 GraphicsPath
绘制文本,这会派上用场。
Ems
大小是考虑到字体的 Ascent
和 Descent
计算的。
GraphicsPath
class 使用此度量效果更好,因为混合文本可以包含两个部分,如果没有,则该部分为 = 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);
}
}
}
使用此class及相关方法的视觉效果:
class 对象,用作参考:
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();
}