在C#(.net)中兑现文字按指定的路径排列(沿线排版)

在C#(.net)中实现文字按指定的路径排列(沿线排版)

先看一下效果图:

在C#(.net)中兑现文字按指定的路径排列(沿线排版)

这个的难点在于排版的算法上,其他的就是对GDI+中GraphicsPath的应用。以下为核心部分的源代码:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;

namespace Lgms.net.Drawing
{
    public enum PathAlign //文字在路径方向的对齐方向
    {
        Left = 0,  //从第一点开始排列
        Center = 1,  //居中排列
        Right = 2 //居右排列
    }
    public enum TextPosition  //文字排列在路径上的位置
    {
        OverPath = 0,  //路径之上
        CenterPath = 1,  //路径中间,文字中心即路径经过区域
        UnderPath = 2  //路径下方
    }

    //基础类
    public class TextOnPath
    {
        private PathData _pathdataTOP;
        private string _textTOP;
        private Font _fontTOP;
        private Color _pathcolorTOP = Color.Red;
        private Color _colorTOP = Color.Black;
        private Color _fillcolorTOP = Color.Black;
        private PathAlign _pathalignTOP = PathAlign.Center;
        private int _letterspacepercentageTOP = 100;
        private bool _addsvg = false;
        private System.Text.StringBuilder _SVG;
        private int _currentPathID;
        private TextPosition _textPathPosition = TextPosition.CenterPath;
        public Exception LastError = null;
        public bool ShowPath = true;
        public TextPosition TextPathPosition
        {
            get { return _textPathPosition; }
            set { _textPathPosition = value; }
        }
        public PathData PathDataTOP
        {
            get { return _pathdataTOP; }
            set { _pathdataTOP = value; }
        }

        public string TextTOP
        {
            get { return _textTOP; }
            set { _textTOP = value; }
        }


        public Font FontTOP
        {
            get { return _fontTOP; }
            set { _fontTOP = value; }
        }


        public Color PathColorTOP
        {
            get { return _pathcolorTOP; }
            set { _pathcolorTOP = value; }
        }
        public Color ColorTOP
        {
            get { return _colorTOP; }
            set { _colorTOP = value; }
        }


        public Color FillColorTOP
        {
            get { return _fillcolorTOP; }
            set { _fillcolorTOP = value; }
        }


        public PathAlign PathAlignTOP
        {
            get { return _pathalignTOP; }
            set { _pathalignTOP = value; }
        }


        public int LetterSpacePercentage
        {
            get { return _letterspacepercentageTOP; }
            set { _letterspacepercentageTOP = value; }
        }


        public Bitmap TextOnPathBitmap(PathData _pathdata, string _text, Font _font, Color _color, Color _fillcolor, int _letterspacepercentage)
        {
            _pathdataTOP = _pathdata;
            _textTOP = _text;
            _fontTOP = new Font(_font, _fontTOP.Style);
            _colorTOP = _color;
            _fillcolorTOP = _fillcolor;
            _letterspacepercentageTOP = _letterspacepercentage;
            return TextOnPathBitmap();
        }

        //核心方法
        public Bitmap TextOnPathBitmap()
        {
            int i = 0;
            PointF[] _TmpPoints = null;
            PointF _TmpPoint = default(PointF);
            PointF[] _Points = new PointF[25001];
            //= oGP.PathPoints()
            int _count = 0;
            GraphicsPath _gp = new GraphicsPath(_pathdataTOP.Points, _pathdataTOP.Types);
            _gp.FillMode = FillMode.Winding;
            _gp.Flatten(null, 1);
            try
            {
                _TmpPoint = _gp.PathPoints[0];
                for (i = 0; i <= _gp.PathPoints.Length - 2; i++)
                {
                    if (_gp.PathTypes[i + 1] == (byte)(PathPointType.Start) | (_gp.PathTypes[i] & ((byte)(PathPointType.CloseSubpath))) == (byte)(PathPointType.CloseSubpath))
                    {
                        _TmpPoints = GetLinePoints(_gp.PathPoints[i], _TmpPoint, 1);
                        Array.ConstrainedCopy(_TmpPoints, 0, _Points, _count, _TmpPoints.Length);
                        _count += 1;
                        _TmpPoint = _gp.PathPoints[i + 1];
                    }
                    else
                    {
                        _TmpPoints = GetLinePoints(_gp.PathPoints[i], _gp.PathPoints[i + 1], 1);
                        Array.ConstrainedCopy(_TmpPoints, 0, _Points, _count, _TmpPoints.Length);
                        _count += _TmpPoints.Length - 1;
                    }
                }
                _TmpPoints = new PointF[_count + 1];
                Array.Copy(_Points, _TmpPoints, _count);
                _Points = CleanPoints(_TmpPoints);


                _count = _Points.Length - 1;


                return DrawText(_Points, _count);
            }
            catch (Exception ex)
            {
                LastError = ex;
                return null;
            }
        }


        private PointF[] CleanPoints(PointF[] _points)
        {
            int i = 0;
            PointF[] _tmppoints = new PointF[_points.Length + 1];
            PointF _lastpoint = default(PointF);
            int _count = 0;

            for (i = 0; i <= _points.Length - 1; i++)
            {
                if (i == 0 | _points[i].X != _lastpoint.X | _points[i].Y != _lastpoint.Y)
                {
                    _tmppoints[_count] = _points[i];
                    _count += 1;
                }
                _lastpoint = _points[i];
            }

            _points = new PointF[_count + 1];
            Array.Copy(_tmppoints, _points, _count);
            return _points;
        }

       //排版
        private Bitmap DrawText(PointF[] _Points, int _MaxPoints)
        {
            GraphicsPath _gp = new GraphicsPath(_pathdataTOP.Points, _pathdataTOP.Types);
            _gp.FillMode = FillMode.Winding;
            _gp.Flatten();

            Bitmap _bitmap = new Bitmap(
                Convert.ToInt32(_gp.GetBounds().Right + _fontTOP.Size * 2), 
                Convert.ToInt32(_gp.GetBounds().Bottom + _fontTOP.Size * 2)
                );
            _gp.Dispose();
            Graphics _G = Graphics.FromImage(_bitmap);
            int _count = 0;
            PointF _Point1 = default(PointF);
            PointF _Point2 = default(PointF);
            PointF _Point = default(PointF);
            int _CharStep = 0;
            int lStrWidth = 0;
            double _Angle = default(double);
            double _MaxWidthText = default(double);
            int i = 0;
            float[] _widths = null;
            //_widths = MeasureWidths(_G)
            Pen _pathpen = new Pen(_pathcolorTOP);
            if (ShowPath == true)
            {
                foreach (PointF _Point1_loopVariable in _Points)
                {
                    _Point1 = _Point1_loopVariable;
                    _G.DrawEllipse(_pathpen, _Point1.X, _Point1.Y, 1, 1);
                }
            }
            _pathpen.Dispose();

            for (i = 0; i <= _textTOP.Length - 1; i++)
            {
                _MaxWidthText += StringRegion(_G, i);
                // _widths(i)
            }

            switch (_pathalignTOP)
            {
                case PathAlign.Left:
                    _Point1 = _Points[0];
                    _count = 0;
                    break;


                case PathAlign.Center:
                    _count = (int)(_MaxPoints - _MaxWidthText) / 2;
                    if (_count > 0)
                    {
                        _Point1 = _Points[_count];
                    }
                    else
                    {
                        _Point1 = _Points[0];
                    }
                    break;


                case PathAlign.Right:
                    _count = (int)(_MaxPoints - _MaxWidthText - StringRegion(_G, _textTOP.Length - 1) * LetterSpacePercentage / 100);
                    if (_count > 0)
                    {
                        _Point1 = _Points[_count];
                    }
                    else
                    {
                        _Point1 = _Points[0];
                    }
                    break;
            }

            while (!(_CharStep > _textTOP.Length - 1))
            {
                lStrWidth = (int)StringRegion(_G, _CharStep) * LetterSpacePercentage / 100;
                if ((_count + lStrWidth / 2) >= 0 & (_count + lStrWidth) < _MaxPoints)
                {
                    _count += lStrWidth;
                    _Point2 = _Points[_count];
                    _Point = _Points[_count - lStrWidth / 2];
                    _Angle = GetAngle(_Point1, _Point2);
                    DrawRotatedText(_G, _textTOP[_CharStep].ToString(), (float)_Angle, _Point);

                    _Point1 = _Points[_count];
                }
                else
                {
                    _count += lStrWidth;
                }
                _CharStep += 1;
            }
            _G.Dispose();


            return _bitmap;
        }

        private float StringRegion(Graphics _g, int _textpos)
        {
            string measureString = _textTOP.Substring(_textpos, 1);
            int numChars = measureString.Length;
            CharacterRange[] characterRanges = new CharacterRange[numChars + 1];
            StringFormat stringFormat = new StringFormat();
            stringFormat.Trimming = StringTrimming.None;
            stringFormat.FormatFlags = StringFormatFlags.NoClip | StringFormatFlags.NoWrap | StringFormatFlags.LineLimit;
            SizeF size = _g.MeasureString(_textTOP, _fontTOP);
            RectangleF layoutRect = new RectangleF(0f, 0f, size.Width, size.Height);
            Region[] stringRegions = new Region[numChars + 1];
            characterRanges[0] = new CharacterRange(0, 1);
            stringFormat.FormatFlags = StringFormatFlags.NoClip;
            stringFormat.SetMeasurableCharacterRanges(characterRanges);
            stringFormat.Alignment = StringAlignment.Near;
            stringRegions = _g.MeasureCharacterRanges(_textTOP.Substring(_textpos), _fontTOP, layoutRect, stringFormat);

            return stringRegions[0].GetBounds(_g).Width;
        }

        private float[] MeasureWidths(Graphics graphics)
        {
            float[] widths = new float[_textTOP.Length + 1];
            StringFormat format = StringFormat.GenericTypographic;
            format.Trimming = StringTrimming.None;
            format.FormatFlags = StringFormatFlags.NoClip | StringFormatFlags.NoWrap | StringFormatFlags.LineLimit;
            graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;

            RectangleF layout = new RectangleF(0, 0, 10000, 10000);
            int remainder = _textTOP.Length;
            int start = 0;
            while (remainder > 0)
            {
                int length = remainder;
                if (length > 32)
                {
                    length = 32;
                }
                CharacterRange[] ranges = new CharacterRange[length + 1];
                int i = 0;
                while (i < ranges.Length)
                {
                    ranges[i] = new CharacterRange(start + i, 1);
                    i += 1;
                }
                format.SetMeasurableCharacterRanges(ranges);
                Region[] regions = graphics.MeasureCharacterRanges(_textTOP, _fontTOP, layout, format);
                i = 0;
                while (i < regions.Length)
                {
                    Region region = regions[i];
                    RectangleF cb = region.GetBounds(graphics);
                    widths[start + i] = cb.Width;
                    i += 1;
                }
                start += length;
                remainder -= 31;
            }
            return widths;
        }

        private double GetAngle(PointF _point1, PointF _point2)
        {
            double c = default(double);

            c = Math.Sqrt(Math.Pow((_point2.X - _point1.X), 2) + Math.Pow((_point2.Y - _point1.Y), 2));

            if (c == 0)
            {
                return 0;
            }

            if (_point1.X > _point2.X)
            {
                return Math.Asin((_point1.Y - _point2.Y) / c) * 180 / Math.PI - 180;
            }
            else
            {
                return Math.Asin((_point2.Y - _point1.Y) / c) * 180 / Math.PI;
            }
        }

        private void DrawRotatedText(Graphics _gr, string _text, float _angle, PointF _PointCenter)
        {
            StringFormat string_format = new StringFormat();
            string_format.Alignment = StringAlignment.Center;

            _gr.SmoothingMode = SmoothingMode.HighQuality;
            _gr.CompositingQuality = CompositingQuality.HighQuality;
            _gr.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
            GraphicsPath graphics_path = new GraphicsPath(System.Drawing.Drawing2D.FillMode.Winding);
            int x = Convert.ToInt32(_PointCenter.X);
            int y = Convert.ToInt32(_PointCenter.Y);

            switch (TextPathPosition)
            {
                case TextPosition.OverPath:
                    graphics_path.AddString(_text, _fontTOP.FontFamily, (int)_fontTOP.Style, (float)_fontTOP.Size, new Point(x, y - (int)_fontTOP.Size), string_format);
                    break;
                case TextPosition.CenterPath:
                    graphics_path.AddString(_text, _fontTOP.FontFamily, (int)_fontTOP.Style, (float)_fontTOP.Size, new Point(x, y - (int)_fontTOP.Size / 2), string_format);
                    break;
                case TextPosition.UnderPath:
                    graphics_path.AddString(_text, _fontTOP.FontFamily, (int)_fontTOP.Style, (float)_fontTOP.Size, new Point(x, y), string_format);
                    break;
            }

            Matrix rotation_matrix = new Matrix();
            rotation_matrix.RotateAt(_angle, new PointF(x, y));
            graphics_path.Transform(rotation_matrix);

            _gr.DrawPath(new Pen(_colorTOP), graphics_path);
            _gr.FillPath(new SolidBrush(_fillcolorTOP), graphics_path);

            graphics_path.Dispose();
        }

        public PointF[] GetLinePoints(PointF _p1, PointF _p2, int _stepWitdth)
        {
            int lCount = 0;
            PointF[] _tmpPoints = new PointF[10001];
            float _width = 0;
            float _height = 0;
            long d = 0;
            long ix = 0;
            long iy = 0;
            int dd = 0;
            int id = 0;
            int lStep = _stepWitdth;

            _p1.X = Convert.ToInt32(_p1.X);
            _p1.Y = Convert.ToInt32(_p1.Y);
            _p2.X = Convert.ToInt32(_p2.X);
            _p2.Y = Convert.ToInt32(_p2.Y);
            _width = _p2.X - _p1.X;
            _height = _p2.Y - _p1.Y;
            d = 0;

            if (_width < 0)
            {
                _width = -_width;
                ix = -1;
            }
            else
            {
                ix = 1;
            }

            if (_height < 0)
            {
                _height = -_height;
                iy = -1;
            }
            else
            {
                iy = 1;
            }

            if (_width > _height)
            {
                dd = (int)(_width + _width);
                id = (int)(_height + _height);

                do
                {
                    if (lStep == _stepWitdth)
                    {
                        _tmpPoints[lCount].X = _p1.X;
                        _tmpPoints[lCount].Y = _p1.Y;
                        lCount += 1;
                    }
                    else
                    {
                        lStep += _stepWitdth;
                    }
                    if (Convert.ToInt32(_p1.X) == Convert.ToInt32(_p2.X))
                        break; 

                    _p1.X += ix;
                    d += id;

                    if (d > _width)
                    {
                        _p1.Y += iy;
                        d -= dd;
                    }
                } while (true);


            }
            else
            {
                dd = (int)(_height + _height);
                id = (int)(_width + _width);

                do
                {
                    if (lStep == _stepWitdth)
                    {
                        _tmpPoints[lCount].X = _p1.X;
                        _tmpPoints[lCount].Y = _p1.Y;
                        lCount += 1;
                    }
                    else
                    {
                        lStep += _stepWitdth;
                    }
                    if (Convert.ToInt32(_p1.Y) == Convert.ToInt32(_p2.Y))
                        break; // TODO: might not be correct. Was : Exit Do
                    _p1.Y += iy;
                    d += id;

                    if (d > _height)
                    {
                        _p1.X += ix;
                        d -= dd;
                    }
                } while (true);
            }

            PointF[] _tmpPoints2 = null;
            _tmpPoints2 = new PointF[lCount + 1];
            Array.Copy(_tmpPoints, _tmpPoints2, lCount);
            return _tmpPoints2;
        }
    }
}


下为测试部分的核心源代码:

        PointF[] _points = new PointF[8];
        TextOnPath _top = new TextOnPath();
      

//初始化的相关代码:

            _points[0] = new PointF(303, 485);
            _points[1] = new PointF(476, 616);
            _points[2] = new PointF(745, 599);
            _points[3] = new PointF(876, 434);
            _points[4] = new PointF(829, 179);
            _points[5] = new PointF(657, 73);
            _points[6] = new PointF(518, 269);
            _points[7] = new PointF(308, 349);

            _top.FontTOP = new Font("楷体_GB2312", 48, FontStyle.Bold);
            _top.FillColorTOP = Color.Red;
            _top.ColorTOP = Color.Black;
            _top.TextTOP = "龙岗民生网http://www.lgms.net";
            _top.ShowPath = true;
            _top.PathAlignTOP = PathAlign.Left;
            _top.LetterSpacePercentage = 80;
            _top.TextPathPosition = TextPosition.OverPath;

//关键的调用方法:
        private Image DrawText()
        {
            GraphicsPath _gp = new GraphicsPath();
                _gp.AddClosedCurve(_points);

            _top.PathDataTOP = _gp.PathData;

            return _top.TextOnPathBitmap();
        }

在相应地方加上DrawText()即可。