/// <summary> /// Converts a string containing SVG path data to a sequence of <see cref="PathPiece"/> objects.</summary> /// <param name="svgPath"> /// SVG path data to parse.</param> public static IEnumerable <PathPiece> DecodePieces(string svgPath) { // Parse all the commands and coordinates var numRegex = @"-?\d*(?:\.\d*)?\d(?:e-?\d+)?\s*,?\s*"; var prevPoint = new PointD(0, 0); var prevStartPoint = (PointD?)null; var prevControlPoint = new PointD(0, 0); svgPath = svgPath.TrimStart(); while (!string.IsNullOrWhiteSpace(svgPath)) { Match m; if ((m = Regex.Match(svgPath, @"^[MLCHVS]\s*({0})*".Fmt(numRegex), RegexOptions.IgnoreCase)).Success) { PathPieceType type; PointD[] points; bool prevControlPointDetermined = false; var numbers = m.Groups[1].Captures.Cast <Capture>().Select(c => double.Parse(c.Value.Trim().TrimEnd(',').Trim())).ToArray(); switch (m.Value[0]) { case 'M': type = PathPieceType.Move; points = numbers.Split(2).Select(gr => new PointD(gr.First(), gr.Last())).ToArray(); prevPoint = points[points.Length - 1]; break; case 'm': type = PathPieceType.Move; points = numbers.Split(2).Select(gr => new PointD(gr.First(), gr.Last())).ToArray(); for (int i = 0; i < points.Length; i++) { prevPoint = (points[i] += prevPoint); } break; case 'L': type = PathPieceType.Line; points = numbers.Split(2).Select(gr => new PointD(gr.First(), gr.Last())).ToArray(); prevPoint = points[points.Length - 1]; break; case 'l': type = PathPieceType.Line; points = numbers.Split(2).Select(gr => new PointD(gr.First(), gr.Last())).ToArray(); for (int i = 0; i < points.Length; i++) { prevPoint = (points[i] += prevPoint); } break; case 'H': type = PathPieceType.Line; points = numbers.Select(x => new PointD(x, prevPoint.Y)).ToArray(); prevPoint = points.Last(); break; case 'h': type = PathPieceType.Line; points = new PointD[numbers.Length]; for (int i = 0; i < numbers.Length; i++) { prevPoint = points[i] = new PointD(prevPoint.X + numbers[i], prevPoint.Y); } break; case 'V': type = PathPieceType.Line; points = numbers.Select(y => new PointD(prevPoint.X, y)).ToArray(); prevPoint = points.Last(); break; case 'v': type = PathPieceType.Line; points = new PointD[numbers.Length]; for (int i = 0; i < numbers.Length; i++) { prevPoint = points[i] = new PointD(prevPoint.X, prevPoint.Y + numbers[i]); } break; case 'C': type = PathPieceType.Curve; points = numbers.Split(2).Select(x => new PointD(x.First(), x.Last())).ToArray(); prevPoint = points.Last(); prevControlPoint = points.SkipLast(1).Last(); prevControlPointDetermined = true; break; case 'c': type = PathPieceType.Curve; points = numbers.Split(2).Select(x => new PointD(x.First(), x.Last())).ToArray(); Ut.Assert(points.Length % 3 == 0); for (int i = 0; i < points.Length; i += 3) { points[i] += prevPoint; points[i + 1] += prevPoint; prevPoint = (points[i + 2] += prevPoint); } prevPoint = points.Last(); prevControlPoint = points.SkipLast(1).Last(); prevControlPointDetermined = true; break; case 'S': type = PathPieceType.Curve; var pointsList1 = new List <PointD>(); foreach (var pair in numbers.Split(2).Select(x => new PointD(x.First(), x.Last())).Split(2)) { Ut.Assert(pair.Count() == 2); pointsList1.Add(prevPoint + (prevPoint - prevControlPoint)); pointsList1.Add(prevControlPoint = pair.First()); pointsList1.Add(prevPoint = pair.Last()); } points = pointsList1.ToArray(); prevControlPointDetermined = true; break; case 's': type = PathPieceType.Curve; var pointsList2 = new List <PointD>(); foreach (var pair in numbers.Split(2).Select(x => new PointD(x.First(), x.Last())).Split(2)) { Ut.Assert(pair.Count() == 2); pointsList2.Add(prevPoint + (prevPoint - prevControlPoint)); pointsList2.Add(prevControlPoint = (pair.First() + prevPoint)); pointsList2.Add(prevPoint = (pair.Last() + prevPoint)); } points = pointsList2.ToArray(); prevControlPointDetermined = true; break; default: throw new InvalidOperationException(); } if (prevStartPoint == null) { prevStartPoint = points[0]; } if (!prevControlPointDetermined) { prevControlPoint = prevPoint; } yield return(new PathPiece(type, points)); } else if ((m = Regex.Match(svgPath, @"^Z\s*", RegexOptions.IgnoreCase)).Success) { yield return(PathPiece.End); prevPoint = prevStartPoint ?? new PointD(0, 0); prevStartPoint = null; } else if ((m = Regex.Match(svgPath, @"^A\s*(({0})({0})({0})([01])[\s,]*([01])[\s,]*({0})({0}))+".Fmt(numRegex), RegexOptions.IgnoreCase)).Success) { for (var cp = 0; cp < m.Groups[1].Captures.Count; cp++) { double convert(int gr) => double.Parse(m.Groups[gr].Captures[cp].Value.Trim().TrimEnd(',').Trim()); var p = new PointD(convert(7), convert(8)); prevPoint = m.Value[0] == 'a' ? p + prevPoint : p; yield return(new PathPieceArc(convert(2), convert(3), convert(4), m.Groups[5].Captures[cp].Value != "0", m.Groups[6].Captures[cp].Value != "0", prevPoint)); } } else { Debugger.Break(); throw new NotImplementedException(); } svgPath = svgPath.Substring(m.Length); } }