在 .NET Framework 中 Graphics.DrawString 方法提供了基本的文本绘制功能。然而,这个方法本身缺乏对字符格式的控制能力,例如不支持多数文本处理器支持的字符间距(大概微软认为不会有人编写基于 .NET 的文本处理器)。这个问题最简单的解决方法是将整个字符串“化整为零”,一个字符一个字符的按照指定间距画出来。然而这样做会产生大量的临时字符串,而且有巨大的 PInvoke 代价。那有没有其他的方法呢?答案是肯定的——GDI+ 底层提供了 GdipDrawDriverString 方法,允许我们对单个字符的输出位置进行控制。遗憾的是也许因为这个方法太底层了,所以在 .NET Framework 中并没有针对它的封装。(顺便说一下,Office 从 Office XP 开始就使用 GDI+ 作为绘图引擎,象 Visio 中的文本绘制就使用了 GdipDrawDriverString)
下面是对 GdipDrawDriverString 的简单封装(GdiplusMethods.cs):
using System; using System.Drawing.Drawing2D; using System.Reflection; using System.Runtime.InteropServices;
namespace System.Drawing { /// <summary> /// Summary description for GdiplusMethods. /// </summary> public class GdiplusMethods { private GdiplusMethods() { }
private enum DriverStringOptions { CmapLookup = 1, Vertical = 2, Advance = 4, LimitSubpixel = 8, }
public static void DrawDriverString(Graphics graphics, string text, Font font, Brush brush, PointF[] positions) { DrawDriverString(graphics, text, font, brush, positions, null); }
public static void DrawDriverString(Graphics graphics, string text, Font font, Brush brush, PointF[] positions, Matrix matrix) { if (graphics == null) throw new ArgumentNullException("graphics"); if (text == null) throw new ArgumentNullException("text"); if (font == null) throw new ArgumentNullException("font"); if (brush == null) throw new ArgumentNullException("brush"); if (positions == null) throw new ArgumentNullException("positions");
// Get hGraphics FieldInfo field = typeof(Graphics).GetField("nativeGraphics", BindingFlags.Instance | BindingFlags.NonPublic); IntPtr hGraphics = (IntPtr)field.GetValue(graphics);
// Get hFont field = typeof(Font).GetField("nativeFont", BindingFlags.Instance | BindingFlags.NonPublic); IntPtr hFont = (IntPtr)field.GetValue(font);
// Get hBrush field = typeof(Brush).GetField("nativeBrush", BindingFlags.Instance | BindingFlags.NonPublic); IntPtr hBrush = (IntPtr)field.GetValue(brush);
// Get hMatrix IntPtr hMatrix = IntPtr.Zero; if (matrix != null) { field = typeof(Matrix).GetField("nativeMatrix", BindingFlags.Instance | BindingFlags.NonPublic); hMatrix = (IntPtr)field.GetValue(matrix); }
int result = GdipDrawDriverString(hGraphics, text, text.Length, hFont, hBrush, positions, (int)DriverStringOptions.CmapLookup, hMatrix); }
[DllImport("Gdiplus.dll", CharSet=CharSet.Unicode)] internal extern static int GdipMeasureDriverString(IntPtr graphics, string text, int length, IntPtr font, PointF[] positions, int flags, IntPtr matrix, ref RectangleF bounds);
[DllImport("Gdiplus.dll", CharSet=CharSet.Unicode)] internal extern static int GdipDrawDriverString(IntPtr graphics, string text, int length, IntPtr font, IntPtr brush, PointF[] positions, int flags, IntPtr matrix); } }
下面我们来试验一下:新建一个窗体,在 OnPaint 事件里加入如下代码:
protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e);
Graphics g = e.Graphics; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; Brush brush = new SolidBrush(Color.Red); Font font = new Font("宋体", 12.0f);
string s = "Test中文オンエア版"; SizeF size = g.MeasureString(s, font, int.MaxValue);
PointF[] positions = new PointF[s.Length]; for (int i = 0; i < s.Length; i++) positions[i] = new PointF(i * 40 + 20, size.Height);
GdiplusMethods.DrawDriverString(g, s, font, brush, positions); }
在这里,我们使用 PointF[] positions 这个数组来控制字符的输出位置。需要注意的是坐标指的是字符的左下角,而不是左上角(计算机科学家和数学家使用不同的坐标系)。
有趣的是,如果我们把字体从“宋体”改为英文字体,如“Arial”,那么非英文字母将显示成一个框。这是因为 GdipDrawDriverString 就像它的名字暗示的那样,它直接使用指定字体绘制文本而不做其他处理。而 GdipDrawString 则会对文本进行分析,例如如果我们使用一个英文字体绘制中文字,则 GdipDrawString 会在内部创建一个中文字体(通常是宋体)然后使用这个字体进行绘制(这个分析和匹配过程是相当复杂的,并且没有任何文档记载)。

|