Browse Source

Span-ification v2. Remove CommandToken and just operate directly on the single span. Memory allocation is now down to less than 5KB.

pull/1685/head
Jeremy Koritzinsky 8 years ago
parent
commit
85159c069c
  1. 534
      src/Avalonia.Visuals/Media/PathMarkupParser.cs

534
src/Avalonia.Visuals/Media/PathMarkupParser.cs

@ -105,61 +105,64 @@ namespace Avalonia.Media
while(!span.IsEmpty)
{
var commandToken = CommandToken.Parse(ref span);
try
if(!ReadCommand(ref span, out var command, out var relative))
{
do
{
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");
}
} while (commandToken.HasImplicitCommands);
}
catch (InvalidDataException)
{
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)
@ -180,11 +183,28 @@ namespace Avalonia.Media
_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()
@ -199,21 +219,22 @@ namespace Avalonia.Media
_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;
CreateFigure();
while (commandToken.HasImplicitCommands)
while (PeekArgument(span))
{
AddLine(commandToken);
span = ReadSeparator(span);
AddLine(ref span, relative);
if (!commandToken.IsRelative)
if (!relative)
{
_currentPoint = currentPoint;
CreateFigure();
@ -221,11 +242,11 @@ namespace Avalonia.Media
}
}
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)
{
@ -235,11 +256,11 @@ namespace Avalonia.Media
_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)
{
@ -249,11 +270,11 @@ namespace Avalonia.Media
_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)
{
@ -263,21 +284,25 @@ namespace Avalonia.Media
_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;
var point3 = commandToken.IsRelative
? commandToken.ReadRelativePoint(_currentPoint)
: commandToken.ReadPoint();
span = ReadSeparator(span);
var point3 = relative
? ReadRelativePoint(ref span, _currentPoint)
: ReadPoint(ref span);
if (!_isOpen)
{
@ -289,17 +314,19 @@ namespace Avalonia.Media
_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;
var end = commandToken.IsRelative
? commandToken.ReadRelativePoint(_currentPoint)
: commandToken.ReadPoint();
span = ReadSeparator(span);
var end = relative
? ReadRelativePoint(ref span, _currentPoint)
: ReadPoint(ref span);
if (!_isOpen)
{
@ -311,15 +338,17 @@ namespace Avalonia.Media
_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);
span = ReadSeparator(span);
var end = commandToken.IsRelative
? commandToken.ReadRelativePoint(_currentPoint)
: commandToken.ReadPoint();
var end = relative
? ReadRelativePoint(ref span, _currentPoint)
: ReadPoint(ref span);
if (_previousControlPoint != null)
{
@ -338,11 +367,11 @@ namespace Avalonia.Media
_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)
{
@ -359,19 +388,25 @@ namespace Avalonia.Media
_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);
var rotationAngle = commandToken.ReadDouble();
span = ReadSeparator(span);
var isLargeArc = commandToken.ReadBool();
var rotationAngle = ReadDouble(ref span);
span = ReadSeparator(span);
var isLargeArc = ReadBool(ref span);
var sweepDirection = commandToken.ReadBool() ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
span = ReadSeparator(span);
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)
{
@ -385,227 +420,134 @@ namespace Avalonia.Media
_previousControlPoint = null;
}
private class CommandToken
private static bool PeekArgument(ReadOnlySpan<char> span)
{
private CommandToken(Command command, bool isRelative, List<string> arguments)
{
Command = command;
IsRelative = isRelative;
Arguments = 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(ref ReadOnlySpan<char> span)
var valid = false;
int i = 0;
if (remaining[i] == '-')
{
if (!ReadCommand(ref span, out var command, out var isRelative))
{
throw new InvalidDataException("No path command declared.");
}
span = span.Slice(1);
span = SkipWhitespace(span);
var arguments = new List<string>();
while (ReadArgument(ref span, out var argument))
{
arguments.Add(argument.ToString());
span = ReadSeparator(span);
}
return new CommandToken(command, isRelative, arguments);
i++;
}
for (; i < remaining.Length && char.IsNumber(remaining[i]); i++) valid = true;
public FillRule ReadFillRule()
if (i < remaining.Length && 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");
}
valid = false;
i++;
}
for (; i < remaining.Length && char.IsNumber(remaining[i]); i++) valid = true;
public bool ReadBool()
if (!valid)
{
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");
}
argument = ReadOnlySpan<char>.Empty;
return false;
}
argument = remaining.Slice(0, i);
remaining = remaining.Slice(i);
return true;
}
public double ReadDouble()
{
if (CurrentPosition == Arguments.Count)
{
throw new InvalidDataException("Invalid double value");
}
var value = Arguments[CurrentPosition];
CurrentPosition++;
return double.Parse(value, CultureInfo.InvariantCulture);
}
public Size ReadSize()
private static ReadOnlySpan<char> ReadSeparator(ReadOnlySpan<char> span)
{
span = SkipWhitespace(span);
if (!span.IsEmpty && span[0] == ',')
{
var width = ReadDouble();
var height = ReadDouble();
return new Size(width, height);
span = span.Slice(1);
}
return span;
}
public Point ReadPoint()
{
var x = ReadDouble();
var y = ReadDouble();
return new Point(x, 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);
}
public Point ReadRelativePoint(Point origin)
private bool ReadBool(ref ReadOnlySpan<char> span)
{
if (!ReadArgument(ref span, out var boolValue) || boolValue.Length != 1)
{
var x = ReadDouble();
var y = ReadDouble();
return new Point(origin.X + x, origin.Y + y);
throw new InvalidDataException("Invalid bool rule.");
}
private static bool ReadCommand(ref ReadOnlySpan<char> span, out Command command, out bool relative)
switch (boolValue[0])
{
span = SkipWhitespace(span);
if (span.IsEmpty)
{
command = default;
relative = false;
case '0':
return false;
}
var c = span[0];
if (!s_commands.TryGetValue(char.ToUpperInvariant(c), out command))
{
throw new InvalidDataException("Unexpected path command '" + c + "'.");
}
relative = char.IsLower(c);
return true;
case '1':
return true;
default:
throw new InvalidDataException("Invalid bool rule");
}
}
private static bool ReadArgument(ref ReadOnlySpan<char> remaining, out ReadOnlySpan<char> argument)
private double ReadDouble(ref ReadOnlySpan<char> span)
{
if (!ReadArgument(ref span, out var doubleValue))
{
if (remaining.IsEmpty)
{
argument = ReadOnlySpan<char>.Empty;
return false;
}
throw new InvalidDataException("Invalid double value");
}
var valid = false;
int i = 0;
if (remaining[i] == '-')
{
i++;
}
for (; i < remaining.Length && char.IsNumber(remaining[i]); i++) valid = true ;
return double.Parse(doubleValue.ToString(), CultureInfo.InvariantCulture);
}
if (i < remaining.Length && remaining[i] == '.')
{
valid = false;
i++;
}
for (; i < remaining.Length && char.IsNumber(remaining[i]); i++) valid = true ;
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);
}
if (!valid)
{
argument = ReadOnlySpan<char>.Empty;
return false;
}
argument = remaining.Slice(0, i);
remaining = remaining.Slice(i);
return true;
}
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);
}
private static ReadOnlySpan<char> ReadSeparator(ReadOnlySpan<char> span)
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);
}
private bool ReadCommand(ref ReadOnlySpan<char> span, out Command command, out bool relative)
{
span = SkipWhitespace(span);
if (span.IsEmpty)
{
span = SkipWhitespace(span);
if (!span.IsEmpty && span[0] == ',')
{
span = span.Slice(1);
}
return SkipWhitespace(span);
command = default;
relative = false;
return false;
}
private static ReadOnlySpan<char> SkipWhitespace(ReadOnlySpan<char> span)
var c = span[0];
if (!s_commands.TryGetValue(char.ToUpperInvariant(c), out command))
{
int i = 0;
for (; i < span.Length && char.IsWhiteSpace(span[i]); i++) ;
return span.Slice(i);
throw new InvalidDataException("Unexpected path command '" + c + "'.");
}
relative = char.IsLower(c);
span = span.Slice(1);
return true;
}
}
}
Loading…
Cancel
Save