From cbca7beefde6c69bc21be5bf5d0eb8919278a057 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 6 Jun 2018 20:53:27 +0200 Subject: [PATCH] Initial --- .../Media/PathMarkupParser.cs | 805 +++++++++++------- src/Avalonia.Visuals/Media/StreamGeometry.cs | 10 +- .../Media/PathMarkupParserTests.cs | 127 ++- 3 files changed, 610 insertions(+), 332 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PathMarkupParser.cs b/src/Avalonia.Visuals/Media/PathMarkupParser.cs index 9e4a3cbeae..0307701e82 100644 --- a/src/Avalonia.Visuals/Media/PathMarkupParser.cs +++ b/src/Avalonia.Visuals/Media/PathMarkupParser.cs @@ -5,50 +5,46 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Text; +using System.Text.RegularExpressions; namespace Avalonia.Media { /// /// Parses a path markup string. /// - public class PathMarkupParser + public class PathMarkupParser : IDisposable { - private static readonly Dictionary Commands = new Dictionary - { - { 'F', Command.FillRule }, - { 'M', Command.Move }, - { 'L', Command.Line }, - { 'H', Command.HorizontalLine }, - { 'V', Command.VerticalLine }, - { 'Q', Command.QuadraticBezierCurve }, - { 'T', Command.SmoothQuadraticBezierCurve }, - { 'C', Command.CubicBezierCurve }, - { 'S', Command.SmoothCubicBezierCurve }, - { 'A', Command.Arc }, - { 'Z', Command.Close }, - }; - - private static readonly Dictionary FillRules = new Dictionary - { - {'0', FillRule.EvenOdd }, - {'1', FillRule.NonZero } - }; + private static readonly string s_separatorPattern; - private readonly StreamGeometryContext _context; + private Point _currentPoint; + private Point? _previousControlPoint; + private PathGeometry _currentGeometry; + private PathFigure _currentFigure; + private bool _isDisposed; - /// - /// Initializes a new instance of the class. - /// - /// The context for the geometry. - public PathMarkupParser(StreamGeometryContext context) + private static readonly Dictionary s_commands = + new Dictionary + { + { 'F', Command.FillRule }, + { 'M', Command.Move }, + { 'L', Command.Line }, + { 'H', Command.HorizontalLine }, + { 'V', Command.VerticalLine }, + { 'Q', Command.QuadraticBezierCurve }, + { 'T', Command.SmoothQuadraticBezierCurve }, + { 'C', Command.CubicBezierCurve }, + { 'S', Command.SmoothCubicBezierCurve }, + { 'A', Command.Arc }, + { 'Z', Command.Close }, + }; + + static PathMarkupParser() { - _context = context; + s_separatorPattern = CreatesSeparatorPattern(); } - /// - /// Defines the command currently being processed. - /// private enum Command { None, @@ -62,358 +58,581 @@ namespace Avalonia.Media SmoothCubicBezierCurve, SmoothQuadraticBezierCurve, Arc, - Close, + Close } - /// - /// Parses the specified markup string. - /// - /// The markup string. - public void Parse(string s) + public PathGeometry Parse(string s) { - bool openFigure = false; + _currentGeometry = new PathGeometry(); + + var tokens = ParseTokens(s); - using (StringReader reader = new StringReader(s)) + return CreateGeometry(tokens); + } + + void IDisposable.Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (_isDisposed) { - Command command = Command.None; - Point point = new Point(); - bool relative = false; - Point? previousControlPoint = null; + return; + } + + if (disposing) + { + _currentFigure = null; + + _currentGeometry = null; + } + + _isDisposed = true; + } + + private static string CreatesSeparatorPattern() + { + var stringBuilder = new StringBuilder(); + + foreach (var command in s_commands.Keys) + { + stringBuilder.Append(command); + + stringBuilder.Append(char.ToLower(command)); + } + + return @"(?=[" + stringBuilder + "])"; + } + + private static IEnumerable ParseTokens(string s) + { + return Regex.Split(s, s_separatorPattern).Where(t => !string.IsNullOrEmpty(t)).Select(CommandToken.Parse); + } + + private static Point MirrorControlPoint(Point controlPoint, Point center) + { + var dir = controlPoint - center; + + return center + -dir; + } - while (ReadCommand(reader, ref command, ref relative)) + private PathGeometry CreateGeometry(IEnumerable commandTokens) + { + _currentGeometry = new PathGeometry(); + + _currentPoint = new Point(); + + foreach (var commandToken in commandTokens) + { + try { - switch (command) + while (true) { - case Command.FillRule: - _context.SetFillRule(ReadFillRule(reader)); - previousControlPoint = null; - break; - - case Command.Move: - if (openFigure) - { - _context.EndFigure(false); - } - - point = ReadPoint(reader, point, relative); - _context.BeginFigure(point, true); - openFigure = true; - previousControlPoint = null; - break; - - case Command.Line: - point = ReadPoint(reader, point, relative); - _context.LineTo(point); - previousControlPoint = null; - break; - - case Command.HorizontalLine: - if (!relative) - { - point = point.WithX(ReadDouble(reader)); - } - else - { - point = new Point(point.X + ReadDouble(reader), point.Y); - } - - _context.LineTo(point); - previousControlPoint = null; - break; - - case Command.VerticalLine: - if (!relative) - { - point = point.WithY(ReadDouble(reader)); - } - else - { - point = new Point(point.X, point.Y + ReadDouble(reader)); - } - - _context.LineTo(point); - previousControlPoint = null; - break; - - case Command.QuadraticBezierCurve: - { - Point handle = ReadPoint(reader, point, relative); - previousControlPoint = handle; - ReadSeparator(reader); - point = ReadPoint(reader, point, relative); - _context.QuadraticBezierTo(handle, point); + switch (commandToken.Command) + { + case Command.None: + break; + case Command.FillRule: + SetFillRule(commandToken); + break; + case Command.Move: + AddMove(commandToken); + break; + case Command.Line: + AddLine(commandToken); + break; + case Command.HorizontalLine: + AddHorizontalLine(commandToken); + break; + case Command.VerticalLine: + AddVerticalLine(commandToken); + break; + case Command.CubicBezierCurve: + AddCubicBezierCurve(commandToken); + break; + case Command.QuadraticBezierCurve: + AddQuadraticBezierCurve(commandToken); break; - } - - case Command.SmoothQuadraticBezierCurve: - { - Point end = ReadPoint(reader, point, relative); - - if(previousControlPoint != null) - previousControlPoint = MirrorControlPoint((Point)previousControlPoint, point); - - _context.QuadraticBezierTo(previousControlPoint ?? point, end); - point = end; + case Command.SmoothCubicBezierCurve: + AddSmoothCubicBezierCurve(commandToken); break; - } - - case Command.CubicBezierCurve: - { - Point point1 = ReadPoint(reader, point, relative); - ReadSeparator(reader); - Point point2 = ReadPoint(reader, point, relative); - previousControlPoint = point2; - ReadSeparator(reader); - point = ReadPoint(reader, point, relative); - _context.CubicBezierTo(point1, point2, point); + case Command.SmoothQuadraticBezierCurve: + AddSmoothQuadraticBezierCurve(commandToken); break; - } - - case Command.SmoothCubicBezierCurve: - { - Point point2 = ReadPoint(reader, point, relative); - ReadSeparator(reader); - Point end = ReadPoint(reader, point, relative); - - if(previousControlPoint != null) - previousControlPoint = MirrorControlPoint((Point)previousControlPoint, point); - - _context.CubicBezierTo(previousControlPoint ?? point, point2, end); - previousControlPoint = point2; - point = end; + case Command.Arc: + AddArc(commandToken); break; - } - - case Command.Arc: - { - Size size = ReadSize(reader); - ReadSeparator(reader); - double rotationAngle = ReadDouble(reader); - ReadSeparator(reader); - bool isLargeArc = ReadBool(reader); - ReadSeparator(reader); - SweepDirection sweepDirection = ReadBool(reader) ? SweepDirection.Clockwise : SweepDirection.CounterClockwise; - ReadSeparator(reader); - point = ReadPoint(reader, point, relative); - - _context.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection); - previousControlPoint = null; + case Command.Close: + CloseFigure(); break; - } + default: + throw new NotSupportedException("Unsupported command"); + } - case Command.Close: - _context.EndFigure(true); - openFigure = false; - previousControlPoint = null; - break; + if (commandToken.HasImplicitCommands) + { + continue; + } - default: - throw new NotSupportedException("Unsupported command"); + break; } } - - if (openFigure) + catch (InvalidDataException) { - _context.EndFigure(false); + break; + } + catch (NotSupportedException) + { + break; } } + + return _currentGeometry; } - private Point MirrorControlPoint(Point controlPoint, Point center) + private void SetFillRule(CommandToken commandToken) { - Point dir = (controlPoint - center); - return center + -dir; + _currentGeometry.FillRule = commandToken.ReadFillRule(); } - private static bool ReadCommand( - StringReader reader, - ref Command command, - ref bool relative) + private void CloseFigure() { - ReadWhitespace(reader); + if (_currentFigure != null && !_currentFigure.IsClosed) + { + _currentFigure.IsClosed = true; + } - int i = reader.Peek(); + _previousControlPoint = null; - if (i == -1) + _currentFigure = null; + } + + private void CreateFigure() + { + _currentFigure = new PathFigure { - return false; + StartPoint = _currentPoint, + IsClosed = false + }; + + _currentGeometry.Figures.Add(_currentFigure); + } + + private void AddSegment(PathSegment segment) + { + if (_currentFigure == null) + { + CreateFigure(); } - else + + _currentFigure.Segments.Add(segment); + } + + private void AddMove(CommandToken commandToken) + { + var currentPoint = commandToken.ReadPoint(); + + _currentPoint = currentPoint; + + CreateFigure(); + + if (!commandToken.HasImplicitCommands) { - char c = (char)i; - Command next = Command.None; + return; + } - if (!Commands.TryGetValue(char.ToUpperInvariant(c), out next)) + while (commandToken.HasImplicitCommands) + { + AddLine(commandToken); + + if (commandToken.IsRelative) { - if ((char.IsDigit(c) || c == '.' || c == '+' || c == '-') && - (command != Command.None)) - { - return true; - } - else - { - throw new InvalidDataException("Unexpected path command '" + c + "'."); - } + continue; } - command = next; - relative = char.IsLower(c); - reader.Read(); - return true; + _currentPoint = currentPoint; + + CreateFigure(); } } - private static FillRule ReadFillRule(StringReader reader) + private void AddLine(CommandToken commandToken) { - int i = reader.Read(); - if (i == -1) - { - throw new InvalidDataException("Invalid fill rule"); - } - char c = (char)i; - FillRule rule; + _currentPoint = commandToken.IsRelative + ? commandToken.ReadRelativePoint(_currentPoint) + : commandToken.ReadPoint(); - if (!FillRules.TryGetValue(c, out rule)) + var lineSegment = new LineSegment { - throw new InvalidDataException("Invalid fill rule"); - } + Point = _currentPoint + }; - return rule; + AddSegment(lineSegment); } - private static double ReadDouble(StringReader reader) + private void AddHorizontalLine(CommandToken commandToken) { - ReadWhitespace(reader); + _currentPoint = commandToken.IsRelative + ? new Point(_currentPoint.X + commandToken.ReadDouble(), _currentPoint.Y) + : _currentPoint.WithX(commandToken.ReadDouble()); - // TODO: Handle Infinity, NaN and scientific notation. - StringBuilder b = new StringBuilder(); - bool readSign = false; - bool readPoint = false; - bool readExponent = false; - int i; + var lineSegment = new LineSegment + { + Point = _currentPoint + }; - while ((i = reader.Peek()) != -1) + AddSegment(lineSegment); + } + + private void AddVerticalLine(CommandToken commandToken) + { + _currentPoint = commandToken.IsRelative + ? new Point(_currentPoint.X, _currentPoint.Y + commandToken.ReadDouble()) + : _currentPoint.WithY(commandToken.ReadDouble()); + + var lineSegment = new LineSegment { - char c = char.ToUpperInvariant((char)i); + Point = _currentPoint + }; - if (((c == '+' || c == '-') && !readSign) || - (c == '.' && !readPoint) || - (c == 'E' && !readExponent) || - char.IsDigit(c)) - { - if (b.Length != 0 && !readExponent && c == '-') - break; - - b.Append(c); - reader.Read(); + AddSegment(lineSegment); + } - if (!readSign) - { - readSign = c == '+' || c == '-'; - } + private void AddCubicBezierCurve(CommandToken commandToken) + { + var point1 = commandToken.IsRelative + ? commandToken.ReadRelativePoint(_currentPoint) + : commandToken.ReadPoint(); - if (!readPoint) - { - readPoint = c == '.'; - } + var point2 = commandToken.IsRelative + ? commandToken.ReadRelativePoint(_currentPoint) + : commandToken.ReadPoint(); - if (c == 'E') - { - readSign = false; - readExponent = true; - } - } - else - { - break; - } - } + _previousControlPoint = point2; + + var point3 = commandToken.IsRelative + ? commandToken.ReadRelativePoint(_currentPoint) + : commandToken.ReadPoint(); + + var bezierSegment = new BezierSegment + { + Point1 = point1, + Point2 = point2, + Point3 = point3 + }; + + AddSegment(bezierSegment); - return double.Parse(b.ToString(), CultureInfo.InvariantCulture); + _currentPoint = point3; } - private static Point ReadPoint(StringReader reader, Point current, bool relative) + private void AddQuadraticBezierCurve(CommandToken commandToken) { - if (!relative) + var start = commandToken.IsRelative + ? commandToken.ReadRelativePoint(_currentPoint) + : commandToken.ReadPoint(); + + _previousControlPoint = start; + + var end = commandToken.IsRelative + ? commandToken.ReadRelativePoint(_currentPoint) + : commandToken.ReadPoint(); + + var quadraticBezierSegment = new QuadraticBezierSegment { - current = new Point(); - } + Point1 = start, + Point2 = end + }; + + AddSegment(quadraticBezierSegment); - ReadWhitespace(reader); - double x = current.X + ReadDouble(reader); - ReadSeparator(reader); - double y = current.Y + ReadDouble(reader); - return new Point(x, y); + _currentPoint = end; } - private static Size ReadSize(StringReader reader) + private void AddSmoothCubicBezierCurve(CommandToken commandToken) { - ReadWhitespace(reader); - double x = ReadDouble(reader); - ReadSeparator(reader); - double y = ReadDouble(reader); - return new Size(x, y); + var point2 = commandToken.IsRelative + ? commandToken.ReadRelativePoint(_currentPoint) + : commandToken.ReadPoint(); + + var end = commandToken.IsRelative + ? commandToken.ReadRelativePoint(_currentPoint) + : commandToken.ReadPoint(); + + if (_previousControlPoint != null) + { + _previousControlPoint = MirrorControlPoint((Point)_previousControlPoint, _currentPoint); + } + + var bezierSegment = + new BezierSegment { Point1 = _previousControlPoint ?? _currentPoint, Point2 = point2, Point3 = end }; + + AddSegment(bezierSegment); + + _previousControlPoint = point2; + + _currentPoint = end; } - private static bool ReadBool(StringReader reader) + private void AddSmoothQuadraticBezierCurve(CommandToken commandToken) { - return ReadDouble(reader) != 0; + var end = commandToken.IsRelative + ? commandToken.ReadRelativePoint(_currentPoint) + : commandToken.ReadPoint(); + + if (_previousControlPoint != null) + { + _previousControlPoint = MirrorControlPoint((Point)_previousControlPoint, _currentPoint); + } + + var quadraticBezierSegment = new QuadraticBezierSegment + { + Point1 = _previousControlPoint ?? _currentPoint, + Point2 = end + }; + + AddSegment(quadraticBezierSegment); + + _currentPoint = end; } - private static Point ReadRelativePoint(StringReader reader, Point lastPoint) + private void AddArc(CommandToken commandToken) { - ReadWhitespace(reader); - double x = ReadDouble(reader); - ReadSeparator(reader); - double y = ReadDouble(reader); - return new Point(lastPoint.X + x, lastPoint.Y + y); + var size = commandToken.ReadSize(); + + var rotationAngle = commandToken.ReadDouble(); + + var isLargeArc = commandToken.ReadBool(); + + var sweepDirection = commandToken.ReadBool() ? SweepDirection.Clockwise : SweepDirection.CounterClockwise; + + var end = commandToken.IsRelative + ? commandToken.ReadRelativePoint(_currentPoint) + : commandToken.ReadPoint(); + + var arcSegment = new ArcSegment + { + Size = size, + RotationAngle = rotationAngle, + IsLargeArc = isLargeArc, + SweepDirection = sweepDirection, + Point = end + }; + + AddSegment(arcSegment); + + _currentPoint = end; + + _previousControlPoint = null; } - private static void ReadSeparator(StringReader reader) + private class CommandToken { - int i; - bool readComma = false; + private const string ArgumentExpression = @"-?[0-9]*\.?\d+"; - while ((i = reader.Peek()) != -1) + private CommandToken(Command command, bool isRelative, IEnumerable arguments) { - char c = (char)i; + Command = command; + + IsRelative = isRelative; + + Arguments = new List(arguments); + } + + public Command Command { get; } + + public bool IsRelative { get; } - if (char.IsWhiteSpace(c)) + public bool HasImplicitCommands + { + get { - reader.Read(); + if (CurrentPosition == 0 && Arguments.Count > 0) + { + return true; + } + + return CurrentPosition < Arguments.Count - 1; } - else if (c == ',') + } + + private int CurrentPosition { get; set; } + + private List Arguments { get; } + + public static CommandToken Parse(string s) + { + using (var reader = new StringReader(s)) { - if (readComma) + var command = Command.None; + + var isRelative = false; + + if (!ReadCommand(reader, ref command, ref isRelative)) { - throw new InvalidDataException("Unexpected ','."); + throw new InvalidDataException("No path command declared."); } - readComma = true; - reader.Read(); + var commandArguments = reader.ReadToEnd(); + + var argumentMatches = Regex.Matches(commandArguments, ArgumentExpression); + + var arguments = new List(); + + foreach (Match match in argumentMatches) + { + arguments.Add(match.Value); + } + + return new CommandToken(command, isRelative, arguments); } - else + } + + public FillRule ReadFillRule() + { + if (CurrentPosition == Arguments.Count) { - break; + throw new InvalidDataException("Invalid fill rule"); + } + + var value = Arguments[CurrentPosition]; + + CurrentPosition++; + + switch (value) + { + case "0": + { + return FillRule.EvenOdd; + } + + case "1": + { + return FillRule.NonZero; + } + + default: + throw new InvalidDataException("Invalid fill rule"); } } - } - private static void ReadWhitespace(StringReader reader) - { - int i; + public bool ReadBool() + { + if (CurrentPosition == Arguments.Count) + { + throw new InvalidDataException("Invalid boolean value"); + } + + var value = Arguments[CurrentPosition]; - while ((i = reader.Peek()) != -1) + CurrentPosition++; + + switch (value) + { + case "1": + { + return true; + } + + case "0": + { + return false; + } + + default: + throw new InvalidDataException("Invalid boolean value"); + } + } + + public double ReadDouble() { - char c = (char)i; + if (CurrentPosition == Arguments.Count) + { + throw new InvalidDataException("Invalid double value"); + } + + var value = Arguments[CurrentPosition]; + + CurrentPosition++; + + return double.Parse(value, CultureInfo.InvariantCulture); + } - if (char.IsWhiteSpace(c)) + public Size ReadSize() + { + var width = ReadDouble(); + + var height = ReadDouble(); + + return new Size(width, height); + } + + public Point ReadPoint() + { + var x = ReadDouble(); + + var y = ReadDouble(); + + return new Point(x, y); + } + + public Point ReadRelativePoint(Point origin) + { + var x = ReadDouble(); + + var y = ReadDouble(); + + return new Point(origin.X + x, origin.Y + y); + } + + private static bool ReadCommand( + TextReader reader, + ref Command command, + ref bool relative) + { + ReadWhitespace(reader); + + var i = reader.Peek(); + + if (i == -1) { - reader.Read(); + return false; } - else + + var c = (char)i; + + if (!s_commands.TryGetValue(char.ToUpperInvariant(c), out var next)) { - break; + throw new InvalidDataException("Unexpected path command '" + c + "'."); + } + + command = next; + + relative = char.IsLower(c); + + reader.Read(); + + return true; + } + + private static void ReadWhitespace(TextReader reader) + { + int i; + + while ((i = reader.Peek()) != -1) + { + var c = (char)i; + + if (char.IsWhiteSpace(c)) + { + reader.Read(); + } + else + { + break; + } } } } diff --git a/src/Avalonia.Visuals/Media/StreamGeometry.cs b/src/Avalonia.Visuals/Media/StreamGeometry.cs index 9848a649aa..1983740375 100644 --- a/src/Avalonia.Visuals/Media/StreamGeometry.cs +++ b/src/Avalonia.Visuals/Media/StreamGeometry.cs @@ -35,14 +35,10 @@ namespace Avalonia.Media /// A . public static new StreamGeometry Parse(string s) { - StreamGeometry result = new StreamGeometry(); - - using (StreamGeometryContext ctx = result.Open()) + using (var parser = new PathMarkupParser()) { - PathMarkupParser parser = new PathMarkupParser(ctx); - parser.Parse(s); - return result; - } + return parser.Parse(s); + } } /// diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs index e63d23283c..d7eb6129ac 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs @@ -8,53 +8,128 @@ using Xunit; namespace Avalonia.Visuals.UnitTests.Media { + using System.Linq; + public class PathMarkupParserTests { [Fact] public void Parses_Move() { - using (AvaloniaLocator.EnterScope()) - { - var result = new Mock(); - - var parser = PrepareParser(result); + using (var parser = new PathMarkupParser()) + { + var geometry = parser.Parse("M10 10"); - parser.Parse("M10 10"); + var figure = geometry.Figures.First(); - result.Verify(x => x.BeginFigure(new Point(10, 10), true)); + Assert.Equal(new Point(10, 10), figure.StartPoint); } } [Fact] public void Parses_Line() { - using (AvaloniaLocator.EnterScope()) + using (var parser = new PathMarkupParser()) { - var result = new Mock(); + var geometry = parser.Parse("M0 0L10 10"); - var parser = PrepareParser(result); + var figure = geometry.Figures.First(); - parser.Parse("M0 0L10 10"); + var segment = figure.Segments.First(); - result.Verify(x => x.LineTo(new Point(10, 10))); + Assert.IsType(segment); + + var lineSegment = (LineSegment)segment; + + Assert.Equal(new Point(10, 10), lineSegment.Point); } } [Fact] public void Parses_Close() { - using (AvaloniaLocator.EnterScope()) + using (var parser = new PathMarkupParser()) { - var result = new Mock(); + var geometry = parser.Parse("M0 0L10 10z"); + + var figure = geometry.Figures.First(); + + Assert.True(figure.IsClosed); + } + } + + [Fact] + public void Parses_FillMode_Before_Move() + { + using (var parser = new PathMarkupParser()) + { + var geometry = parser.Parse("F 1M0,0"); + + Assert.Equal(FillRule.NonZero, geometry.FillRule); + } + } + + [Theory] + [InlineData("M0 0 10 10 20 20")] + [InlineData("M0,0 10,10 20,20")] + [InlineData("M0,0,10,10,20,20")] + public void Parses_Implicit_Line_Command_After_Move(string pathData) + { + using (var parser = new PathMarkupParser()) + { + var geometry = parser.Parse(pathData); + + var figure = geometry.Figures[0]; + + var segment = figure.Segments[0]; + + Assert.IsType(segment); - var parser = PrepareParser(result); + var lineSegment = (LineSegment)segment; - parser.Parse("M0 0L10 10z"); + Assert.Equal(new Point(10, 10), lineSegment.Point); - result.Verify(x => x.EndFigure(true)); + figure = geometry.Figures[1]; + + segment = figure.Segments[0]; + + Assert.IsType(segment); + + lineSegment = (LineSegment)segment; + + Assert.Equal(new Point(20, 20), lineSegment.Point); } } + [Theory] + [InlineData("m0 0 10 10 20 20")] + [InlineData("m0,0 10,10 20,20")] + [InlineData("m0,0,10,10,20,20")] + public void Parses_Implicit_Line_Command_After_Relative_Move(string pathData) + { + using (var parser = new PathMarkupParser()) + { + var geometry = parser.Parse(pathData); + + var figure = geometry.Figures[0]; + + var segment = figure.Segments[0]; + + Assert.IsType(segment); + + var lineSegment = (LineSegment)segment; + + Assert.Equal(new Point(10, 10), lineSegment.Point); + + segment = figure.Segments[1]; + + Assert.IsType(segment); + + lineSegment = (LineSegment)segment; + + Assert.Equal(new Point(30, 30), lineSegment.Point); + } + } + [Theory] [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")] @@ -75,29 +150,17 @@ namespace Avalonia.Visuals.UnitTests.Media ".3809 36.1563C 18.3809 36.1563 18 38 16.3809 36.9063C 15 36 16.3809 34.9063 16.3809 34.9063C 16.3809 34" + ".9063 10.1309 30.9062 16.6309 19.9063 Z ")] [InlineData( - "F1M16,12C16,14.209 14.209,16 12,16 9.791,16 8,14.209 8,12 8,11.817 8.03,11.644 8.054,11.467L6.585,10 4,10 " + - "4,6.414 2.5,7.914 0,5.414 0,3.586 3.586,0 4.414,0 7.414,3 7.586,3 9,1.586 11.914,4.5 10.414,6 " + + "F1M16,12C16,14.209 14.209,16 12,16 9.791,16 8,14.209 8,12 8,11.817 8.03,11.644 8.054,11.467L6.585,10 4,10 " + + "4,6.414 2.5,7.914 0,5.414 0,3.586 3.586,0 4.414,0 7.414,3 7.586,3 9,1.586 11.914,4.5 10.414,6 " + "12.461,8.046C14.45,8.278,16,9.949,16,12")] public void Should_Parse(string pathData) { - using (AvaloniaLocator.EnterScope()) + using (var parser = new PathMarkupParser()) { - var parser = PrepareParser(); - parser.Parse(pathData); Assert.True(true); } } - - private static PathMarkupParser PrepareParser(Mock implMock = null) - { - AvaloniaLocator.CurrentMutable - .Bind() - .ToConstant(Mock.Of()); - - return new PathMarkupParser( - new StreamGeometryContext(implMock != null ? implMock.Object : Mock.Of())); - } } } \ No newline at end of file