/// <summary> /// Parses the specified string into a collection of path segments. /// </summary> /// <param name="path">A <see cref="string"/> containing path data.</param> public static SvgPathSegmentList Parse(ReadOnlySpan <char> path) { var segments = new SvgPathSegmentList(); try { var pathTrimmed = path.TrimEnd(); var commandStart = 0; var pathLength = pathTrimmed.Length; for (var i = 0; i < pathLength; ++i) { var currentChar = pathTrimmed[i]; if (char.IsLetter(currentChar) && currentChar != 'e' && currentChar != 'E') // e is used in scientific notiation. but not svg path { var start = commandStart; var length = i - commandStart; var command = pathTrimmed.Slice(start, length).Trim(); commandStart = i; if (command.Length > 0) { var commandSetTrimmed = pathTrimmed.Slice(start, length).Trim(); var state = new CoordinateParserState(ref commandSetTrimmed); CreatePathSegment(commandSetTrimmed[0], segments, ref state, ref commandSetTrimmed); } if (pathLength == i + 1) { var commandSetTrimmed = pathTrimmed.Slice(i, 1).Trim(); var state = new CoordinateParserState(ref commandSetTrimmed); CreatePathSegment(commandSetTrimmed[0], segments, ref state, ref commandSetTrimmed); } } else if (pathLength == i + 1) { var start = commandStart; var length = i - commandStart + 1; var command = pathTrimmed.Slice(start, length).Trim(); if (command.Length > 0) { var commandSetTrimmed = pathTrimmed.Slice(start, length).Trim(); var state = new CoordinateParserState(ref commandSetTrimmed); CreatePathSegment(commandSetTrimmed[0], segments, ref state, ref commandSetTrimmed); } } } } catch (Exception exc) { Trace.TraceError("Error parsing path \"{0}\": {1}", path.ToString(), exc.Message); } return(segments); }
/// <summary> /// Converts the given object to the type of this converter, using the specified context and culture information. /// </summary> /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param> /// <param name="culture">The <see cref="T:System.Globalization.CultureInfo"/> to use as the current culture.</param> /// <param name="value">The <see cref="T:System.Object"/> to convert.</param> /// <returns> /// An <see cref="T:System.Object"/> that represents the converted value. /// </returns> /// <exception cref="T:System.NotSupportedException">The conversion cannot be performed. </exception> public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string s) { var coords = s.AsSpan().Trim(); var state = new CoordinateParserState(ref coords); var result = new SvgPointCollection(); while (CoordinateParser.TryGetFloat(out var pointValue, ref coords, ref state)) { result.Add(new SvgUnit(SvgUnitType.User, pointValue)); } return(result); } return(base.ConvertFrom(context, culture, value)); }
public static bool TryGetFloat(out float result, ref ReadOnlySpan <char> chars, ref CoordinateParserState state) { var charsLength = chars.Length; while (state.CharsPosition < charsLength && state.HasMore) { var currentChar = chars[state.CharsPosition]; switch (state.CurrNumState) { case NumState.Separator: if (char.IsNumber(currentChar)) { state.NewNumState = NumState.Integer; } else if (IsCoordSeparator(currentChar)) { state.NewNumState = NumState.Separator; } else { switch (currentChar) { case '.': state.NewNumState = NumState.DecPlace; break; case '+': case '-': state.NewNumState = NumState.Prefix; break; default: state.NewNumState = NumState.Invalid; break; } } break; case NumState.Prefix: if (char.IsNumber(currentChar)) { state.NewNumState = NumState.Integer; } else if (currentChar == '.') { state.NewNumState = NumState.DecPlace; } else { state.NewNumState = NumState.Invalid; } break; case NumState.Integer: if (char.IsNumber(currentChar)) { state.NewNumState = NumState.Integer; } else if (IsCoordSeparator(currentChar)) { state.NewNumState = NumState.Separator; } else { switch (currentChar) { case '.': state.NewNumState = NumState.DecPlace; break; case 'E': case 'e': state.NewNumState = NumState.Exponent; break; case '+': case '-': state.NewNumState = NumState.Prefix; break; default: state.NewNumState = NumState.Invalid; break; } } break; case NumState.DecPlace: if (char.IsNumber(currentChar)) { state.NewNumState = NumState.Fraction; } else if (IsCoordSeparator(currentChar)) { state.NewNumState = NumState.Separator; } else { switch (currentChar) { case 'E': case 'e': state.NewNumState = NumState.Exponent; break; case '+': case '-': state.NewNumState = NumState.Prefix; break; default: state.NewNumState = NumState.Invalid; break; } } break; case NumState.Fraction: if (char.IsNumber(currentChar)) { state.NewNumState = NumState.Fraction; } else if (IsCoordSeparator(currentChar)) { state.NewNumState = NumState.Separator; } else { switch (currentChar) { case '.': state.NewNumState = NumState.DecPlace; break; case 'E': case 'e': state.NewNumState = NumState.Exponent; break; case '+': case '-': state.NewNumState = NumState.Prefix; break; default: state.NewNumState = NumState.Invalid; break; } } break; case NumState.Exponent: if (char.IsNumber(currentChar)) { state.NewNumState = NumState.ExpValue; } else if (IsCoordSeparator(currentChar)) { state.NewNumState = NumState.Invalid; } else { switch (currentChar) { case '+': case '-': state.NewNumState = NumState.ExpPrefix; break; default: state.NewNumState = NumState.Invalid; break; } } break; case NumState.ExpPrefix: if (char.IsNumber(currentChar)) { state.NewNumState = NumState.ExpValue; } else { state.NewNumState = NumState.Invalid; } break; case NumState.ExpValue: if (char.IsNumber(currentChar)) { state.NewNumState = NumState.ExpValue; } else if (IsCoordSeparator(currentChar)) { state.NewNumState = NumState.Separator; } else { switch (currentChar) { case '.': state.NewNumState = NumState.DecPlace; break; case '+': case '-': state.NewNumState = NumState.Prefix; break; default: state.NewNumState = NumState.Invalid; break; } } break; } if (state.CurrNumState != NumState.Separator && state.NewNumState < state.CurrNumState) { #if NETSTANDARD2_1 || NETCORE || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 result = float.Parse(chars.Slice(state.Position, state.CharsPosition - state.Position), NumberStyles.Float, CultureInfo.InvariantCulture); #else result = float.Parse(chars.Slice(state.Position, state.CharsPosition - state.Position).ToString(), NumberStyles.Float, CultureInfo.InvariantCulture); #endif state.Position = state.CharsPosition; state.CurrNumState = state.NewNumState; return(MarkState(true, ref state)); } else if (state.NewNumState != state.CurrNumState && state.CurrNumState == NumState.Separator) { state.Position = state.CharsPosition; } if (state.NewNumState == NumState.Invalid) { result = float.MinValue; return(MarkState(false, ref state)); } state.CurrNumState = state.NewNumState; ++state.CharsPosition; } if (state.CurrNumState == NumState.Separator || !state.HasMore || state.Position >= charsLength) { result = float.MinValue; return(MarkState(false, ref state)); } else { #if NETSTANDARD2_1 || NETCORE || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 result = float.Parse(chars.Slice(state.Position, charsLength - state.Position), NumberStyles.Float, CultureInfo.InvariantCulture); #else result = float.Parse(chars.Slice(state.Position, charsLength - state.Position).ToString(), NumberStyles.Float, CultureInfo.InvariantCulture); #endif state.Position = charsLength; return(MarkState(true, ref state)); } }
public static bool TryGetBool(out bool result, ref ReadOnlySpan <char> chars, ref CoordinateParserState state) { var charsLength = chars.Length; while (state.CharsPosition < charsLength && state.HasMore) { switch (state.CurrNumState) { case NumState.Separator: var currentChar = chars[state.CharsPosition]; if (IsCoordSeparator(currentChar)) { state.NewNumState = NumState.Separator; } else if (currentChar == '0') { result = false; state.NewNumState = NumState.Separator; state.Position = state.CharsPosition + 1; return(MarkState(true, ref state)); } else if (currentChar == '1') { result = true; state.NewNumState = NumState.Separator; state.Position = state.CharsPosition + 1; return(MarkState(true, ref state)); } else { result = false; return(MarkState(false, ref state)); } break; default: result = false; return(MarkState(false, ref state)); } ++state.CharsPosition; } result = false; return(MarkState(false, ref state)); }
private static bool MarkState(bool hasMode, ref CoordinateParserState state) { state.HasMore = hasMode; ++state.CharsPosition; return(hasMode); }
private static void CreatePathSegment(char command, SvgPathSegmentList segments, ref CoordinateParserState state, ref ReadOnlySpan <char> chars) { var isRelative = char.IsLower(command); // http://www.w3.org/TR/SVG11/paths.html#PathDataGeneralInformation switch (command) { case 'M': // moveto case 'm': // relative moveto { if (CoordinateParser.TryGetFloat(out var coords0, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords1, ref chars, ref state)) { segments.Add( new SvgMoveToSegment( isRelative, new PointF(coords0, coords1))); } while (CoordinateParser.TryGetFloat(out coords0, ref chars, ref state) && CoordinateParser.TryGetFloat(out coords1, ref chars, ref state)) { segments.Add( new SvgLineSegment( isRelative, new PointF(coords0, coords1))); } } break; case 'A': // elliptical arc case 'a': // relative elliptical arc { while (CoordinateParser.TryGetFloat(out var coords0, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords1, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords2, ref chars, ref state) && CoordinateParser.TryGetBool(out var size, ref chars, ref state) && CoordinateParser.TryGetBool(out var sweep, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords3, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords4, ref chars, ref state)) { // A|a rx ry x-axis-rotation large-arc-flag sweep-flag x y segments.Add( new SvgArcSegment( coords0, coords1, coords2, size ? SvgArcSize.Large : SvgArcSize.Small, sweep ? SvgArcSweep.Positive : SvgArcSweep.Negative, isRelative, new PointF(coords3, coords4))); } } break; case 'L': // lineto case 'l': // relative lineto { while (CoordinateParser.TryGetFloat(out var coords0, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords1, ref chars, ref state)) { segments.Add( new SvgLineSegment( isRelative, new PointF(coords0, coords1))); } } break; case 'H': // horizontal lineto case 'h': // relative horizontal lineto { while (CoordinateParser.TryGetFloat(out var coords0, ref chars, ref state)) { segments.Add( new SvgLineSegment( isRelative, new PointF(coords0, float.NaN))); } } break; case 'V': // vertical lineto case 'v': // relative vertical lineto { while (CoordinateParser.TryGetFloat(out var coords0, ref chars, ref state)) { segments.Add( new SvgLineSegment( isRelative, new PointF(float.NaN, coords0))); } } break; case 'Q': // quadratic bézier curveto case 'q': // relative quadratic bézier curveto { while (CoordinateParser.TryGetFloat(out var coords0, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords1, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords2, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords3, ref chars, ref state)) { segments.Add( new SvgQuadraticCurveSegment( isRelative, new PointF(coords0, coords1), new PointF(coords2, coords3))); } } break; case 'T': // shorthand/smooth quadratic bézier curveto case 't': // relative shorthand/smooth quadratic bézier curveto { while (CoordinateParser.TryGetFloat(out var coords0, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords1, ref chars, ref state)) { segments.Add( new SvgQuadraticCurveSegment( isRelative, new PointF(coords0, coords1))); } } break; case 'C': // curveto case 'c': // relative curveto { while (CoordinateParser.TryGetFloat(out var coords0, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords1, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords2, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords3, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords4, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords5, ref chars, ref state)) { segments.Add( new SvgCubicCurveSegment( isRelative, new PointF(coords0, coords1), new PointF(coords2, coords3), new PointF(coords4, coords5))); } } break; case 'S': // shorthand/smooth curveto case 's': // relative shorthand/smooth curveto { while (CoordinateParser.TryGetFloat(out var coords0, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords1, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords2, ref chars, ref state) && CoordinateParser.TryGetFloat(out var coords3, ref chars, ref state)) { segments.Add( new SvgCubicCurveSegment( isRelative, new PointF(coords0, coords1), new PointF(coords2, coords3))); } } break; case 'Z': // closepath case 'z': // relative closepath { segments.Add(new SvgClosePathSegment(isRelative)); } break; } }