diff --git a/src/Avalonia.Visuals/Media/PathMarkupParser.cs b/src/Avalonia.Visuals/Media/PathMarkupParser.cs index e8824753b5..a322d404bf 100644 --- a/src/Avalonia.Visuals/Media/PathMarkupParser.cs +++ b/src/Avalonia.Visuals/Media/PathMarkupParser.cs @@ -82,7 +82,8 @@ namespace Avalonia.Media /// The path data. public void Parse(string pathData) { - var tokens = ParseTokens(pathData); + var normalizedPathData = NormalizeWhiteSpaces(pathData); + var tokens = ParseTokens(normalizedPathData); CreateGeometry(tokens); } @@ -107,6 +108,45 @@ namespace Avalonia.Media _isDisposed = true; } + private static string NormalizeWhiteSpaces(string s) + { + int length = s.Length, + index = 0, + i = 0; + var source = s.ToCharArray(); + var skip = false; + + for (; i < length; i++) + { + var c = source[i]; + + if (char.IsWhiteSpace(c)) + { + if (skip) + { + continue; + } + + source[index++] = c; + + skip = true; + + continue; + } + + skip = false; + + source[index++] = c; + } + + if (char.IsWhiteSpace(source[index - 1])) + { + index--; + } + + return char.IsWhiteSpace(source[0]) ? new string(source, 1, index) : new string(source, 0, index); + } + private static string CreatesSeparatorPattern() { var stringBuilder = new StringBuilder(); @@ -123,7 +163,9 @@ namespace Avalonia.Media private static IEnumerable ParseTokens(string s) { - return Regex.Split(s, s_separatorPattern).Where(t => !string.IsNullOrEmpty(t)).Select(CommandToken.Parse); + var expressions = Regex.Split(s, s_separatorPattern).Where(t => !string.IsNullOrEmpty(t)); + + return expressions.Select(CommandToken.Parse); } private static Point MirrorControlPoint(Point controlPoint, Point center) @@ -135,7 +177,7 @@ namespace Avalonia.Media private void CreateGeometry(IEnumerable commandTokens) { - _currentPoint = new Point(); + _currentPoint = new Point(); foreach (var commandToken in commandTokens) { @@ -468,7 +510,7 @@ namespace Avalonia.Media private List Arguments { get; } public static CommandToken Parse(string s) - { + { using (var reader = new StringReader(s)) { var command = Command.None; @@ -590,7 +632,7 @@ namespace Avalonia.Media var y = ReadDouble(); return new Point(origin.X + x, origin.Y + y); - } + } private static bool ReadCommand(TextReader reader, ref Command command, ref bool relative) { diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs index c8bea43fb0..35ec38789e 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs @@ -7,6 +7,8 @@ using Xunit; namespace Avalonia.Visuals.UnitTests.Media { + using System.IO; + public class PathMarkupParserTests { [Fact] @@ -140,6 +142,7 @@ namespace Avalonia.Visuals.UnitTests.Media } [Theory] + [InlineData(" M0 0")] [InlineData("F1 M24,14 A2,2,0,1,1,20,14 A2,2,0,1,1,24,14 z")] // issue #1107 [InlineData("M0 0L10 10z")] [InlineData("M50 50 L100 100 L150 50")] @@ -173,5 +176,18 @@ namespace Avalonia.Visuals.UnitTests.Media Assert.True(true); } } + + [Theory] + [InlineData("0 0")] + [InlineData("j")] + public void Throws_InvalidDataException_On_None_Defined_Command(string pathData) + { + var pathGeometry = new PathGeometry(); + using (var context = new PathGeometryContext(pathGeometry)) + using (var parser = new PathMarkupParser(context)) + { + Assert.Throws(() => parser.Parse(pathData)); + } + } } } \ No newline at end of file