public static IEnumerable <Point> GetPointsFromSVGPathsString(string svgPathsString) { List <Point> points = new List <Point>(); var svgPaths = GetSVGCommandsFromSVGPathsString(svgPathsString); Point currentPoint = new Point(0, 0); foreach (var command in svgPaths) { switch (command.SVGCommandType) { case SVGCommandType.M: currentPoint = new Point(command.XYParams.FirstOrDefault(), command.XYParams.Skip(1).FirstOrDefault()); points.Add(currentPoint); break; case SVGCommandType.c: case SVGCommandType.C: double dX = (command.SVGCommandType == SVGCommandType.c) ? currentPoint.X : 0; double dY = (command.SVGCommandType == SVGCommandType.c) ? currentPoint.Y : 0; Point p0 = currentPoint; Point p1 = new Point(dX + command.XYParams.FirstOrDefault(), dY + command.XYParams.Skip(1).FirstOrDefault()); Point p2 = new Point(dX + command.XYParams.Skip(2).FirstOrDefault(), dY + command.XYParams.Skip(3).FirstOrDefault()); Point p3 = new Point(dX + command.XYParams.Skip(4).FirstOrDefault(), dY + command.XYParams.Skip(5).FirstOrDefault()); currentPoint = p3; points.AddRange(SVGMath.CalculateCubicBeizer(p0, p1, p2, p3, 100)); break; } } return(points); }
/// <summary> /// Returns a list of XY coordinates for an SVG path string. /// /// Drawing lines between these coordinates will allow you to draw the SVG image (although you lose fill/colour/line thickness /// information). /// </summary> /// <param name="svgPathsString">SVG Path string</param> /// <param name="pointsPerBeizerCommand">Number of points to use for Beizer commands</param> /// <returns>Enumerable list of Points</returns> public static SVGPathData GetPointsFromSVGPathsString(string svgPathsString, int pointsPerBeizerCommand = 100) { SVGPathData pathData = new SVGPathData(); List <Point> points = new List <Point>(); var svgPaths = GetSVGCommandsFromSVGPathsString(svgPathsString); Point currentPoint = new Point(0, 0); Point? p0 = null; Point? p1 = null; Point? p2 = null; Point? p3 = null; Point? q0 = null; Point? q1 = null; Point? q2 = null; foreach (var command in svgPaths) { // if lower case use delta values instead of absolute double dX = (char.IsLower(command.SVGCommandType.ToString().FirstOrDefault())) ? currentPoint.X : 0; double dY = (char.IsLower(command.SVGCommandType.ToString().FirstOrDefault())) ? currentPoint.Y : 0; bool keepQValues = false; bool keepPValues = false; // Handle commands switch (command.SVGCommandType) { // Fix move, this needs to return an array of SVGPathData if multiple move commands case SVGCommandType.m: case SVGCommandType.M: case SVGCommandType.l: case SVGCommandType.L: for (int i = 0; i < command.XYParams.Count(); i += 2) { currentPoint = new Point(dX + command.XYParams[i], dY + command.XYParams[i + 1]); points.Add(currentPoint); } break; case SVGCommandType.h: case SVGCommandType.H: for (int i = 0; i < command.XYParams.Count(); i++) { currentPoint = new Point(dX + command.XYParams[i], currentPoint.Y); points.Add(currentPoint); } break; case SVGCommandType.v: case SVGCommandType.V: for (int i = 0; i < command.XYParams.Count(); i++) { currentPoint = new Point(currentPoint.X, dX + command.XYParams[i]); points.Add(currentPoint); } break; case SVGCommandType.c: case SVGCommandType.C: keepPValues = true; for (int i = 0; i < command.XYParams.Count(); i += 6) { p0 = currentPoint; p1 = new Point(dX + command.XYParams[i + 0], dY + command.XYParams[i + 1]); p2 = new Point(dX + command.XYParams[i + 2], dY + command.XYParams[i + 3]); p3 = new Point(dX + command.XYParams[i + 4], dY + command.XYParams[i + 5]); currentPoint = p3.Value; points.AddRange(SVGMath.CalculateCubicBeizer(p0.Value, p1.Value, p2.Value, p3.Value, pointsPerBeizerCommand)); } break; case SVGCommandType.s: case SVGCommandType.S: keepPValues = true; for (int i = 0; i < command.XYParams.Count(); i += 4) { p0 = currentPoint; // P1 is calculatd based off of the parameters for the previous curve p1 = !p2.HasValue ? currentPoint : new Point((p3.Value.X * 2) - p2.Value.X, (p3.Value.Y * 2) - p2.Value.Y); p2 = new Point(dX + command.XYParams[i + 0], dY + command.XYParams[i + 1]); p3 = new Point(dX + command.XYParams[i + 2], dY + command.XYParams[i + 3]); currentPoint = p3.Value; points.AddRange(SVGMath.CalculateCubicBeizer(p0.Value, p1.Value, p2.Value, p3.Value, pointsPerBeizerCommand)); } break; case SVGCommandType.q: case SVGCommandType.Q: keepQValues = true; for (int i = 0; i < command.XYParams.Count(); i += 4) { q0 = currentPoint; q1 = new Point(dX + command.XYParams[i + 0], dY + command.XYParams[i + 1]); q2 = new Point(dX + command.XYParams[i + 2], dY + command.XYParams[i + 3]); currentPoint = q2.Value; points.AddRange(SVGMath.CalculateQuadraticBeizer(q0.Value, q1.Value, q2.Value, pointsPerBeizerCommand)); } break; case SVGCommandType.t: case SVGCommandType.T: keepQValues = true; for (int i = 0; i < command.XYParams.Count(); i += 2) { q0 = currentPoint; q1 = !q2.HasValue ? currentPoint : new Point((q2.Value.X * 2) - q1.Value.X, (q2.Value.Y * 2) - q1.Value.Y); q2 = new Point(dX + command.XYParams[i + 0], dY + command.XYParams[i + 1]); currentPoint = q2.Value; points.AddRange(SVGMath.CalculateQuadraticBeizer(q0.Value, q1.Value, q2.Value, pointsPerBeizerCommand)); } break; case SVGCommandType.z: case SVGCommandType.Z: pathData.IsClosed = true; break; case SVGCommandType.invalid: throw new InvalidOperationException($"Invalid SVG Command {command.SVGCommandString}"); } // Get rid of Q values if there is a non quadratic beizer command run in between quadratic beizer commands if (!keepQValues) { q0 = null; q1 = null; q2 = null; } // Get rid of P values if there is a non cubic beizer command run in between cubic beizer commands if (!keepPValues) { p0 = null; p1 = null; p2 = null; p3 = null; } } pathData.Points = points; return(pathData); }