SvgPathBuilder.cs 9.07 KB
Newer Older
davescriven's avatar
davescriven committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Xml;
using System.Text.RegularExpressions;
using System.Diagnostics;

using Svg.Pathing;

namespace Svg
{
    internal class SvgPathBuilder : TypeConverter
    {
        public static SvgPathSegmentList Parse(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentNullException("path");
            }

            SvgPathSegmentList segments = new SvgPathSegmentList();

            try
            {
                IEnumerable<PointF> coords;
                char command;
                PointF controlPoint;
                SvgQuadraticCurveSegment lastQuadCurve;
                SvgCubicCurveSegment lastCubicCurve;
                List<PointF> pointCache = new List<PointF>();

                foreach (string commandSet in SvgPathBuilder.SplitCommands(path.TrimEnd(null)))
                {
                    coords = SvgPathBuilder.ParseCoordinates(commandSet, segments);
                    command = commandSet[0];
                    // http://www.w3.org/TR/SVG11/paths.html#PathDataGeneralInformation

                    switch (command)
                    {
                        case 'm': // relative moveto
                        case 'M': // moveto
                            foreach (PointF point in coords)
                            {
                                segments.Add(new SvgMoveToSegment(point));
                            }
                            break;
                        case 'a':
                        case 'A':
                            throw new NotImplementedException("Arc segments are not yet implemented");
                        case 'l': // relative lineto
                        case 'L': // lineto
                            foreach (PointF point in coords)
                            {
                                segments.Add(new SvgLineSegment(segments.Last.End, point));
                            }
                            break;
                        case 'H': // horizontal lineto
                        case 'h': // relative horizontal lineto
                            foreach (PointF point in coords)
                            {
                                segments.Add(new SvgLineSegment(segments.Last.End, new PointF(segments.Last.End.X, point.Y)));
                            }
                            break;
                        case 'V': // vertical lineto
                        case 'v': // relative vertical lineto
                            foreach (PointF point in coords)
                            {
                                segments.Add(new SvgLineSegment(segments.Last.End, new PointF(point.X, segments.Last.End.Y)));
                            }
                            break;
                        case 'Q': // curveto
                        case 'q': // relative curveto
                            pointCache.Clear();
                            foreach (PointF point in coords) { pointCache.Add(point); }

                            for (int i = 0; i < pointCache.Count; i += 2)
                            {
                                segments.Add(new SvgQuadraticCurveSegment(segments.Last.End, pointCache[i], pointCache[i + 1]));
                            }
                            break;
                        case 'T': // shorthand/smooth curveto
                        case 't': // relative shorthand/smooth curveto
                            foreach (PointF point in coords)
                            {
                                lastQuadCurve = segments.Last as SvgQuadraticCurveSegment;

                                if (lastQuadCurve != null)
                                    controlPoint = Reflect(lastQuadCurve.ControlPoint, segments.Last.End);
                                else
                                    controlPoint = segments.Last.End;

                                segments.Add(new SvgQuadraticCurveSegment(segments.Last.End, controlPoint, point));
                            }
                            break;
                        case 'C': // curveto
                        case 'c': // relative curveto
                            pointCache.Clear();
                            foreach (PointF point in coords) { pointCache.Add(point); }

                            for (int i = 0; i < pointCache.Count; i += 3)
                            {
                                segments.Add(new SvgCubicCurveSegment(segments.Last.End, pointCache[i], pointCache[i + 1], pointCache[i + 2]));
                            }
                            break;
                        case 'S': // shorthand/smooth curveto
                        case 's': // relative shorthand/smooth curveto
                            pointCache.Clear();
                            foreach (PointF point in coords) { pointCache.Add(point); }

                            for (int i = 0; i < pointCache.Count; i += 2)
                            {
                                lastCubicCurve = segments.Last as SvgCubicCurveSegment;

                                if (lastCubicCurve != null)
                                {
                                    controlPoint = Reflect(lastCubicCurve.SecondControlPoint, segments.Last.End);
                                }
                                else
                                {
                                    controlPoint = segments.Last.End;
                                }

                                segments.Add(new SvgCubicCurveSegment(segments.Last.End, controlPoint, pointCache[i], pointCache[i + 1]));
                            }
                            break;
                        case 'Z': // closepath
                        case 'z': // relative closepath
                            segments.Add(new SvgClosePathSegment());
                            break;
                    }
                }
            }
            catch
            {
                Trace.TraceError("Error parsing path \"{0}\".", path);
            }

            return segments;
        }

        private static PointF Reflect(PointF point, PointF mirror)
        {
            // TODO: Only works left to right???
            float x = mirror.X + (mirror.X - point.X);
            float y = mirror.Y + (mirror.Y - point.Y);

            return new PointF(Math.Abs(x), Math.Abs(y));
        }

        private static PointF ToAbsolute(PointF point, SvgPathSegmentList segments)
        {
            PointF lastPoint = segments.Last.End;
            return new PointF(lastPoint.X + point.X, lastPoint.Y + point.Y);
        }

        private static IEnumerable<string> SplitCommands(string path)
        {
            int commandStart = 0;
            string command = null;

            for (int i = 0; i < path.Length; i++)
            {
                if (char.IsLetter(path[i]))
                {
                    command = path.Substring(commandStart, i - commandStart).Trim();
                    commandStart = i;

                    if (!string.IsNullOrEmpty(command))
                    {
                        yield return command;
                    }

                    if (path.Length == i + 1)
                    {
                        yield return path[i].ToString();
                    }
                }
                else if (path.Length == i + 1)
                {
                    command = path.Substring(commandStart, i - commandStart + 1).Trim();

                    if (!string.IsNullOrEmpty(command))
                    {
                        yield return command;
                    }
                }
            }
        }

        private static IEnumerable<PointF> ParseCoordinates(string coords, SvgPathSegmentList segments)
        {
            // TODO: Handle "1-1" (new PointF(1, -1);
            string[] parts = coords.Remove(0, 1).Replace("-", " -").Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
            float x;
            float y;
            PointF point;
            bool relative = char.IsLower(coords[0]);

            for (int i = 0; i < parts.Length; i += 2)
            {
                x = float.Parse(parts[i]);
                y = float.Parse(parts[i + 1]);
                point = new PointF(x, y);

                if (relative)
                {
                    point = SvgPathBuilder.ToAbsolute(point, segments);
                }

                yield return point;
            }
        }

        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            if (value is string)
            {
                return SvgPathBuilder.Parse((string)value);
            }

            return base.ConvertFrom(context, culture, value);
        }
    }
}