Exemple #1
0
        /// <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);
        }
Exemple #2
0
        /// <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));
        }
Exemple #3
0
        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));
            }
        }
Exemple #4
0
        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));
        }
Exemple #5
0
 private static bool MarkState(bool hasMode, ref CoordinateParserState state)
 {
     state.HasMore = hasMode;
     ++state.CharsPosition;
     return(hasMode);
 }
Exemple #6
0
        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;
            }
        }