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