使用 GDI+ 绘制有间距的文本

原创 2004年10月25日 11:56:00

在 .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 会在内部创建一个中文字体(通常是宋体)然后使用这个字体进行绘制(这个分析和匹配过程是相当复杂的,并且没有任何文档记载)。

GDI文字绘制和windows下opengl文字绘制小结

GDI绘制文字,要设置文字的属性,主要就是通过设置DC中的font对象来实现的。 font对象的创建可以由createfont()、createfontIndirect()来实现。 HFONT Cr...
  • T_W_S
  • T_W_S
  • 2013年11月27日 16:44
  • 3722

Java中调整字距与行距的方法 其一(以DrawString为例)

今天要谈的是如何在Java中调整字距与行距,当然,这里谈的主要是针对swing与awt里的字距与行距。 调整字距与行距看起来似乎是个很简单的问题,大概可以直接调用某个内置的方法来操作,紫雾我最开始也...
  • zixiaomuwu
  • zixiaomuwu
  • 2016年04月05日 22:29
  • 6947

跟奥巴马一起编程

题目描述 美国总统奥巴马不仅呼吁所有人都学习编程,甚至以身作则编写代码,成为美国历史上首位编写计算机代码的总统。2014年底,为庆祝“计算机科学教育周”正式启动,奥巴马编写了很简单的计算机代码:...
  • ESESZB
  • ESESZB
  • 2016年06月22日 14:36
  • 348

Java中调整字距与行距的方法 其一(以DrawString为例)

今天要谈的是如何在Java中调整字距与行距,当然,这里谈的主要是针对swing与awt里的字距与行距。调整字距与行距看起来似乎是个很简单的问题,大概可以直接调用某个内置的方法来操作,紫雾我最开始也是这...
  • liuxinyang666
  • liuxinyang666
  • 2018年01月03日 23:40
  • 79

Graphics.DrawString 方法

MSDN上的解释: 在指定位置并且用指定的 Brush 和 Font 对象绘制指定的文本字符串。 public void DrawString( string s, Font font, ...
  • Fire870923chen
  • Fire870923chen
  • 2014年07月16日 17:04
  • 40422

C#利用GDI+绘制旋转文字,矩形内可以根据布局方式排列文本

C#中利用GDI+绘制旋转文本的文字,网上有很多资料,基本都使用矩阵旋转的方式实现。但基本都只提及按点旋转,若要实现在矩形范围内旋转文本,资料较少。经过琢磨,可以将矩形内旋转转化为按点旋转,不过需要经...
  • alicehyxx
  • alicehyxx
  • 2013年11月28日 17:31
  • 11571

Graphics实现指定字符间距

  • 2013年04月26日 11:50
  • 26KB
  • 下载

Windows GDI:CDC绘制文本

函数原型int CDC::DrawText( const CString& str, LPRECT lpRect, UINT nFormat);功能使用DC关联的字体,文本颜色,背景...
  • wangyao1052
  • wangyao1052
  • 2015年05月16日 00:13
  • 689

C++ GDI+ DrawString 保存字符串文字为透明图片

实现通过GDI+将输入的字符串保存为背景透明的图片的示例代码
  • aoshilang2249
  • aoshilang2249
  • 2014年07月30日 19:47
  • 3659

用GDI+使文字轻松旋转

用GDI+使文字轻松旋转 下载本文代码见资源 在老式的Windows图形设备接口中制作旋转文字会是一件痛苦费力的工作,但在.NET...
  • metababy
  • metababy
  • 2006年01月14日 18:27
  • 8868
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:使用 GDI+ 绘制有间距的文本
举报原因:
原因补充:

(最多只允许输入30个字)