Browse Source

Merge pull request #692 from AvaloniaUI/relative-path-fixes

Fix relative points in paths.
pull/701/head
Steven Kirk 10 years ago
committed by GitHub
parent
commit
b7704c8c34
  1. 2
      samples/ControlCatalog/Pages/CanvasPage.xaml
  2. 110
      src/Avalonia.SceneGraph/Media/PathMarkupParser.cs
  3. 17
      src/Windows/Avalonia.Direct2D1/Media/StreamGeometryContextImpl.cs
  4. 254
      tests/Avalonia.RenderTests/Shapes/PathTests.cs
  5. BIN
      tests/TestFiles/Cairo/Shapes/Path/Arc_Absolute.expected.png
  6. BIN
      tests/TestFiles/Cairo/Shapes/Path/Arc_Relative.expected.png
  7. BIN
      tests/TestFiles/Cairo/Shapes/Path/CubicBezier_Absolute.expected.png
  8. BIN
      tests/TestFiles/Cairo/Shapes/Path/CubicBezier_Relative.expected.png
  9. BIN
      tests/TestFiles/Cairo/Shapes/Path/HorizontalLine_Absolute.expected.png
  10. BIN
      tests/TestFiles/Cairo/Shapes/Path/HorizontalLine_Relative.expected.png
  11. BIN
      tests/TestFiles/Cairo/Shapes/Path/Line_Absolute.expected.png
  12. BIN
      tests/TestFiles/Cairo/Shapes/Path/VerticalLine_Absolute.expected.png
  13. BIN
      tests/TestFiles/Cairo/Shapes/Path/VerticalLine_Relative.expected.png
  14. BIN
      tests/TestFiles/Direct2D1/Shapes/Path/Arc_Absolute.expected.png
  15. BIN
      tests/TestFiles/Direct2D1/Shapes/Path/Arc_Relative.expected.png
  16. BIN
      tests/TestFiles/Direct2D1/Shapes/Path/CubicBezier_Absolute.expected.png
  17. BIN
      tests/TestFiles/Direct2D1/Shapes/Path/CubicBezier_Relative.expected.png
  18. BIN
      tests/TestFiles/Direct2D1/Shapes/Path/HorizontalLine_Absolute.expected.png
  19. BIN
      tests/TestFiles/Direct2D1/Shapes/Path/HorizontalLine_Relative.expected.png
  20. BIN
      tests/TestFiles/Direct2D1/Shapes/Path/Line_Absolute.expected.png
  21. BIN
      tests/TestFiles/Direct2D1/Shapes/Path/Line_Relative.expected.png
  22. BIN
      tests/TestFiles/Direct2D1/Shapes/Path/VerticalLine_Absolute.expected.png
  23. BIN
      tests/TestFiles/Direct2D1/Shapes/Path/VerticalLine_Relative.expected.png
  24. BIN
      tests/TestFiles/Skia/Shapes/Path/Arc_Absolute.expected.png
  25. BIN
      tests/TestFiles/Skia/Shapes/Path/Arc_Relative.expected.png
  26. BIN
      tests/TestFiles/Skia/Shapes/Path/CubicBezier_Absolute.expected.png
  27. BIN
      tests/TestFiles/Skia/Shapes/Path/CubicBezier_Relative.expected.png
  28. BIN
      tests/TestFiles/Skia/Shapes/Path/HorizontalLine_Absolute.expected.png
  29. BIN
      tests/TestFiles/Skia/Shapes/Path/HorizontalLine_Relative.expected.png
  30. BIN
      tests/TestFiles/Skia/Shapes/Path/Line_Absolute.expected.png
  31. BIN
      tests/TestFiles/Skia/Shapes/Path/Line_Relative.expected.png
  32. BIN
      tests/TestFiles/Skia/Shapes/Path/VerticalLine_Absolute.expected.png
  33. BIN
      tests/TestFiles/Skia/Shapes/Path/VerticalLine_Relative.expected.png

2
samples/ControlCatalog/Pages/CanvasPage.xaml

@ -13,7 +13,7 @@
</LinearGradientBrush>
</Rectangle.OpacityMask> </Rectangle>
<Ellipse Fill="Green" Width="58" Height="58" Canvas.Left="88" Canvas.Top="100"/>
<Path Fill="Orange" Data="M 0,0 c 50,0 50,-50 c 50,0 50,50 h -50 v 50 l -50,-50 Z" Canvas.Left="30" Canvas.Top="250"/>
<Path Fill="Orange" Data="M 0,0 c 0,0 50,0 50,-50 c 0,0 50,0 50,50 h -50 v 50 l -50,-50 Z" Canvas.Left="30" Canvas.Top="250"/>
<Path Fill="OrangeRed" Canvas.Left="180" Canvas.Top="250">
<Path.Data>
<PathGeometry>

110
src/Avalonia.SceneGraph/Media/PathMarkupParser.cs

@ -17,21 +17,13 @@ namespace Avalonia.Media
private static readonly Dictionary<char, Command> Commands = new Dictionary<char, Command>
{
{ 'F', Command.FillRule },
{ 'f', Command.FillRule },
{ 'M', Command.Move },
{ 'm', Command.MoveRelative },
{ 'L', Command.Line },
{ 'l', Command.LineRelative },
{ 'H', Command.HorizontalLine },
{ 'h', Command.HorizontalLineRelative },
{ 'V', Command.VerticalLine },
{ 'v', Command.VerticalLineRelative },
{ 'C', Command.CubicBezierCurve },
{ 'c', Command.CubicBezierCurveRelative },
{ 'A', Command.Arc },
{ 'a', Command.Arc },
{ 'Z', Command.Close },
{ 'z', Command.Close },
};
private static readonly Dictionary<char, FillRule> FillRules = new Dictionary<char, FillRule>
@ -63,18 +55,12 @@ namespace Avalonia.Media
None,
FillRule,
Move,
MoveRelative,
Line,
LineRelative,
HorizontalLine,
HorizontalLineRelative,
VerticalLine,
VerticalLineRelative,
CubicBezierCurve,
CubicBezierCurveRelative,
Arc,
Close,
Eof,
}
/// <summary>
@ -87,11 +73,11 @@ namespace Avalonia.Media
using (StringReader reader = new StringReader(s))
{
Command lastCommand = Command.None;
Command command;
Command command = Command.None;
Point point = new Point();
bool relative = false;
while ((command = ReadCommand(reader, lastCommand)) != Command.Eof)
while (ReadCommand(reader, ref command, ref relative))
{
switch (command)
{
@ -100,72 +86,58 @@ namespace Avalonia.Media
break;
case Command.Move:
case Command.MoveRelative:
if (openFigure)
{
_context.EndFigure(false);
}
point = command == Command.Move ?
ReadPoint(reader) :
ReadRelativePoint(reader, point);
point = ReadPoint(reader, point, relative);
_context.BeginFigure(point, true);
openFigure = true;
break;
case Command.Line:
point = ReadPoint(reader);
_context.LineTo(point);
break;
case Command.LineRelative:
point = ReadRelativePoint(reader, point);
point = ReadPoint(reader, point, relative);
_context.LineTo(point);
break;
case Command.HorizontalLine:
point = point.WithX(ReadDouble(reader));
_context.LineTo(point);
break;
if (!relative)
{
point = point.WithX(ReadDouble(reader));
}
else
{
point = new Point(point.X + ReadDouble(reader), point.Y);
}
case Command.HorizontalLineRelative:
point = new Point(point.X + ReadDouble(reader), point.Y);
_context.LineTo(point);
break;
case Command.VerticalLine:
point = point.WithY(ReadDouble(reader));
_context.LineTo(point);
break;
if (!relative)
{
point = point.WithY(ReadDouble(reader));
}
else
{
point = new Point(point.X, point.Y + ReadDouble(reader));
}
case Command.VerticalLineRelative:
point = new Point(point.X, point.Y + ReadDouble(reader));
_context.LineTo(point);
break;
case Command.CubicBezierCurve:
{
Point point1 = ReadPoint(reader);
Point point2 = ReadPoint(reader);
point = ReadPoint(reader);
Point point1 = ReadPoint(reader, point, relative);
Point point2 = ReadPoint(reader, point, relative);
point = ReadPoint(reader, point, relative);
_context.CubicBezierTo(point1, point2, point);
break;
}
case Command.CubicBezierCurveRelative:
{
Point point1 = ReadRelativePoint(reader, point);
Point point2 = ReadRelativePoint(reader, point);
_context.CubicBezierTo(point, point1, point2);
point = point2;
break;
}
case Command.Arc:
{
//example: A10,10 0 0,0 10,20
//format - size rotationAngle isLargeArcFlag sweepDirectionFlag endPoint
Size size = ReadSize(reader);
ReadSeparator(reader);
double rotationAngle = ReadDouble(reader);
@ -173,7 +145,7 @@ namespace Avalonia.Media
bool isLargeArc = ReadBool(reader);
ReadSeparator(reader);
SweepDirection sweepDirection = ReadBool(reader) ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
point = ReadPoint(reader);
point = ReadPoint(reader, point, relative);
_context.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection);
break;
@ -187,8 +159,6 @@ namespace Avalonia.Media
default:
throw new NotSupportedException("Unsupported command");
}
lastCommand = command;
}
if (openFigure)
@ -198,7 +168,10 @@ namespace Avalonia.Media
}
}
private static Command ReadCommand(StringReader reader, Command lastCommand)
private static bool ReadCommand(
StringReader reader,
ref Command command,
ref bool relative)
{
ReadWhitespace(reader);
@ -206,19 +179,19 @@ namespace Avalonia.Media
if (i == -1)
{
return Command.Eof;
return false;
}
else
{
char c = (char)i;
Command command = Command.None;
Command next = Command.None;
if (!Commands.TryGetValue(c, out command))
if (!Commands.TryGetValue(char.ToUpperInvariant(c), out next))
{
if ((char.IsDigit(c) || c == '.' || c == '+' || c == '-') &&
(lastCommand != Command.None))
(command != Command.None))
{
return lastCommand;
return true;
}
else
{
@ -226,8 +199,10 @@ namespace Avalonia.Media
}
}
command = next;
relative = char.IsLower(c);
reader.Read();
return command;
return true;
}
}
@ -297,12 +272,17 @@ namespace Avalonia.Media
return double.Parse(b.ToString(), CultureInfo.InvariantCulture);
}
private static Point ReadPoint(StringReader reader)
private static Point ReadPoint(StringReader reader, Point current, bool relative)
{
if (!relative)
{
current = new Point();
}
ReadWhitespace(reader);
double x = ReadDouble(reader);
double x = current.X + ReadDouble(reader);
ReadSeparator(reader);
double y = ReadDouble(reader);
double y = current.Y + ReadDouble(reader);
return new Point(x, y);
}

17
src/Windows/Avalonia.Direct2D1/Media/StreamGeometryContextImpl.cs

@ -6,6 +6,8 @@ using Avalonia.Platform;
using SharpDX.Direct2D1;
using SweepDirection = SharpDX.Direct2D1.SweepDirection;
using D2D = SharpDX.Direct2D1;
using Avalonia.Logging;
using System;
namespace Avalonia.Direct2D1.Media
{
@ -76,7 +78,20 @@ namespace Avalonia.Direct2D1.Media
public void Dispose()
{
_sink.Close();
// Put a catch around sink.Close as it may throw if there were an error e.g. parsing a path.
try
{
_sink.Close();
}
catch (Exception ex)
{
Logger.Error(
LogArea.Visual,
this,
"GeometrySink.Close exception: {Exception}",
ex);
}
_sink.Dispose();
}
}

254
tests/Avalonia.RenderTests/Shapes/PathTests.cs

@ -24,6 +24,260 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
{
}
#if AVALONIA_CAIRO
[Fact(Skip = "Broken in Cairo: waiting for Skia")]
#else
[Fact]
#endif
public void Line_Absolute()
{
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new Path
{
Stroke = Brushes.Red,
StrokeThickness = 1,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Data = StreamGeometry.Parse("M 10,190 L 190,10 M0,0M200,200"),
}
};
RenderToFile(target);
CompareImages();
}
#if AVALONIA_CAIRO
[Fact(Skip = "Broken in Cairo: waiting for Skia")]
#else
[Fact]
#endif
public void Line_Relative()
{
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new Path
{
Stroke = Brushes.Red,
StrokeThickness = 1,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Data = StreamGeometry.Parse("M10,190 l190,-190 M0,0M200,200"),
}
};
RenderToFile(target);
CompareImages();
}
#if AVALONIA_CAIRO
[Fact(Skip = "Broken in Cairo: waiting for Skia")]
#else
[Fact]
#endif
public void HorizontalLine_Absolute()
{
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new Path
{
Stroke = Brushes.Red,
StrokeThickness = 1,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Data = StreamGeometry.Parse("M190,100 H10 M0,0M200,200"),
}
};
RenderToFile(target);
CompareImages();
}
#if AVALONIA_CAIRO
[Fact(Skip = "Broken in Cairo: waiting for Skia")]
#else
[Fact]
#endif
public void HorizontalLine_Relative()
{
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new Path
{
Stroke = Brushes.Red,
StrokeThickness = 1,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Data = StreamGeometry.Parse("M190,100 h-180 M0,0M200,200"),
}
};
RenderToFile(target);
CompareImages();
}
#if AVALONIA_CAIRO
[Fact(Skip = "Broken in Cairo: waiting for Skia")]
#else
[Fact]
#endif
public void VerticalLine_Absolute()
{
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new Path
{
Stroke = Brushes.Red,
StrokeThickness = 1,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Data = StreamGeometry.Parse("M100,190 V10 M0,0M200,200"),
}
};
RenderToFile(target);
CompareImages();
}
#if AVALONIA_CAIRO
[Fact(Skip = "Broken in Cairo: waiting for Skia")]
#else
[Fact]
#endif
public void VerticalLine_Relative()
{
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new Path
{
Stroke = Brushes.Red,
StrokeThickness = 1,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Data = StreamGeometry.Parse("M100,190 V-180 M0,0M200,200"),
}
};
RenderToFile(target);
CompareImages();
}
#if AVALONIA_CAIRO
[Fact(Skip = "Broken in Cairo: waiting for Skia")]
#else
[Fact]
#endif
public void CubicBezier_Absolute()
{
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new Path
{
Fill = Brushes.Gray,
Stroke = Brushes.Red,
StrokeThickness = 1,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Data = StreamGeometry.Parse("M190,0 C10,10 190,190 10,190 M0,0M200,200"),
}
};
RenderToFile(target);
CompareImages();
}
#if AVALONIA_CAIRO
[Fact(Skip = "Broken in Cairo: waiting for Skia")]
#else
[Fact]
#endif
public void CubicBezier_Relative()
{
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new Path
{
Fill = Brushes.Gray,
Stroke = Brushes.Red,
StrokeThickness = 1,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Data = StreamGeometry.Parse("M190,0 c-180,10 0,190 -180,190 M0,0M200,200"),
}
};
RenderToFile(target);
CompareImages();
}
#if AVALONIA_CAIRO
[Fact(Skip = "Broken in Cairo: waiting for Skia")]
#else
[Fact]
#endif
public void Arc_Absolute()
{
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new Path
{
Fill = Brushes.Gray,
Stroke = Brushes.Red,
StrokeThickness = 1,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Data = StreamGeometry.Parse("M190,100 A90,90 0 1,0 10,100 M0,0M200,200"),
}
};
RenderToFile(target);
CompareImages();
}
#if AVALONIA_CAIRO
[Fact(Skip = "Broken in Cairo: waiting for Skia")]
#else
[Fact]
#endif
public void Arc_Relative()
{
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new Path
{
Fill = Brushes.Gray,
Stroke = Brushes.Red,
StrokeThickness = 1,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Data = StreamGeometry.Parse("M190,100 a90,90 0 1,0 -180,0 M0,0M200,200"),
}
};
RenderToFile(target);
CompareImages();
}
[Fact]
public void Path_100px_Triangle_Centered()
{

BIN
tests/TestFiles/Cairo/Shapes/Path/Arc_Absolute.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
tests/TestFiles/Cairo/Shapes/Path/Arc_Relative.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
tests/TestFiles/Cairo/Shapes/Path/CubicBezier_Absolute.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
tests/TestFiles/Cairo/Shapes/Path/CubicBezier_Relative.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
tests/TestFiles/Cairo/Shapes/Path/HorizontalLine_Absolute.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

BIN
tests/TestFiles/Cairo/Shapes/Path/HorizontalLine_Relative.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

BIN
tests/TestFiles/Cairo/Shapes/Path/Line_Absolute.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

BIN
tests/TestFiles/Cairo/Shapes/Path/VerticalLine_Absolute.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 B

BIN
tests/TestFiles/Cairo/Shapes/Path/VerticalLine_Relative.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

BIN
tests/TestFiles/Direct2D1/Shapes/Path/Arc_Absolute.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
tests/TestFiles/Direct2D1/Shapes/Path/Arc_Relative.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
tests/TestFiles/Direct2D1/Shapes/Path/CubicBezier_Absolute.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
tests/TestFiles/Direct2D1/Shapes/Path/CubicBezier_Relative.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
tests/TestFiles/Direct2D1/Shapes/Path/HorizontalLine_Absolute.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

BIN
tests/TestFiles/Direct2D1/Shapes/Path/HorizontalLine_Relative.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

BIN
tests/TestFiles/Direct2D1/Shapes/Path/Line_Absolute.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

BIN
tests/TestFiles/Direct2D1/Shapes/Path/Line_Relative.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

BIN
tests/TestFiles/Direct2D1/Shapes/Path/VerticalLine_Absolute.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 B

BIN
tests/TestFiles/Direct2D1/Shapes/Path/VerticalLine_Relative.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

BIN
tests/TestFiles/Skia/Shapes/Path/Arc_Absolute.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
tests/TestFiles/Skia/Shapes/Path/Arc_Relative.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
tests/TestFiles/Skia/Shapes/Path/CubicBezier_Absolute.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
tests/TestFiles/Skia/Shapes/Path/CubicBezier_Relative.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
tests/TestFiles/Skia/Shapes/Path/HorizontalLine_Absolute.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

BIN
tests/TestFiles/Skia/Shapes/Path/HorizontalLine_Relative.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

BIN
tests/TestFiles/Skia/Shapes/Path/Line_Absolute.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

BIN
tests/TestFiles/Skia/Shapes/Path/Line_Relative.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

BIN
tests/TestFiles/Skia/Shapes/Path/VerticalLine_Absolute.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 B

BIN
tests/TestFiles/Skia/Shapes/Path/VerticalLine_Relative.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Loading…
Cancel
Save