|
@@ -5,9 +5,6 @@ using System;
|
|
|
using System.Collections.Generic;
|
|
using System.Collections.Generic;
|
|
|
using System.Globalization;
|
|
using System.Globalization;
|
|
|
using System.IO;
|
|
using System.IO;
|
|
|
-using System.Linq;
|
|
|
|
|
-using System.Text;
|
|
|
|
|
-using System.Text.RegularExpressions;
|
|
|
|
|
using Avalonia.Platform;
|
|
using Avalonia.Platform;
|
|
|
|
|
|
|
|
namespace Avalonia.Media
|
|
namespace Avalonia.Media
|
|
@@ -17,7 +14,6 @@ namespace Avalonia.Media
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
public class PathMarkupParser : IDisposable
|
|
public class PathMarkupParser : IDisposable
|
|
|
{
|
|
{
|
|
|
- private static readonly string s_separatorPattern;
|
|
|
|
|
private static readonly Dictionary<char, Command> s_commands =
|
|
private static readonly Dictionary<char, Command> s_commands =
|
|
|
new Dictionary<char, Command>
|
|
new Dictionary<char, Command>
|
|
|
{
|
|
{
|
|
@@ -37,14 +33,9 @@ namespace Avalonia.Media
|
|
|
private IGeometryContext _geometryContext;
|
|
private IGeometryContext _geometryContext;
|
|
|
private Point _currentPoint;
|
|
private Point _currentPoint;
|
|
|
private Point? _previousControlPoint;
|
|
private Point? _previousControlPoint;
|
|
|
- private bool? _isOpen;
|
|
|
|
|
|
|
+ private bool _isOpen;
|
|
|
private bool _isDisposed;
|
|
private bool _isDisposed;
|
|
|
|
|
|
|
|
- static PathMarkupParser()
|
|
|
|
|
- {
|
|
|
|
|
- s_separatorPattern = CreatesSeparatorPattern();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Initializes a new instance of the <see cref="PathMarkupParser"/> class.
|
|
/// Initializes a new instance of the <see cref="PathMarkupParser"/> class.
|
|
|
/// </summary>
|
|
/// </summary>
|
|
@@ -76,18 +67,6 @@ namespace Avalonia.Media
|
|
|
Close
|
|
Close
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Parses the specified path data and writes the result to the geometryContext of this instance.
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- /// <param name="pathData">The path data.</param>
|
|
|
|
|
- public void Parse(string pathData)
|
|
|
|
|
- {
|
|
|
|
|
- var normalizedPathData = NormalizeWhiteSpaces(pathData);
|
|
|
|
|
- var tokens = ParseTokens(normalizedPathData);
|
|
|
|
|
-
|
|
|
|
|
- CreateGeometry(tokens);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
void IDisposable.Dispose()
|
|
void IDisposable.Dispose()
|
|
|
{
|
|
{
|
|
|
Dispose(true);
|
|
Dispose(true);
|
|
@@ -108,66 +87,6 @@ namespace Avalonia.Media
|
|
|
_isDisposed = true;
|
|
_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();
|
|
|
|
|
-
|
|
|
|
|
- foreach (var command in s_commands.Keys)
|
|
|
|
|
- {
|
|
|
|
|
- stringBuilder.Append(command);
|
|
|
|
|
-
|
|
|
|
|
- stringBuilder.Append(char.ToLower(command));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return @"(?=[" + stringBuilder + "])";
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private static IEnumerable<CommandToken> ParseTokens(string s)
|
|
|
|
|
- {
|
|
|
|
|
- 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)
|
|
private static Point MirrorControlPoint(Point controlPoint, Point center)
|
|
|
{
|
|
{
|
|
|
var dir = controlPoint - center;
|
|
var dir = controlPoint - center;
|
|
@@ -175,76 +94,78 @@ namespace Avalonia.Media
|
|
|
return center + -dir;
|
|
return center + -dir;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void CreateGeometry(IEnumerable<CommandToken> commandTokens)
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Parses the specified path data and writes the result to the geometryContext of this instance.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ /// <param name="pathData">The path data.</param>
|
|
|
|
|
+ public void Parse(string pathData)
|
|
|
{
|
|
{
|
|
|
|
|
+ var span = pathData.AsSpan();
|
|
|
_currentPoint = new Point();
|
|
_currentPoint = new Point();
|
|
|
|
|
|
|
|
- foreach (var commandToken in commandTokens)
|
|
|
|
|
|
|
+ while(!span.IsEmpty)
|
|
|
{
|
|
{
|
|
|
- try
|
|
|
|
|
- {
|
|
|
|
|
- while (true)
|
|
|
|
|
- {
|
|
|
|
|
- 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.SmoothCubicBezierCurve:
|
|
|
|
|
- AddSmoothCubicBezierCurve(commandToken);
|
|
|
|
|
- break;
|
|
|
|
|
- case Command.SmoothQuadraticBezierCurve:
|
|
|
|
|
- AddSmoothQuadraticBezierCurve(commandToken);
|
|
|
|
|
- break;
|
|
|
|
|
- case Command.Arc:
|
|
|
|
|
- AddArc(commandToken);
|
|
|
|
|
- break;
|
|
|
|
|
- case Command.Close:
|
|
|
|
|
- CloseFigure();
|
|
|
|
|
- break;
|
|
|
|
|
- default:
|
|
|
|
|
- throw new NotSupportedException("Unsupported command");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (commandToken.HasImplicitCommands)
|
|
|
|
|
- {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- catch (InvalidDataException)
|
|
|
|
|
|
|
+ if(!ReadCommand(ref span, out var command, out var relative))
|
|
|
{
|
|
{
|
|
|
- break;
|
|
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
- catch (NotSupportedException)
|
|
|
|
|
|
|
+
|
|
|
|
|
+ bool initialCommand = true;
|
|
|
|
|
+
|
|
|
|
|
+ do
|
|
|
{
|
|
{
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!initialCommand)
|
|
|
|
|
+ {
|
|
|
|
|
+ span = ReadSeparator(span);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ switch (command)
|
|
|
|
|
+ {
|
|
|
|
|
+ case Command.None:
|
|
|
|
|
+ break;
|
|
|
|
|
+ case Command.FillRule:
|
|
|
|
|
+ SetFillRule(ref span);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case Command.Move:
|
|
|
|
|
+ AddMove(ref span, relative);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case Command.Line:
|
|
|
|
|
+ AddLine(ref span, relative);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case Command.HorizontalLine:
|
|
|
|
|
+ AddHorizontalLine(ref span, relative);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case Command.VerticalLine:
|
|
|
|
|
+ AddVerticalLine(ref span, relative);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case Command.CubicBezierCurve:
|
|
|
|
|
+ AddCubicBezierCurve(ref span, relative);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case Command.QuadraticBezierCurve:
|
|
|
|
|
+ AddQuadraticBezierCurve(ref span, relative);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case Command.SmoothCubicBezierCurve:
|
|
|
|
|
+ AddSmoothCubicBezierCurve(ref span, relative);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case Command.SmoothQuadraticBezierCurve:
|
|
|
|
|
+ AddSmoothQuadraticBezierCurve(ref span, relative);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case Command.Arc:
|
|
|
|
|
+ AddArc(ref span, relative);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case Command.Close:
|
|
|
|
|
+ CloseFigure();
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ throw new NotSupportedException("Unsupported command");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ initialCommand = false;
|
|
|
|
|
+ } while (PeekArgument(span));
|
|
|
|
|
+
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (_isOpen != null)
|
|
|
|
|
|
|
+ if (_isOpen)
|
|
|
{
|
|
{
|
|
|
_geometryContext.EndFigure(false);
|
|
_geometryContext.EndFigure(false);
|
|
|
}
|
|
}
|
|
@@ -252,7 +173,7 @@ namespace Avalonia.Media
|
|
|
|
|
|
|
|
private void CreateFigure()
|
|
private void CreateFigure()
|
|
|
{
|
|
{
|
|
|
- if (_isOpen != null)
|
|
|
|
|
|
|
+ if (_isOpen)
|
|
|
{
|
|
{
|
|
|
_geometryContext.EndFigure(false);
|
|
_geometryContext.EndFigure(false);
|
|
|
}
|
|
}
|
|
@@ -262,62 +183,72 @@ namespace Avalonia.Media
|
|
|
_isOpen = true;
|
|
_isOpen = true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void SetFillRule(CommandToken commandToken)
|
|
|
|
|
|
|
+ private void SetFillRule(ref ReadOnlySpan<char> span)
|
|
|
{
|
|
{
|
|
|
- var fillRule = commandToken.ReadFillRule();
|
|
|
|
|
|
|
+ if (!ReadArgument(ref span, out var fillRule) || fillRule.Length != 1)
|
|
|
|
|
+ {
|
|
|
|
|
+ throw new InvalidDataException("Invalid fill rule.");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ FillRule rule;
|
|
|
|
|
|
|
|
- _geometryContext.SetFillRule(fillRule);
|
|
|
|
|
|
|
+ switch (fillRule[0])
|
|
|
|
|
+ {
|
|
|
|
|
+ case '0':
|
|
|
|
|
+ rule = FillRule.EvenOdd;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case '1':
|
|
|
|
|
+ rule = FillRule.NonZero;
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ throw new InvalidDataException("Invalid fill rule");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _geometryContext.SetFillRule(rule);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void CloseFigure()
|
|
private void CloseFigure()
|
|
|
{
|
|
{
|
|
|
- if (_isOpen == true)
|
|
|
|
|
|
|
+ if (_isOpen)
|
|
|
{
|
|
{
|
|
|
_geometryContext.EndFigure(true);
|
|
_geometryContext.EndFigure(true);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
_previousControlPoint = null;
|
|
_previousControlPoint = null;
|
|
|
|
|
|
|
|
- _isOpen = null;
|
|
|
|
|
|
|
+ _isOpen = false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void AddMove(CommandToken commandToken)
|
|
|
|
|
|
|
+ private void AddMove(ref ReadOnlySpan<char> span, bool relative)
|
|
|
{
|
|
{
|
|
|
- var currentPoint = commandToken.IsRelative
|
|
|
|
|
- ? commandToken.ReadRelativePoint(_currentPoint)
|
|
|
|
|
- : commandToken.ReadPoint();
|
|
|
|
|
|
|
+ var currentPoint = relative
|
|
|
|
|
+ ? ReadRelativePoint(ref span, _currentPoint)
|
|
|
|
|
+ : ReadPoint(ref span);
|
|
|
|
|
|
|
|
_currentPoint = currentPoint;
|
|
_currentPoint = currentPoint;
|
|
|
|
|
|
|
|
CreateFigure();
|
|
CreateFigure();
|
|
|
|
|
|
|
|
- if (!commandToken.HasImplicitCommands)
|
|
|
|
|
|
|
+ while (PeekArgument(span))
|
|
|
{
|
|
{
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ span = ReadSeparator(span);
|
|
|
|
|
+ AddLine(ref span, relative);
|
|
|
|
|
|
|
|
- while (commandToken.HasImplicitCommands)
|
|
|
|
|
- {
|
|
|
|
|
- AddLine(commandToken);
|
|
|
|
|
-
|
|
|
|
|
- if (commandToken.IsRelative)
|
|
|
|
|
|
|
+ if (!relative)
|
|
|
{
|
|
{
|
|
|
- continue;
|
|
|
|
|
|
|
+ _currentPoint = currentPoint;
|
|
|
|
|
+ CreateFigure();
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- _currentPoint = currentPoint;
|
|
|
|
|
-
|
|
|
|
|
- CreateFigure();
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void AddLine(CommandToken commandToken)
|
|
|
|
|
|
|
+ private void AddLine(ref ReadOnlySpan<char> span, bool relative)
|
|
|
{
|
|
{
|
|
|
- _currentPoint = commandToken.IsRelative
|
|
|
|
|
- ? commandToken.ReadRelativePoint(_currentPoint)
|
|
|
|
|
- : commandToken.ReadPoint();
|
|
|
|
|
|
|
+ _currentPoint = relative
|
|
|
|
|
+ ? ReadRelativePoint(ref span, _currentPoint)
|
|
|
|
|
+ : ReadPoint(ref span);
|
|
|
|
|
|
|
|
- if (_isOpen == null)
|
|
|
|
|
|
|
+ if (!_isOpen)
|
|
|
{
|
|
{
|
|
|
CreateFigure();
|
|
CreateFigure();
|
|
|
}
|
|
}
|
|
@@ -325,13 +256,13 @@ namespace Avalonia.Media
|
|
|
_geometryContext.LineTo(_currentPoint);
|
|
_geometryContext.LineTo(_currentPoint);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void AddHorizontalLine(CommandToken commandToken)
|
|
|
|
|
|
|
+ private void AddHorizontalLine(ref ReadOnlySpan<char> span, bool relative)
|
|
|
{
|
|
{
|
|
|
- _currentPoint = commandToken.IsRelative
|
|
|
|
|
- ? new Point(_currentPoint.X + commandToken.ReadDouble(), _currentPoint.Y)
|
|
|
|
|
- : _currentPoint.WithX(commandToken.ReadDouble());
|
|
|
|
|
|
|
+ _currentPoint = relative
|
|
|
|
|
+ ? new Point(_currentPoint.X + ReadDouble(ref span), _currentPoint.Y)
|
|
|
|
|
+ : _currentPoint.WithX(ReadDouble(ref span));
|
|
|
|
|
|
|
|
- if (_isOpen == null)
|
|
|
|
|
|
|
+ if (!_isOpen)
|
|
|
{
|
|
{
|
|
|
CreateFigure();
|
|
CreateFigure();
|
|
|
}
|
|
}
|
|
@@ -339,13 +270,13 @@ namespace Avalonia.Media
|
|
|
_geometryContext.LineTo(_currentPoint);
|
|
_geometryContext.LineTo(_currentPoint);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void AddVerticalLine(CommandToken commandToken)
|
|
|
|
|
|
|
+ private void AddVerticalLine(ref ReadOnlySpan<char> span, bool relative)
|
|
|
{
|
|
{
|
|
|
- _currentPoint = commandToken.IsRelative
|
|
|
|
|
- ? new Point(_currentPoint.X, _currentPoint.Y + commandToken.ReadDouble())
|
|
|
|
|
- : _currentPoint.WithY(commandToken.ReadDouble());
|
|
|
|
|
|
|
+ _currentPoint = relative
|
|
|
|
|
+ ? new Point(_currentPoint.X, _currentPoint.Y + ReadDouble(ref span))
|
|
|
|
|
+ : _currentPoint.WithY(ReadDouble(ref span));
|
|
|
|
|
|
|
|
- if (_isOpen == null)
|
|
|
|
|
|
|
+ if (!_isOpen)
|
|
|
{
|
|
{
|
|
|
CreateFigure();
|
|
CreateFigure();
|
|
|
}
|
|
}
|
|
@@ -353,23 +284,27 @@ namespace Avalonia.Media
|
|
|
_geometryContext.LineTo(_currentPoint);
|
|
_geometryContext.LineTo(_currentPoint);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void AddCubicBezierCurve(CommandToken commandToken)
|
|
|
|
|
|
|
+ private void AddCubicBezierCurve(ref ReadOnlySpan<char> span, bool relative)
|
|
|
{
|
|
{
|
|
|
- var point1 = commandToken.IsRelative
|
|
|
|
|
- ? commandToken.ReadRelativePoint(_currentPoint)
|
|
|
|
|
- : commandToken.ReadPoint();
|
|
|
|
|
|
|
+ var point1 = relative
|
|
|
|
|
+ ? ReadRelativePoint(ref span, _currentPoint)
|
|
|
|
|
+ : ReadPoint(ref span);
|
|
|
|
|
|
|
|
- var point2 = commandToken.IsRelative
|
|
|
|
|
- ? commandToken.ReadRelativePoint(_currentPoint)
|
|
|
|
|
- : commandToken.ReadPoint();
|
|
|
|
|
|
|
+ span = ReadSeparator(span);
|
|
|
|
|
+
|
|
|
|
|
+ var point2 = relative
|
|
|
|
|
+ ? ReadRelativePoint(ref span, _currentPoint)
|
|
|
|
|
+ : ReadPoint(ref span);
|
|
|
|
|
|
|
|
_previousControlPoint = point2;
|
|
_previousControlPoint = point2;
|
|
|
|
|
|
|
|
- var point3 = commandToken.IsRelative
|
|
|
|
|
- ? commandToken.ReadRelativePoint(_currentPoint)
|
|
|
|
|
- : commandToken.ReadPoint();
|
|
|
|
|
|
|
+ span = ReadSeparator(span);
|
|
|
|
|
+
|
|
|
|
|
+ var point3 = relative
|
|
|
|
|
+ ? ReadRelativePoint(ref span, _currentPoint)
|
|
|
|
|
+ : ReadPoint(ref span);
|
|
|
|
|
|
|
|
- if (_isOpen == null)
|
|
|
|
|
|
|
+ if (!_isOpen)
|
|
|
{
|
|
{
|
|
|
CreateFigure();
|
|
CreateFigure();
|
|
|
}
|
|
}
|
|
@@ -379,19 +314,21 @@ namespace Avalonia.Media
|
|
|
_currentPoint = point3;
|
|
_currentPoint = point3;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void AddQuadraticBezierCurve(CommandToken commandToken)
|
|
|
|
|
|
|
+ private void AddQuadraticBezierCurve(ref ReadOnlySpan<char> span, bool relative)
|
|
|
{
|
|
{
|
|
|
- var start = commandToken.IsRelative
|
|
|
|
|
- ? commandToken.ReadRelativePoint(_currentPoint)
|
|
|
|
|
- : commandToken.ReadPoint();
|
|
|
|
|
|
|
+ var start = relative
|
|
|
|
|
+ ? ReadRelativePoint(ref span, _currentPoint)
|
|
|
|
|
+ : ReadPoint(ref span);
|
|
|
|
|
|
|
|
_previousControlPoint = start;
|
|
_previousControlPoint = start;
|
|
|
|
|
|
|
|
- var end = commandToken.IsRelative
|
|
|
|
|
- ? commandToken.ReadRelativePoint(_currentPoint)
|
|
|
|
|
- : commandToken.ReadPoint();
|
|
|
|
|
|
|
+ span = ReadSeparator(span);
|
|
|
|
|
+
|
|
|
|
|
+ var end = relative
|
|
|
|
|
+ ? ReadRelativePoint(ref span, _currentPoint)
|
|
|
|
|
+ : ReadPoint(ref span);
|
|
|
|
|
|
|
|
- if (_isOpen == null)
|
|
|
|
|
|
|
+ if (!_isOpen)
|
|
|
{
|
|
{
|
|
|
CreateFigure();
|
|
CreateFigure();
|
|
|
}
|
|
}
|
|
@@ -401,22 +338,24 @@ namespace Avalonia.Media
|
|
|
_currentPoint = end;
|
|
_currentPoint = end;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void AddSmoothCubicBezierCurve(CommandToken commandToken)
|
|
|
|
|
|
|
+ private void AddSmoothCubicBezierCurve(ref ReadOnlySpan<char> span, bool relative)
|
|
|
{
|
|
{
|
|
|
- var point2 = commandToken.IsRelative
|
|
|
|
|
- ? commandToken.ReadRelativePoint(_currentPoint)
|
|
|
|
|
- : commandToken.ReadPoint();
|
|
|
|
|
|
|
+ var point2 = relative
|
|
|
|
|
+ ? ReadRelativePoint(ref span, _currentPoint)
|
|
|
|
|
+ : ReadPoint(ref span);
|
|
|
|
|
|
|
|
- var end = commandToken.IsRelative
|
|
|
|
|
- ? commandToken.ReadRelativePoint(_currentPoint)
|
|
|
|
|
- : commandToken.ReadPoint();
|
|
|
|
|
|
|
+ span = ReadSeparator(span);
|
|
|
|
|
+
|
|
|
|
|
+ var end = relative
|
|
|
|
|
+ ? ReadRelativePoint(ref span, _currentPoint)
|
|
|
|
|
+ : ReadPoint(ref span);
|
|
|
|
|
|
|
|
if (_previousControlPoint != null)
|
|
if (_previousControlPoint != null)
|
|
|
{
|
|
{
|
|
|
_previousControlPoint = MirrorControlPoint((Point)_previousControlPoint, _currentPoint);
|
|
_previousControlPoint = MirrorControlPoint((Point)_previousControlPoint, _currentPoint);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (_isOpen == null)
|
|
|
|
|
|
|
+ if (!_isOpen)
|
|
|
{
|
|
{
|
|
|
CreateFigure();
|
|
CreateFigure();
|
|
|
}
|
|
}
|
|
@@ -428,18 +367,18 @@ namespace Avalonia.Media
|
|
|
_currentPoint = end;
|
|
_currentPoint = end;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void AddSmoothQuadraticBezierCurve(CommandToken commandToken)
|
|
|
|
|
|
|
+ private void AddSmoothQuadraticBezierCurve(ref ReadOnlySpan<char> span, bool relative)
|
|
|
{
|
|
{
|
|
|
- var end = commandToken.IsRelative
|
|
|
|
|
- ? commandToken.ReadRelativePoint(_currentPoint)
|
|
|
|
|
- : commandToken.ReadPoint();
|
|
|
|
|
|
|
+ var end = relative
|
|
|
|
|
+ ? ReadRelativePoint(ref span, _currentPoint)
|
|
|
|
|
+ : ReadPoint(ref span);
|
|
|
|
|
|
|
|
if (_previousControlPoint != null)
|
|
if (_previousControlPoint != null)
|
|
|
{
|
|
{
|
|
|
_previousControlPoint = MirrorControlPoint((Point)_previousControlPoint, _currentPoint);
|
|
_previousControlPoint = MirrorControlPoint((Point)_previousControlPoint, _currentPoint);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (_isOpen == null)
|
|
|
|
|
|
|
+ if (!_isOpen)
|
|
|
{
|
|
{
|
|
|
CreateFigure();
|
|
CreateFigure();
|
|
|
}
|
|
}
|
|
@@ -449,21 +388,27 @@ namespace Avalonia.Media
|
|
|
_currentPoint = end;
|
|
_currentPoint = end;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void AddArc(CommandToken commandToken)
|
|
|
|
|
|
|
+ private void AddArc(ref ReadOnlySpan<char> span, bool relative)
|
|
|
{
|
|
{
|
|
|
- var size = commandToken.ReadSize();
|
|
|
|
|
|
|
+ var size = ReadSize(ref span);
|
|
|
|
|
+
|
|
|
|
|
+ span = ReadSeparator(span);
|
|
|
|
|
|
|
|
- var rotationAngle = commandToken.ReadDouble();
|
|
|
|
|
|
|
+ var rotationAngle = ReadDouble(ref span);
|
|
|
|
|
+ span = ReadSeparator(span);
|
|
|
|
|
+ var isLargeArc = ReadBool(ref span);
|
|
|
|
|
|
|
|
- var isLargeArc = commandToken.ReadBool();
|
|
|
|
|
|
|
+ span = ReadSeparator(span);
|
|
|
|
|
|
|
|
- var sweepDirection = commandToken.ReadBool() ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
|
|
|
|
|
|
|
+ var sweepDirection = ReadBool(ref span) ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
|
|
|
|
|
+
|
|
|
|
|
+ span = ReadSeparator(span);
|
|
|
|
|
|
|
|
- var end = commandToken.IsRelative
|
|
|
|
|
- ? commandToken.ReadRelativePoint(_currentPoint)
|
|
|
|
|
- : commandToken.ReadPoint();
|
|
|
|
|
|
|
+ var end = relative
|
|
|
|
|
+ ? ReadRelativePoint(ref span, _currentPoint)
|
|
|
|
|
+ : ReadPoint(ref span);
|
|
|
|
|
|
|
|
- if (_isOpen == null)
|
|
|
|
|
|
|
+ if (!_isOpen)
|
|
|
{
|
|
{
|
|
|
CreateFigure();
|
|
CreateFigure();
|
|
|
}
|
|
}
|
|
@@ -475,210 +420,134 @@ namespace Avalonia.Media
|
|
|
_previousControlPoint = null;
|
|
_previousControlPoint = null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private class CommandToken
|
|
|
|
|
|
|
+ private static bool PeekArgument(ReadOnlySpan<char> span)
|
|
|
{
|
|
{
|
|
|
- private const string ArgumentExpression = @"-?[0-9]*\.?\d+";
|
|
|
|
|
-
|
|
|
|
|
- private CommandToken(Command command, bool isRelative, IEnumerable<string> arguments)
|
|
|
|
|
- {
|
|
|
|
|
- Command = command;
|
|
|
|
|
-
|
|
|
|
|
- IsRelative = isRelative;
|
|
|
|
|
-
|
|
|
|
|
- Arguments = new List<string>(arguments);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public Command Command { get; }
|
|
|
|
|
|
|
+ span = SkipWhitespace(span);
|
|
|
|
|
|
|
|
- public bool IsRelative { get; }
|
|
|
|
|
|
|
+ return !span.IsEmpty && (span[0] == ',' || span[0] == '-' || char.IsDigit(span[0]));
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- public bool HasImplicitCommands
|
|
|
|
|
|
|
+ private static bool ReadArgument(ref ReadOnlySpan<char> remaining, out ReadOnlySpan<char> argument)
|
|
|
|
|
+ {
|
|
|
|
|
+ remaining = SkipWhitespace(remaining);
|
|
|
|
|
+ if (remaining.IsEmpty)
|
|
|
{
|
|
{
|
|
|
- get
|
|
|
|
|
- {
|
|
|
|
|
- if (CurrentPosition == 0 && Arguments.Count > 0)
|
|
|
|
|
- {
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return CurrentPosition < Arguments.Count - 1;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ argument = ReadOnlySpan<char>.Empty;
|
|
|
|
|
+ return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private int CurrentPosition { get; set; }
|
|
|
|
|
-
|
|
|
|
|
- private List<string> Arguments { get; }
|
|
|
|
|
-
|
|
|
|
|
- public static CommandToken Parse(string s)
|
|
|
|
|
- {
|
|
|
|
|
- using (var reader = new StringReader(s))
|
|
|
|
|
- {
|
|
|
|
|
- var command = Command.None;
|
|
|
|
|
-
|
|
|
|
|
- var isRelative = false;
|
|
|
|
|
-
|
|
|
|
|
- if (!ReadCommand(reader, ref command, ref isRelative))
|
|
|
|
|
- {
|
|
|
|
|
- throw new InvalidDataException("No path command declared.");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- var commandArguments = reader.ReadToEnd();
|
|
|
|
|
-
|
|
|
|
|
- var argumentMatches = Regex.Matches(commandArguments, ArgumentExpression);
|
|
|
|
|
-
|
|
|
|
|
- var arguments = new List<string>();
|
|
|
|
|
-
|
|
|
|
|
- foreach (Match match in argumentMatches)
|
|
|
|
|
- {
|
|
|
|
|
- arguments.Add(match.Value);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return new CommandToken(command, isRelative, arguments);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public FillRule ReadFillRule()
|
|
|
|
|
|
|
+ var valid = false;
|
|
|
|
|
+ int i = 0;
|
|
|
|
|
+ if (remaining[i] == '-')
|
|
|
{
|
|
{
|
|
|
- if (CurrentPosition == Arguments.Count)
|
|
|
|
|
- {
|
|
|
|
|
- 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");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ i++;
|
|
|
}
|
|
}
|
|
|
|
|
+ for (; i < remaining.Length && char.IsNumber(remaining[i]); i++) valid = true;
|
|
|
|
|
|
|
|
- public bool ReadBool()
|
|
|
|
|
|
|
+ if (i < remaining.Length && remaining[i] == '.')
|
|
|
{
|
|
{
|
|
|
- if (CurrentPosition == Arguments.Count)
|
|
|
|
|
- {
|
|
|
|
|
- throw new InvalidDataException("Invalid boolean value");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- var value = Arguments[CurrentPosition];
|
|
|
|
|
-
|
|
|
|
|
- CurrentPosition++;
|
|
|
|
|
-
|
|
|
|
|
- switch (value)
|
|
|
|
|
- {
|
|
|
|
|
- case "1":
|
|
|
|
|
- {
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- case "0":
|
|
|
|
|
- {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- default:
|
|
|
|
|
- throw new InvalidDataException("Invalid boolean value");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ valid = false;
|
|
|
|
|
+ i++;
|
|
|
}
|
|
}
|
|
|
|
|
+ for (; i < remaining.Length && char.IsNumber(remaining[i]); i++) valid = true;
|
|
|
|
|
|
|
|
- public double ReadDouble()
|
|
|
|
|
|
|
+ if (!valid)
|
|
|
{
|
|
{
|
|
|
- if (CurrentPosition == Arguments.Count)
|
|
|
|
|
- {
|
|
|
|
|
- throw new InvalidDataException("Invalid double value");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- var value = Arguments[CurrentPosition];
|
|
|
|
|
-
|
|
|
|
|
- CurrentPosition++;
|
|
|
|
|
-
|
|
|
|
|
- return double.Parse(value, CultureInfo.InvariantCulture);
|
|
|
|
|
|
|
+ argument = ReadOnlySpan<char>.Empty;
|
|
|
|
|
+ return false;
|
|
|
}
|
|
}
|
|
|
|
|
+ argument = remaining.Slice(0, i);
|
|
|
|
|
+ remaining = remaining.Slice(i);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- public Size ReadSize()
|
|
|
|
|
- {
|
|
|
|
|
- var width = ReadDouble();
|
|
|
|
|
-
|
|
|
|
|
- var height = ReadDouble();
|
|
|
|
|
-
|
|
|
|
|
- return new Size(width, height);
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- public Point ReadPoint()
|
|
|
|
|
|
|
+ private static ReadOnlySpan<char> ReadSeparator(ReadOnlySpan<char> span)
|
|
|
|
|
+ {
|
|
|
|
|
+ span = SkipWhitespace(span);
|
|
|
|
|
+ if (!span.IsEmpty && span[0] == ',')
|
|
|
{
|
|
{
|
|
|
- var x = ReadDouble();
|
|
|
|
|
-
|
|
|
|
|
- var y = ReadDouble();
|
|
|
|
|
-
|
|
|
|
|
- return new Point(x, y);
|
|
|
|
|
|
|
+ span = span.Slice(1);
|
|
|
}
|
|
}
|
|
|
|
|
+ return span;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- public Point ReadRelativePoint(Point origin)
|
|
|
|
|
- {
|
|
|
|
|
- var x = ReadDouble();
|
|
|
|
|
-
|
|
|
|
|
- var y = ReadDouble();
|
|
|
|
|
-
|
|
|
|
|
- return new Point(origin.X + x, origin.Y + y);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ private static ReadOnlySpan<char> SkipWhitespace(ReadOnlySpan<char> span)
|
|
|
|
|
+ {
|
|
|
|
|
+ int i = 0;
|
|
|
|
|
+ for (; i < span.Length && char.IsWhiteSpace(span[i]); i++) ;
|
|
|
|
|
+ return span.Slice(i);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- private static bool ReadCommand(TextReader reader, ref Command command, ref bool relative)
|
|
|
|
|
|
|
+ private bool ReadBool(ref ReadOnlySpan<char> span)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!ReadArgument(ref span, out var boolValue) || boolValue.Length != 1)
|
|
|
{
|
|
{
|
|
|
- ReadWhitespace(reader);
|
|
|
|
|
-
|
|
|
|
|
- var i = reader.Peek();
|
|
|
|
|
-
|
|
|
|
|
- if (i == -1)
|
|
|
|
|
- {
|
|
|
|
|
|
|
+ throw new InvalidDataException("Invalid bool rule.");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ switch (boolValue[0])
|
|
|
|
|
+ {
|
|
|
|
|
+ case '0':
|
|
|
return false;
|
|
return false;
|
|
|
- }
|
|
|
|
|
|
|
+ case '1':
|
|
|
|
|
+ return true;
|
|
|
|
|
+ default:
|
|
|
|
|
+ throw new InvalidDataException("Invalid bool rule");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- var c = (char)i;
|
|
|
|
|
|
|
+ private double ReadDouble(ref ReadOnlySpan<char> span)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!ReadArgument(ref span, out var doubleValue))
|
|
|
|
|
+ {
|
|
|
|
|
+ throw new InvalidDataException("Invalid double value");
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if (!s_commands.TryGetValue(char.ToUpperInvariant(c), out var next))
|
|
|
|
|
- {
|
|
|
|
|
- throw new InvalidDataException("Unexpected path command '" + c + "'.");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return double.Parse(doubleValue.ToString(), CultureInfo.InvariantCulture);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- command = next;
|
|
|
|
|
|
|
+ private Size ReadSize(ref ReadOnlySpan<char> span)
|
|
|
|
|
+ {
|
|
|
|
|
+ var width = ReadDouble(ref span);
|
|
|
|
|
+ span = ReadSeparator(span);
|
|
|
|
|
+ var height = ReadDouble(ref span);
|
|
|
|
|
+ return new Size(width, height);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- relative = char.IsLower(c);
|
|
|
|
|
|
|
+ private Point ReadPoint(ref ReadOnlySpan<char> span)
|
|
|
|
|
+ {
|
|
|
|
|
+ var x = ReadDouble(ref span);
|
|
|
|
|
+ span = ReadSeparator(span);
|
|
|
|
|
+ var y = ReadDouble(ref span);
|
|
|
|
|
+ return new Point(x, y);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- reader.Read();
|
|
|
|
|
|
|
+ private Point ReadRelativePoint(ref ReadOnlySpan<char> span, Point origin)
|
|
|
|
|
+ {
|
|
|
|
|
+ var x = ReadDouble(ref span);
|
|
|
|
|
+ span = ReadSeparator(span);
|
|
|
|
|
+ var y = ReadDouble(ref span);
|
|
|
|
|
+ return new Point(origin.X + x, origin.Y + y);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- return true;
|
|
|
|
|
|
|
+ private bool ReadCommand(ref ReadOnlySpan<char> span, out Command command, out bool relative)
|
|
|
|
|
+ {
|
|
|
|
|
+ span = SkipWhitespace(span);
|
|
|
|
|
+ if (span.IsEmpty)
|
|
|
|
|
+ {
|
|
|
|
|
+ command = default;
|
|
|
|
|
+ relative = false;
|
|
|
|
|
+ return false;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- private static void ReadWhitespace(TextReader reader)
|
|
|
|
|
|
|
+ var c = span[0];
|
|
|
|
|
+ if (!s_commands.TryGetValue(char.ToUpperInvariant(c), out command))
|
|
|
{
|
|
{
|
|
|
- int i;
|
|
|
|
|
-
|
|
|
|
|
- while ((i = reader.Peek()) != -1)
|
|
|
|
|
- {
|
|
|
|
|
- var c = (char)i;
|
|
|
|
|
-
|
|
|
|
|
- if (char.IsWhiteSpace(c))
|
|
|
|
|
- {
|
|
|
|
|
- reader.Read();
|
|
|
|
|
- }
|
|
|
|
|
- else
|
|
|
|
|
- {
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ throw new InvalidDataException("Unexpected path command '" + c + "'.");
|
|
|
}
|
|
}
|
|
|
|
|
+ relative = char.IsLower(c);
|
|
|
|
|
+ span = span.Slice(1);
|
|
|
|
|
+ return true;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|