Browse Source

Merge branch 'master' into compiled-binding-fixes

Max Katz 2 years ago
parent
commit
4118c8cc82

+ 1 - 1
samples/ControlCatalog/Pages/PointerCanvas.cs

@@ -114,7 +114,7 @@ public class PointerCanvas : Control
 
     private string? _status;
     public static readonly DirectProperty<PointerCanvas, string?> StatusProperty =
-        AvaloniaProperty.RegisterDirect<PointerCanvas, string?>(nameof(DrawOnlyPoints), c => c.Status, (c, v) => c.Status = v,
+        AvaloniaProperty.RegisterDirect<PointerCanvas, string?>(nameof(Status), c => c.Status, (c, v) => c.Status = v,
             defaultBindingMode: Avalonia.Data.BindingMode.TwoWay);
 
     public string? Status

+ 6 - 3
src/Avalonia.Controls/MaskedTextBox.cs

@@ -31,9 +31,6 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<string?> MaskProperty =
              AvaloniaProperty.Register<MaskedTextBox, string?>(nameof(Mask), string.Empty);
 
-        public static new readonly StyledProperty<char> PasswordCharProperty =
-             AvaloniaProperty.Register<MaskedTextBox, char>(nameof(PasswordChar), '\0');
-
         public static readonly StyledProperty<char> PromptCharProperty =
              AvaloniaProperty.Register<MaskedTextBox, char>(nameof(PromptChar), '_');
 
@@ -51,6 +48,12 @@ namespace Avalonia.Controls
 
         private bool _resetOnSpace = true;
 
+        static MaskedTextBox()
+        {
+            PasswordCharProperty
+                .OverrideDefaultValue<MaskedTextBox>('\0');
+        }
+
         public MaskedTextBox() { }
 
         /// <summary>

+ 1 - 1
src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs

@@ -15,7 +15,7 @@ namespace Avalonia.Diagnostics.Controls
                 (o, v) => o.Header = v);
 
         public static readonly DirectProperty<ThicknessEditor, bool> IsPresentProperty =
-            AvaloniaProperty.RegisterDirect<ThicknessEditor, bool>(nameof(Header), o => o.IsPresent,
+            AvaloniaProperty.RegisterDirect<ThicknessEditor, bool>(nameof(IsPresent), o => o.IsPresent,
                 (o, v) => o.IsPresent = v);
 
         public static readonly DirectProperty<ThicknessEditor, double> LeftProperty =

+ 36 - 7
src/Skia/Avalonia.Skia/CombinedGeometryImpl.cs

@@ -8,11 +8,25 @@ namespace Avalonia.Skia
     /// </summary>
     internal class CombinedGeometryImpl : GeometryImpl
     {
-        public CombinedGeometryImpl(GeometryCombineMode combineMode, Geometry g1, Geometry g2)
+        public CombinedGeometryImpl(SKPath? stroke, SKPath? fill)
         {
-            var path1 = (g1.PlatformImpl as GeometryImpl)?.EffectivePath;
-            var path2 = (g2.PlatformImpl as GeometryImpl)?.EffectivePath;
+            StrokePath = stroke;
+            FillPath = fill;
+            Bounds = (stroke ?? fill)?.TightBounds.ToAvaloniaRect() ?? default;
+        }
+
+        public static CombinedGeometryImpl ForceCreate(GeometryCombineMode combineMode, Geometry g1, Geometry g2)
+        {
+            if (g1.PlatformImpl is GeometryImpl i1
+                && g2.PlatformImpl is GeometryImpl i2
+                && TryCreate(combineMode, i1, i2) is { } result)
+                return result;
+            
+            return new(null, null);
+        }
 
+        public static CombinedGeometryImpl? TryCreate(GeometryCombineMode combineMode, GeometryImpl g1, GeometryImpl g2)
+        {
             var op = combineMode switch
             {
                 GeometryCombineMode.Intersect => SKPathOp.Intersect,
@@ -21,13 +35,28 @@ namespace Avalonia.Skia
                 _ => SKPathOp.Union
             };
 
-            var path = path1?.Op(path2, op);
+            var stroke =
+                g1.StrokePath != null && g2.StrokePath != null
+                    ? g1.StrokePath.Op(g2.StrokePath, op)
+                    : null;
+
+            SKPath? fill = null;
+            if (g1.FillPath != null && g2.FillPath != null)
+            {
+                // Reuse stroke if fill paths are the same
+                if (ReferenceEquals(g1.FillPath, g1.StrokePath) && ReferenceEquals(g2.FillPath, g2.StrokePath))
+                    fill = stroke;
+                else
+                    fill = g1.FillPath.Op(g2.FillPath, op);
+            }
 
-            EffectivePath = path;
-            Bounds = path?.Bounds.ToAvaloniaRect() ?? default;
+            if (stroke == null && fill == null)
+                return null;
+            return new CombinedGeometryImpl(stroke, fill);
         }
 
         public override Rect Bounds { get; }
-        public override SKPath? EffectivePath { get; }
+        public override SKPath? StrokePath { get; }
+        public override SKPath? FillPath { get; }
     }
 }

+ 5 - 4
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@@ -230,20 +230,21 @@ namespace Avalonia.Skia
             var impl = (GeometryImpl) geometry;
             var size = geometry.Bounds.Size;
 
-            if (brush is not null)
+            if (brush is not null && impl.FillPath != null)
             {
                 using (var fill = CreatePaint(_fillPaint, brush, size))
                 {
-                    Canvas.DrawPath(impl.EffectivePath, fill.Paint);
+                    Canvas.DrawPath(impl.FillPath, fill.Paint);
                 }
             }
 
             if (pen is not null
+                && impl.StrokePath != null
                 && TryCreatePaint(_strokePaint, pen, size.Inflate(new Thickness(pen.Thickness / 2))) is { } stroke)
             {
                 using (stroke)
                 {
-                    Canvas.DrawPath(impl.EffectivePath, stroke.Paint);
+                    Canvas.DrawPath(impl.StrokePath, stroke.Paint);
                 }
             }
         }
@@ -639,7 +640,7 @@ namespace Avalonia.Skia
         {
             CheckLease();
             Canvas.Save();
-            Canvas.ClipPath(((GeometryImpl)clip).EffectivePath, SKClipOperation.Intersect, true);
+            Canvas.ClipPath(((GeometryImpl)clip).FillPath, SKClipOperation.Intersect, true);
         }
 
         /// <inheritdoc />

+ 3 - 2
src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs

@@ -8,14 +8,15 @@ namespace Avalonia.Skia
     internal class EllipseGeometryImpl : GeometryImpl
     {
         public override Rect Bounds { get; }
-        public override SKPath EffectivePath { get; }
+        public override SKPath StrokePath { get; }
+        public override SKPath FillPath => StrokePath;
 
         public EllipseGeometryImpl(Rect rect)
         {
             var path = new SKPath();
             path.AddOval(rect.ToSKRect());
 
-            EffectivePath = path;
+            StrokePath = path;
             Bounds = rect;
         }
     }

+ 34 - 9
src/Skia/Avalonia.Skia/GeometryGroupImpl.cs

@@ -11,26 +11,51 @@ namespace Avalonia.Skia
     {
         public GeometryGroupImpl(FillRule fillRule, IReadOnlyList<Geometry> children)
         {
-            var path = new SKPath
+            var fillType = fillRule == FillRule.NonZero ? SKPathFillType.Winding : SKPathFillType.EvenOdd;
+            var count = children.Count;
+            
+            var stroke = new SKPath
             {
-                FillType = fillRule == FillRule.NonZero ? SKPathFillType.Winding : SKPathFillType.EvenOdd,
+                FillType = fillType
             };
-
-            var count = children.Count;
             
+            bool requiresFillPass = false;
             for (var i = 0; i < count; ++i)
             {
-                if (children[i].PlatformImpl is GeometryImpl { EffectivePath: { } effectivePath })
+                if (children[i].PlatformImpl is GeometryImpl geo)
                 {
-                    path.AddPath(effectivePath);
+                    if (geo.StrokePath != null)
+                        stroke.AddPath(geo.StrokePath);
+                    if (!ReferenceEquals(geo.StrokePath, geo.FillPath))
+                        requiresFillPass = true;
                 }
             }
+            
+            StrokePath = stroke;
+            
+            if (requiresFillPass)
+            {
+                var fill = new SKPath
+                {
+                    FillType = fillType
+                };
+
+                for (var i = 0; i < count; ++i)
+                {
+                    if (children[i].PlatformImpl is GeometryImpl { FillPath: { } fillPath })
+                        fill.AddPath(fillPath);
+                }
+
+                FillPath = fill;
+            }
+            else
+                FillPath = stroke;
 
-            EffectivePath = path;
-            Bounds = path.Bounds.ToAvaloniaRect();
+            Bounds = stroke.TightBounds.ToAvaloniaRect();
         }
 
         public override Rect Bounds { get; }
-        public override SKPath EffectivePath { get; }
+        public override SKPath StrokePath { get; }
+        public override SKPath FillPath { get; }
     }
 }

+ 14 - 17
src/Skia/Avalonia.Skia/GeometryImpl.cs

@@ -14,7 +14,7 @@ namespace Avalonia.Skia
         private PathCache _pathCache;
         private SKPathMeasure? _cachedPathMeasure;
 
-        private SKPathMeasure CachedPathMeasure => _cachedPathMeasure ??= new SKPathMeasure(EffectivePath!);
+        private SKPathMeasure CachedPathMeasure => _cachedPathMeasure ??= new SKPathMeasure(StrokePath!);
 
         /// <inheritdoc />
         public abstract Rect Bounds { get; }
@@ -24,19 +24,20 @@ namespace Avalonia.Skia
         {
             get
             {
-                if (EffectivePath is null)
+                if (StrokePath is null)
                     return 0;
 
                 return CachedPathMeasure.Length;
             }
         }
 
-        public abstract SKPath? EffectivePath { get; }
+        public abstract SKPath? StrokePath { get; }
+        public abstract SKPath? FillPath { get; }
 
         /// <inheritdoc />
         public bool FillContains(Point point)
         {
-            return PathContainsCore(EffectivePath, point);
+            return PathContainsCore(FillPath, point);
         }
 
         /// <inheritdoc />
@@ -74,7 +75,7 @@ namespace Avalonia.Skia
                 var paint = SKPaintCache.Shared.Get();
                 paint.IsStroke = true;
                 paint.StrokeWidth = strokeWidth;
-                paint.GetFillPath(EffectivePath, strokePath);
+                paint.GetFillPath(StrokePath, strokePath);
 
                 SKPaintCache.Shared.ReturnReset(paint);
 
@@ -96,14 +97,10 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public IGeometryImpl? Intersect(IGeometryImpl geometry)
         {
-            if (EffectivePath is { } path
-                && (geometry as GeometryImpl)?.EffectivePath is { } otherPath
-                && path.Op(otherPath, SKPathOp.Intersect) is { } result)
-            {
-                return new StreamGeometryImpl(result);
-            }
-
-            return null;
+            var other = geometry as GeometryImpl;
+            if (other == null)
+                return null;
+            return CombinedGeometryImpl.TryCreate(GeometryCombineMode.Intersect, this, other);
         }
 
         /// <inheritdoc />
@@ -128,7 +125,7 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public bool TryGetPointAtDistance(double distance, out Point point)
         {
-            if (EffectivePath is null)
+            if (StrokePath is null)
             {
                 point = new Point();
                 return false;
@@ -142,7 +139,7 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent)
         {
-            if (EffectivePath is null)
+            if (StrokePath is null)
             {
                 point = new Point();
                 tangent = new Point();
@@ -158,7 +155,7 @@ namespace Avalonia.Skia
         public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure,
             [NotNullWhen(true)] out IGeometryImpl? segmentGeometry)
         {
-            if (EffectivePath is null)
+            if (StrokePath is null)
             {
                 segmentGeometry = null;
                 return false;
@@ -172,7 +169,7 @@ namespace Avalonia.Skia
 
             if (res)
             {
-                segmentGeometry = new StreamGeometryImpl(_skPathSegment);
+                segmentGeometry = new StreamGeometryImpl(_skPathSegment, null);
             }
 
             return res;

+ 3 - 2
src/Skia/Avalonia.Skia/LineGeometryImpl.cs

@@ -9,7 +9,8 @@ namespace Avalonia.Skia
     internal class LineGeometryImpl : GeometryImpl
     {
         public override Rect Bounds { get; }
-        public override SKPath EffectivePath { get; }
+        public override SKPath StrokePath { get; }
+        public override SKPath? FillPath => null;
 
         public LineGeometryImpl(Point p1, Point p2)
         {
@@ -17,7 +18,7 @@ namespace Avalonia.Skia
             path.MoveTo(p1.ToSKPoint());
             path.LineTo(p2.ToSKPoint());
 
-            EffectivePath = path;
+            StrokePath = path;
             Bounds = new Rect(
                 new Point(Math.Min(p1.X, p2.X), Math.Min(p1.Y, p2.Y)), 
                 new Point(Math.Max(p1.X, p2.X), Math.Max(p1.Y, p2.Y)));

+ 2 - 2
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@@ -65,7 +65,7 @@ namespace Avalonia.Skia
 
         public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2)
         {
-            return new CombinedGeometryImpl(combineMode, g1, g2);
+            return CombinedGeometryImpl.ForceCreate(combineMode, g1, g2);
         }
 
         public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
@@ -104,7 +104,7 @@ namespace Avalonia.Skia
 
             SKFontCache.Shared.Return(skFont);
 
-            return new StreamGeometryImpl(path);
+            return new StreamGeometryImpl(path, path);
         }
 
         /// <inheritdoc />

+ 3 - 2
src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs

@@ -8,14 +8,15 @@ namespace Avalonia.Skia
     internal class RectangleGeometryImpl : GeometryImpl
     {
         public override Rect Bounds { get; }
-        public override SKPath EffectivePath { get; }
+        public override SKPath StrokePath { get; }
+        public override SKPath? FillPath => StrokePath;
 
         public RectangleGeometryImpl(Rect rect)
         {
             var path = new SKPath();
             path.AddRect(rect.ToSKRect());
 
-            EffectivePath = path;
+            StrokePath = path;
             Bounds = rect;
         }
     }

+ 64 - 26
src/Skia/Avalonia.Skia/StreamGeometryImpl.cs

@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
 using Avalonia.Media;
 using Avalonia.Platform;
 using SkiaSharp;
@@ -10,36 +11,39 @@ namespace Avalonia.Skia
     internal class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl
     {
         private Rect _bounds;
-        private readonly SKPath _effectivePath;
+        private readonly SKPath _strokePath;
+        private SKPath? _fillPath;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
         /// </summary>
-        /// <param name="path">An existing Skia <see cref="SKPath"/>.</param>
+        /// <param name="stroke">An existing Skia <see cref="SKPath"/> for the stroke.</param>
+        /// <param name="fill">An existing Skia <see cref="SKPath"/> for the fill, can also be null or the same as the stroke</param>
         /// <param name="bounds">Precomputed path bounds.</param>
-        public StreamGeometryImpl(SKPath path, Rect bounds)
+        public StreamGeometryImpl(SKPath stroke, SKPath? fill, Rect? bounds = null)
         {
-            _effectivePath = path;
-            _bounds = bounds;
+            _strokePath = stroke;
+            _fillPath = fill;
+            _bounds = bounds ?? stroke.TightBounds.ToAvaloniaRect();
         }
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
-        /// </summary>
-        /// <param name="path">An existing Skia <see cref="SKPath"/>.</param>
-        public StreamGeometryImpl(SKPath path) : this(path, path.TightBounds.ToAvaloniaRect())
+        private StreamGeometryImpl(SKPath path) : this(path, path, default(Rect))
         {
+
         }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
         /// </summary>
-        public StreamGeometryImpl() : this(CreateEmptyPath(), default)
+        public StreamGeometryImpl() : this(CreateEmptyPath())
         {
         }
-        
+
         /// <inheritdoc />
-        public override SKPath EffectivePath => _effectivePath;
+        public override SKPath? StrokePath => _strokePath;
+
+        /// <inheritdoc />
+        public override SKPath? FillPath => _fillPath;
 
         /// <inheritdoc />
         public override Rect Bounds => _bounds;
@@ -47,7 +51,9 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public IStreamGeometryImpl Clone()
         {
-            return new StreamGeometryImpl(_effectivePath.Clone(), Bounds);
+            var stroke = _strokePath.Clone();
+            var fill = _fillPath == _strokePath ? stroke : _fillPath.Clone();
+            return new StreamGeometryImpl(stroke, fill, Bounds);
         }
 
         /// <inheritdoc />
@@ -74,7 +80,10 @@ namespace Avalonia.Skia
         private class StreamContext : IStreamGeometryContextImpl
         {
             private readonly StreamGeometryImpl _geometryImpl;
-            private readonly SKPath _path;
+            private SKPath Stroke => _geometryImpl._strokePath;
+            private SKPath Fill => _geometryImpl._fillPath ??= new();
+            private bool _isFilled;
+            private bool Duplicate => _isFilled && !ReferenceEquals(_geometryImpl._fillPath, Stroke);
 
             /// <summary>
             /// Initializes a new instance of the <see cref="StreamContext"/> class.
@@ -83,52 +92,79 @@ namespace Avalonia.Skia
             public StreamContext(StreamGeometryImpl geometryImpl)
             {
                 _geometryImpl = geometryImpl;
-                _path = _geometryImpl._effectivePath;
             }
             
             /// <inheritdoc />
             /// <remarks>Will update bounds of passed geometry.</remarks>
             public void Dispose()
             {
-                _geometryImpl._bounds = _path.TightBounds.ToAvaloniaRect();
+                _geometryImpl._bounds = Stroke.TightBounds.ToAvaloniaRect();
                 _geometryImpl.InvalidateCaches();
             }
 
             /// <inheritdoc />
             public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
             {
-                _path.ArcTo(
+                var arc = isLargeArc ? SKPathArcSize.Large : SKPathArcSize.Small;
+                var sweep = sweepDirection == SweepDirection.Clockwise
+                    ? SKPathDirection.Clockwise
+                    : SKPathDirection.CounterClockwise;
+                Stroke.ArcTo(
                     (float)size.Width,
                     (float)size.Height,
                     (float)rotationAngle,
-                    isLargeArc ? SKPathArcSize.Large : SKPathArcSize.Small,
-                    sweepDirection == SweepDirection.Clockwise ? SKPathDirection.Clockwise : SKPathDirection.CounterClockwise,
+                    arc,
+                    sweep,
                     (float)point.X,
                     (float)point.Y);
+                if(Duplicate)
+                    Fill.ArcTo(
+                        (float)size.Width,
+                        (float)size.Height,
+                        (float)rotationAngle,
+                        arc,
+                        sweep,
+                        (float)point.X,
+                        (float)point.Y);
             }
 
             /// <inheritdoc />
             public void BeginFigure(Point startPoint, bool isFilled)
             {
-                _path.MoveTo((float)startPoint.X, (float)startPoint.Y);
+                if (!isFilled)
+                {
+                    if (Stroke == Fill)
+                        _geometryImpl._fillPath = Stroke.Clone();
+                }
+                
+                _isFilled = isFilled;
+                Stroke.MoveTo((float)startPoint.X, (float)startPoint.Y);
+                if(Duplicate)
+                    Fill.MoveTo((float)startPoint.X, (float)startPoint.Y);
             }
 
             /// <inheritdoc />
             public void CubicBezierTo(Point point1, Point point2, Point point3)
             {
-                _path.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y);
+                Stroke.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y);
+                if(Duplicate)
+                    Fill.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y);
             }
 
             /// <inheritdoc />
             public void QuadraticBezierTo(Point point1, Point point2)
             {
-                _path.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y);
+                Stroke.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y);
+                if(Duplicate)
+                    Fill.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y);
             }
 
             /// <inheritdoc />
             public void LineTo(Point point)
             {
-                _path.LineTo((float)point.X, (float)point.Y);
+                Stroke.LineTo((float)point.X, (float)point.Y);
+                if(Duplicate)
+                    Fill.LineTo((float)point.X, (float)point.Y);
             }
 
             /// <inheritdoc />
@@ -136,14 +172,16 @@ namespace Avalonia.Skia
             {
                 if (isClosed)
                 {
-                    _path.Close();
+                    Stroke.Close();
+                    if (Duplicate)
+                        Fill.Close();
                 }
             }
 
             /// <inheritdoc />
             public void SetFillRule(FillRule fillRule)
             {
-                _path.FillType = fillRule == FillRule.EvenOdd ? SKPathFillType.EvenOdd : SKPathFillType.Winding;
+                Fill.FillType = fillRule == FillRule.EvenOdd ? SKPathFillType.EvenOdd : SKPathFillType.Winding;
             }
         }
     }

+ 16 - 5
src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs

@@ -17,16 +17,27 @@ namespace Avalonia.Skia
         {
             SourceGeometry = source;
             Transform = transform;
+            var matrix = transform.ToSKMatrix();
 
-            var transformedPath = source.EffectivePath.Clone();
-            transformedPath?.Transform(transform.ToSKMatrix());
-
-            EffectivePath = transformedPath;
+            var transformedPath = StrokePath =  source.StrokePath.Clone();
+            transformedPath?.Transform(matrix);
+            
             Bounds = transformedPath?.TightBounds.ToAvaloniaRect() ?? default;
+            
+            if (ReferenceEquals(source.StrokePath, source.FillPath))
+                FillPath = transformedPath;
+            else if (source.FillPath != null)
+            {
+                FillPath = transformedPath = source.FillPath.Clone();
+                transformedPath.Transform(matrix);
+            }
         }
 
         /// <inheritdoc />
-        public override SKPath? EffectivePath { get; }
+        public override SKPath? StrokePath { get; }
+        
+        /// <inheritdoc />
+        public override SKPath? FillPath { get; }
 
         /// <inheritdoc />
         public IGeometryImpl SourceGeometry { get; }

+ 58 - 0
tests/Avalonia.RenderTests/Shapes/PathTests.cs

@@ -376,5 +376,63 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
             await RenderToFile(target);
             CompareImages();
         }
+        
+        [Fact]
+        public async Task BeginFigure_IsFilled_Is_Respected()
+        {
+            var target = new Border
+            {
+                Width = 200,
+                Height = 200,
+                Background = Brushes.White,
+                Child = new Path
+                {
+                    Fill = Brushes.Black,
+                    Stroke = Brushes.Black,
+                    StrokeThickness = 10,
+                    Data = new PathGeometry()
+                    {
+                        Figures = new()
+                        {
+                            new PathFigure
+                            {
+                                IsFilled = false, IsClosed = false,
+                                StartPoint = new Point(170,170),
+                                Segments = new ()
+                                {
+                                    new LineSegment
+                                    {
+                                        Point = new Point(60, 170)
+                                    },
+                                    new LineSegment
+                                    {
+                                        Point = new Point(60, 60)
+                                    }
+                                }
+                            },
+                            new PathFigure
+                            {
+                                IsFilled = true, IsClosed = true,
+                                StartPoint = new Point(60,20),
+                                Segments = new ()
+                                {
+                                    new LineSegment
+                                    {
+                                        Point = new Point(20, 60)
+                                    },
+                                    new LineSegment
+                                    {
+                                        Point = new Point(100, 60)
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            };
+            
+            await RenderToFile(target);
+            CompareImages();
+        }
     }
 }

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


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