// THIS FILE IS PART OF SVG PROJECT // THE SVG PROJECT IS AN OPENSOURCE LIBRARY LICENSED UNDER THE MS-PL License. // COPYRIGHT (C) svg-net. ALL RIGHTS RESERVED. // GITHUB: https://github.com/svg-net/SVG using AntdUI.Svg.Pathing; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; namespace AntdUI.Svg { public static class PointFExtensions { public static string ToSvgString(this PointF p) { return p.X.ToString() + " " + p.Y.ToString(); } } internal class SvgPathConverter { /// /// Parses the specified string into a collection of path segments. /// /// A containing path data. public static SvgPathSegmentList Parse(string value) { if (string.IsNullOrEmpty(value)) throw new ArgumentNullException("path"); var segments = new SvgPathSegmentList(); try { char command; bool isRelative; foreach (var commandSet in SplitCommands(value.TrimEnd(null))) { command = commandSet[0]; isRelative = char.IsLower(command); CreatePathSegment(command, segments, new CoordinateParser(commandSet.Trim()), isRelative); } } catch { } return segments; } static void CreatePathSegment(char command, SvgPathSegmentList segments, CoordinateParser parser, bool isRelative) { var coords = new float[6]; switch (command) { case 'm': // relative moveto case 'M': // moveto if (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1])) { segments.Add(new SvgMoveToSegment(ToAbsolute(coords[0], coords[1], segments, isRelative))); } while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1])) { segments.Add(new SvgLineSegment(segments.Last.End, ToAbsolute(coords[0], coords[1], segments, isRelative))); } break; case 'a': case 'A': bool size; bool sweep; while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) && parser.TryGetFloat(out coords[2]) && parser.TryGetBool(out size) && parser.TryGetBool(out sweep) && parser.TryGetFloat(out coords[3]) && parser.TryGetFloat(out coords[4])) { // A|a rx ry x-axis-rotation large-arc-flag sweep-flag x y segments.Add(new SvgArcSegment(segments.Last.End, coords[0], coords[1], coords[2], (size ? SvgArcSize.Large : SvgArcSize.Small), (sweep ? SvgArcSweep.Positive : SvgArcSweep.Negative), ToAbsolute(coords[3], coords[4], segments, isRelative))); } break; case 'l': // relative lineto case 'L': // lineto while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1])) { segments.Add(new SvgLineSegment(segments.Last.End, ToAbsolute(coords[0], coords[1], segments, isRelative))); } break; case 'H': // horizontal lineto case 'h': // relative horizontal lineto while (parser.TryGetFloat(out coords[0])) { segments.Add(new SvgLineSegment(segments.Last.End, ToAbsolute(coords[0], segments.Last.End.Y, segments, isRelative, false))); } break; case 'V': // vertical lineto case 'v': // relative vertical lineto while (parser.TryGetFloat(out coords[0])) { segments.Add(new SvgLineSegment(segments.Last.End, ToAbsolute(segments.Last.End.X, coords[0], segments, false, isRelative))); } break; case 'Q': // curveto case 'q': // relative curveto while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) && parser.TryGetFloat(out coords[2]) && parser.TryGetFloat(out coords[3])) { segments.Add(new SvgQuadraticCurveSegment(segments.Last.End, ToAbsolute(coords[0], coords[1], segments, isRelative), ToAbsolute(coords[2], coords[3], segments, isRelative))); } break; case 'T': // shorthand/smooth curveto case 't': // relative shorthand/smooth curveto while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1])) { var lastQuadCurve = segments.Last as SvgQuadraticCurveSegment; var controlPoint = lastQuadCurve != null ? Reflect(lastQuadCurve.ControlPoint, segments.Last.End) : segments.Last.End; segments.Add(new SvgQuadraticCurveSegment(segments.Last.End, controlPoint, ToAbsolute(coords[0], coords[1], segments, isRelative))); } break; case 'C': // curveto case 'c': // relative curveto while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) && parser.TryGetFloat(out coords[2]) && parser.TryGetFloat(out coords[3]) && parser.TryGetFloat(out coords[4]) && parser.TryGetFloat(out coords[5])) { segments.Add(new SvgCubicCurveSegment(segments.Last.End, ToAbsolute(coords[0], coords[1], segments, isRelative), ToAbsolute(coords[2], coords[3], segments, isRelative), ToAbsolute(coords[4], coords[5], segments, isRelative))); } break; case 'S': // shorthand/smooth curveto case 's': // relative shorthand/smooth curveto while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) && parser.TryGetFloat(out coords[2]) && parser.TryGetFloat(out coords[3])) { var lastCubicCurve = segments.Last as SvgCubicCurveSegment; var controlPoint = lastCubicCurve != null ? Reflect(lastCubicCurve.SecondControlPoint, segments.Last.End) : segments.Last.End; segments.Add(new SvgCubicCurveSegment(segments.Last.End, controlPoint, ToAbsolute(coords[0], coords[1], segments, isRelative), ToAbsolute(coords[2], coords[3], segments, isRelative))); } break; case 'Z': // closepath case 'z': // relative closepath segments.Add(new SvgClosePathSegment()); break; } } static PointF Reflect(PointF point, PointF mirror) { float x, y, dx, dy; dx = Math.Abs(mirror.X - point.X); dy = Math.Abs(mirror.Y - point.Y); if (mirror.X >= point.X) { x = mirror.X + dx; } else { x = mirror.X - dx; } if (mirror.Y >= point.Y) { y = mirror.Y + dy; } else { y = mirror.Y - dy; } return new PointF(x, y); } /// /// Creates point with absolute coorindates. /// /// Raw X-coordinate value. /// Raw Y-coordinate value. /// Current path segments. /// true if and contains relative coordinate values, otherwise false. /// that contains absolute coordinates. static PointF ToAbsolute(float x, float y, SvgPathSegmentList segments, bool isRelativeBoth) { return ToAbsolute(x, y, segments, isRelativeBoth, isRelativeBoth); } /// /// Creates point with absolute coorindates. /// /// Raw X-coordinate value. /// Raw Y-coordinate value. /// Current path segments. /// true if contains relative coordinate value, otherwise false. /// true if contains relative coordinate value, otherwise false. /// that contains absolute coordinates. static PointF ToAbsolute(float x, float y, SvgPathSegmentList segments, bool isRelativeX, bool isRelativeY) { var point = new PointF(x, y); if ((isRelativeX || isRelativeY) && segments.Count > 0) { var lastSegment = segments.Last; // if the last element is a SvgClosePathSegment the position of the previous element should be used because the position of SvgClosePathSegment is 0,0 if (lastSegment is SvgClosePathSegment) lastSegment = segments.Reverse().OfType().First(); if (isRelativeX) { point.X += lastSegment.End.X; } if (isRelativeY) { point.Y += lastSegment.End.Y; } } return point; } static IEnumerable SplitCommands(string path) { var commandStart = 0; for (var i = 0; i < path.Length; i++) { string command; if (char.IsLetter(path[i]) && path[i] != 'e' && path[i] != 'E') //e is used in scientific notiation. but not svg path { 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; } } } } } }