File: PathF.cs
Web Access
Project: src\src\Graphics\src\Graphics\Graphics.csproj (Microsoft.Maui.Graphics)
using System;
using System.Collections.Generic;
using System.Numerics;
// ReSharper disable MemberCanBePrivate.Global
 
namespace Microsoft.Maui.Graphics
{
	public class PathF : IDisposable
	{
		private const float K_RATIO = 0.551784777779014f; // ideal ratio of cubic Bezier points for a quarter circle
 
		private readonly List<float> _arcAngles;
		private readonly List<bool> _arcClockwise;
		private readonly List<PointF> _points;
		private readonly List<PathOperation> _operations;
		private int _subPathCount;
		private readonly List<bool> _subPathsClosed;
 
		private object _platformPath;
		private RectF? _cachedBounds;
 
		private PathF(List<PointF> points, List<float> arcSizes, List<bool> arcClockwise, List<PathOperation> operations, int subPathCount)
		{
			_points = points;
			_arcAngles = arcSizes;
			_arcClockwise = arcClockwise;
			_operations = operations;
			_subPathCount = subPathCount;
			_subPathsClosed = new List<bool>();
 
			var subPathIndex = 0;
			foreach (var operation in _operations)
			{
				if (operation == PathOperation.Move)
				{
					subPathIndex++;
					_subPathsClosed.Add(false);
				}
				else if (operation == PathOperation.Close)
				{
					_subPathsClosed.RemoveAt(subPathIndex - 1);
					_subPathsClosed.Add(true);
				}
			}
		}
 
		public PathF(PathF path) : this()
		{
			_operations.AddRange(path._operations);
			_points = new List<PointF>(path._points);
 
			_arcAngles.AddRange(path._arcAngles);
			_arcClockwise.AddRange(path._arcClockwise);
 
			_subPathCount = path._subPathCount;
			_subPathsClosed = new List<bool>(path._subPathsClosed);
		}
 
		public PathF(PointF point) : this()
		{
			MoveTo(point.X, point.Y);
		}
 
		public PathF(float x, float y) : this()
		{
			MoveTo(x, y);
		}
 
		public PathF()
		{
			_subPathCount = 0;
			_arcAngles = new List<float>();
			_arcClockwise = new List<bool>();
			_points = new List<PointF>();
			_operations = new List<PathOperation>();
			_subPathsClosed = new List<bool>();
		}
 
		public int SubPathCount => _subPathCount;
 
		public bool Closed
		{
			get
			{
				if (_operations.Count > 0)
					return _operations[_operations.Count - 1] == PathOperation.Close;
 
				return false;
			}
		}
 
		public PointF FirstPoint
		{
			get
			{
				if (_points != null && _points.Count > 0)
					return _points[0];
 
				return default;
			}
		}
 
		public IEnumerable<PathOperation> SegmentTypes
		{
			get
			{
				for (var i = 0; i < _operations.Count; i++)
					yield return _operations[i];
			}
		}
 
		public IEnumerable<PointF> Points
		{
			get
			{
				for (var i = 0; i < _points.Count; i++)
					yield return _points[i];
			}
		}
 
		public PointF LastPoint
		{
			get
			{
				if (_points != null && _points.Count > 0)
					return _points[_points.Count - 1];
 
				return default;
			}
		}
 
		public int LastPointIndex
		{
			get
			{
				if (_points != null && _points.Count > 0)
					return _points.Count - 1;
 
				return -1;
			}
		}
 
		public PointF this[int index]
		{
			get
			{
				if (index < 0 || index >= _points.Count)
					return default;
 
				return _points[index];
			}
			//set { points[index] = value; }
		}
 
		public void SetPoint(int index, float x, float y)
		{
			_points[index] = new PointF(x, y);
			Invalidate();
		}
 
		public void SetPoint(int index, PointF point)
		{
			_points[index] = point;
			Invalidate();
		}
 
		public int Count => _points.Count;
 
		public int OperationCount => _operations.Count;
 
		public int SegmentCountExcludingOpenAndClose
		{
			get
			{
				if (_operations != null)
				{
					var operationsCount = _operations.Count;
					if (operationsCount > 0)
					{
						if (_operations[0] == PathOperation.Move)
						{
							operationsCount--;
						}
 
						if (_operations[_operations.Count - 1] == PathOperation.Close)
						{
							operationsCount--;
						}
					}
 
					return operationsCount;
				}
 
				return 0;
			}
		}
 
		public PathOperation GetSegmentType(int aIndex)
		{
			return _operations[aIndex];
		}
 
		public float GetArcAngle(int aIndex)
		{
			if (_arcAngles.Count > aIndex)
			{
				return _arcAngles[aIndex];
			}
 
			return 0;
		}
 
		public void SetArcAngle(int aIndex, float aValue)
		{
			if (_arcAngles.Count > aIndex)
			{
				_arcAngles[aIndex] = aValue;
			}
 
			Invalidate();
		}
 
		public bool GetArcClockwise(int aIndex)
		{
			if (_arcClockwise.Count > aIndex)
			{
				return _arcClockwise[aIndex];
			}
 
			return false;
		}
 
		public void SetArcClockwise(int aIndex, bool aValue)
		{
			if (_arcClockwise.Count > aIndex)
			{
				_arcClockwise[aIndex] = aValue;
			}
 
			Invalidate();
		}
 
		public PathF MoveTo(float x, float y)
		{
			return MoveTo(new PointF(x, y));
		}
 
		public PathF MoveTo(PointF point)
		{
			_subPathCount++;
			_subPathsClosed.Add(false);
			_points.Add(point);
			_operations.Add(PathOperation.Move);
			Invalidate();
			return this;
		}
 
		public void Close()
		{
			if (!Closed)
			{
				_subPathsClosed.RemoveAt(_subPathCount - 1);
				_subPathsClosed.Add(true);
				_operations.Add(PathOperation.Close);
			}
 
			Invalidate();
		}
 
		public void Open()
		{
			if (_operations[_operations.Count - 1] == PathOperation.Close)
			{
				_subPathsClosed.RemoveAt(_subPathCount - 1);
				_subPathsClosed.Add(false);
				_operations.RemoveAt(_operations.Count - 1);
			}
 
			Invalidate();
		}
 
		public PathF LineTo(float x, float y)
		{
			return LineTo(new PointF(x, y));
		}
 
		public PathF LineTo(PointF point)
		{
			if (_points.Count == 0)
			{
				_points.Add(point);
				_subPathCount++;
				_subPathsClosed.Add(false);
				_operations.Add(PathOperation.Move);
			}
			else
			{
				_points.Add(point);
				_operations.Add(PathOperation.Line);
			}
 
			Invalidate();
 
			return this;
		}
 
		public PathF InsertLineTo(PointF point, int index)
		{
			if (index == 0)
			{
				index = 1;
			}
 
			if (index == OperationCount)
			{
				LineTo(point);
			}
			else
			{
				var pointIndex = GetSegmentPointIndex(index);
				_points.Insert(pointIndex, point);
				_operations.Insert(index, PathOperation.Line);
				Invalidate();
			}
 
			return this;
		}
 
		public PathF AddArc(float x1, float y1, float x2, float y2, float startAngle, float endAngle, bool clockwise)
		{
			return AddArc(new PointF(x1, y1), new PointF(x2, y2), startAngle, endAngle, clockwise);
		}
 
		public PathF AddArc(PointF topLeft, PointF bottomRight, float startAngle, float endAngle, bool clockwise)
		{
			if (Count == 0 || OperationCount == 0 || GetSegmentType(OperationCount - 1) == PathOperation.Close)
			{
				_subPathCount++;
				_subPathsClosed.Add(false);
			}
 
			_points.Add(topLeft);
			_points.Add(bottomRight);
			_arcAngles.Add(startAngle);
			_arcAngles.Add(endAngle);
			_arcClockwise.Add(clockwise);
			_operations.Add(PathOperation.Arc);
			Invalidate();
			return this;
		}
 
		public PathF QuadTo(float cx, float cy, float x, float y)
		{
			return QuadTo(new PointF(cx, cy), new PointF(x, y));
		}
 
		public PathF QuadTo(PointF controlPoint, PointF point)
		{
			_points.Add(controlPoint);
			_points.Add(point);
			_operations.Add(PathOperation.Quad);
			Invalidate();
			return this;
		}
 
		public PathF InsertQuadTo(PointF controlPoint, PointF point, int index)
		{
			if (index == 0)
			{
				index = 1;
			}
 
			if (index == OperationCount)
			{
				QuadTo(controlPoint, point);
			}
			else
			{
				var pointIndex = GetSegmentPointIndex(index);
				_points.Insert(pointIndex, point);
				_points.Insert(pointIndex, controlPoint);
				_operations.Insert(index, PathOperation.Quad);
				Invalidate();
			}
 
			return this;
		}
 
		public PathF CurveTo(float c1X, float c1Y, float c2X, float c2Y, float x, float y)
		{
			return CurveTo(new PointF(c1X, c1Y), new PointF(c2X, c2Y), new PointF(x, y));
		}
 
		public PathF CurveTo(PointF controlPoint1, PointF controlPoint2, PointF point)
		{
			_points.Add(controlPoint1);
			_points.Add(controlPoint2);
			_points.Add(point);
			_operations.Add(PathOperation.Cubic);
			Invalidate();
			return this;
		}
 
		public PathF InsertCurveTo(PointF controlPoint1, PointF controlPoint2, PointF point, int index)
		{
			if (index == 0)
			{
				index = 1;
			}
 
			if (index == OperationCount)
			{
				CurveTo(controlPoint1, controlPoint2, point);
			}
			else
			{
				var pointIndex = GetSegmentPointIndex(index);
				_points.Insert(pointIndex, point);
				_points.Insert(pointIndex, controlPoint2);
				_points.Insert(pointIndex, controlPoint1);
				_operations.Insert(index, PathOperation.Cubic);
				Invalidate();
			}
 
			return this;
		}
 
		public int GetSegmentPointIndex(int index)
		{
			if (index <= OperationCount)
			{
				var pointIndex = 0;
				for (var operationIndex = 0; operationIndex < _operations.Count; operationIndex++)
				{
					var operation = _operations[operationIndex];
					if (operation == PathOperation.Move)
					{
						if (operationIndex == index)
							return pointIndex;
 
						pointIndex++;
					}
					else if (operation == PathOperation.Line)
					{
						if (operationIndex == index)
							return pointIndex;
 
						pointIndex++;
					}
					else if (operation == PathOperation.Quad)
					{
						if (operationIndex == index)
							return pointIndex;
 
						pointIndex += 2;
					}
					else if (operation == PathOperation.Cubic)
					{
						if (operationIndex == index)
							return pointIndex;
 
						pointIndex += 3;
					}
					else if (operation == PathOperation.Arc)
					{
						if (operationIndex == index)
							return pointIndex;
 
						pointIndex += 2;
					}
					else if (operation == PathOperation.Close)
					{
						if (operationIndex == index)
							return pointIndex;
					}
				}
			}
 
			return -1;
		}
 
		public PathOperation GetSegmentInfo(int segmentIndex, out int pointIndex, out int arcAngleIndex, out int arcClockwiseIndex)
		{
			pointIndex = 0;
			arcAngleIndex = 0;
			arcClockwiseIndex = 0;
 
			if (segmentIndex <= OperationCount)
			{
				for (var s = 0; s < _operations.Count; s++)
				{
					var type = _operations[s];
					if (type == PathOperation.Move)
					{
						if (s == segmentIndex)
							return type;
 
						pointIndex++;
					}
					else if (type == PathOperation.Line)
					{
						if (s == segmentIndex)
							return type;
						pointIndex++;
					}
					else if (type == PathOperation.Quad)
					{
						if (s == segmentIndex)
							return type;
						pointIndex += 2;
					}
					else if (type == PathOperation.Cubic)
					{
						if (s == segmentIndex)
							return type;
						pointIndex += 3;
					}
					else if (type == PathOperation.Arc)
					{
						if (s == segmentIndex)
							return type;
						pointIndex += 2;
						arcAngleIndex += 2;
						arcClockwiseIndex++;
					}
					else if (type == PathOperation.Close)
					{
						if (s == segmentIndex)
							return type;
					}
				}
			}
 
			return PathOperation.Close;
		}
 
		public int GetSegmentForPoint(int pointIndex)
		{
			if (pointIndex < _points.Count)
			{
				var index = 0;
				for (var segment = 0; segment < _operations.Count; segment++)
				{
					var segmentType = _operations[segment];
					if (segmentType == PathOperation.Move)
					{
						if (pointIndex == index++)
						{
							return segment;
						}
					}
					else if (segmentType == PathOperation.Line)
					{
						if (pointIndex == index++)
						{
							return segment;
						}
					}
					else if (segmentType == PathOperation.Quad)
					{
						if (pointIndex == index++)
						{
							return segment;
						}
 
						if (pointIndex == index++)
						{
							return segment;
						}
					}
					else if (segmentType == PathOperation.Cubic)
					{
						if (pointIndex == index++)
						{
							return segment;
						}
 
						if (pointIndex == index++)
						{
							return segment;
						}
 
						if (pointIndex == index++)
						{
							return segment;
						}
					}
					else if (segmentType == PathOperation.Arc)
					{
						if (pointIndex == index++)
						{
							return segment;
						}
 
						if (pointIndex == index++)
						{
							return segment;
						}
					}
				}
			}
 
			return -1;
		}
 
		public PointF[] GetPointsForSegment(int segmentIndex)
		{
			if (segmentIndex <= OperationCount)
			{
				var pointIndex = 0;
				for (var segment = 0; segment < _operations.Count; segment++)
				{
					var segmentType = _operations[segment];
					if (segmentType == PathOperation.Move)
					{
						if (segment == segmentIndex)
						{
							var points = new[] { _points[pointIndex] };
							return points;
						}
 
						pointIndex++;
					}
					else if (segmentType == PathOperation.Line)
					{
						if (segment == segmentIndex)
						{
							var points = new[] { _points[pointIndex] };
							return points;
						}
 
						pointIndex++;
					}
 
					else if (segmentType == PathOperation.Quad)
					{
						if (segment == segmentIndex)
						{
							var points = new[] { _points[pointIndex++], _points[pointIndex] };
							return points;
						}
 
						pointIndex += 2;
					}
					else if (segmentType == PathOperation.Cubic)
					{
						if (segment == segmentIndex)
						{
							var points = new[] { _points[pointIndex++], _points[pointIndex++], _points[pointIndex] };
							return points;
						}
 
						pointIndex += 3;
					}
					else if (segmentType == PathOperation.Arc)
					{
						if (segment == segmentIndex)
						{
							var points = new[] { _points[pointIndex++], _points[pointIndex] };
							return points;
						}
 
						pointIndex += 2;
					}
					else if (segmentType == PathOperation.Close)
					{
						if (segment == segmentIndex)
						{
							return Array.Empty<PointF>();
						}
					}
				}
			}
 
			return null;
		}
 
		private void RemoveAllAfter(int pointIndex, int segmentIndex, int arcIndex, int arcClockwiseIndex)
		{
			_points.RemoveRange(pointIndex, _points.Count - pointIndex);
			_operations.RemoveRange(segmentIndex, _operations.Count - segmentIndex);
			_arcAngles.RemoveRange(arcIndex, _arcAngles.Count - arcIndex);
			_arcClockwise.RemoveRange(arcClockwiseIndex, _arcClockwise.Count - arcClockwiseIndex);
 
			_subPathCount = 0;
			_subPathsClosed.Clear();
 
			foreach (var operation in _operations)
			{
				if (operation == PathOperation.Move)
				{
					_subPathCount++;
					_subPathsClosed.Add(false);
				}
				else if (operation == PathOperation.Close)
				{
					_subPathsClosed.RemoveAt(_subPathCount);
					_subPathsClosed.Add(true);
				}
			}
 
			if (_subPathCount > 0)
			{
				_subPathCount--;
			}
 
			Invalidate();
		}
 
		public void RemoveAllSegmentsAfter(int segmentIndex)
		{
			if (segmentIndex <= OperationCount)
			{
				var pointIndex = 0;
				var arcIndex = 0;
				var arcClockwiseIndex = 0;
				for (var segment = 0; segment < _operations.Count; segment++)
				{
					var segmentType = _operations[segment];
 
					if (segment == segmentIndex)
					{
						RemoveAllAfter(pointIndex, segment, arcIndex, arcClockwiseIndex);
						return;
					}
 
					if (segmentType == PathOperation.Move)
					{
						pointIndex++;
					}
					else if (segmentType == PathOperation.Line)
					{
						pointIndex++;
					}
					else if (segmentType == PathOperation.Quad)
					{
						pointIndex += 2;
					}
					else if (segmentType == PathOperation.Cubic)
					{
						pointIndex += 3;
					}
					else if (segmentType == PathOperation.Arc)
					{
						pointIndex += 2;
						arcIndex += 2;
						arcClockwiseIndex += 1;
					}
				}
			}
 
			Invalidate();
		}
 
		public void RemoveSegment(int segmentIndex)
		{
			if (segmentIndex <= OperationCount)
			{
				Invalidate();
 
				var pointIndex = 0;
				var arcIndex = 0;
				var arcClockwiseIndex = 0;
 
				for (var segment = 0; segment < _operations.Count; segment++)
				{
					var segmentType = _operations[segment];
					if (segmentType == PathOperation.Move)
					{
						if (segment == segmentIndex)
						{
							if (segmentIndex == _operations.Count - 1)
							{
								var points = GetPointsForSegment(segmentIndex);
								if (points != null)
								{
									for (var i = 0; i < points.Length; i++)
									{
										_points.RemoveAt(pointIndex);
									}
								}
 
								_operations.RemoveAt(segmentIndex);
							}
							else
							{
								var points = GetPointsForSegment(segmentIndex + 1);
								if (points != null)
								{
									if (points.Length > 0)
									{
										_points[pointIndex] = (PointF)points[points.Length - 1];
										for (var i = 0; i < points.Length; i++)
										{
											_points.RemoveAt(pointIndex + 1);
										}
									}
 
									_operations.RemoveAt(segmentIndex + 1);
								}
							}
 
							return;
						}
 
						pointIndex++;
					}
					else if (segmentType == PathOperation.Line)
					{
						if (segment == segmentIndex)
						{
							_points.RemoveAt(pointIndex);
							_operations.RemoveAt(segmentIndex);
							return;
						}
 
						pointIndex++;
					}
					else if (segmentType == PathOperation.Quad)
					{
						if (segment == segmentIndex)
						{
							_points.RemoveAt(pointIndex);
							_points.RemoveAt(pointIndex);
							_operations.RemoveAt(segmentIndex);
							return;
						}
 
						pointIndex += 2;
					}
					else if (segmentType == PathOperation.Cubic)
					{
						if (segment == segmentIndex)
						{
							_points.RemoveAt(pointIndex);
							_points.RemoveAt(pointIndex);
							_points.RemoveAt(pointIndex);
							_operations.RemoveAt(segmentIndex);
							return;
						}
 
						pointIndex += 3;
					}
					else if (segmentType == PathOperation.Arc)
					{
						if (segment == segmentIndex)
						{
							_points.RemoveAt(pointIndex);
							_points.RemoveAt(pointIndex);
							_operations.RemoveAt(segmentIndex);
							_arcAngles.RemoveAt(arcIndex);
							_arcAngles.RemoveAt(arcIndex);
							_arcClockwise.RemoveAt(arcClockwiseIndex);
							return;
						}
 
						pointIndex += 2;
						arcIndex += 2;
						arcClockwiseIndex += 1;
					}
					else if (segmentType == PathOperation.Close)
					{
						if (segment == segmentIndex)
						{
							_operations.RemoveAt(segmentIndex);
							return;
						}
					}
				}
			}
		}
 
		public PathF Rotate(float angleAsDegrees, PointF pivot)
		{
			var path = new PathF();
 
			var index = 0;
			var arcIndex = 0;
			var clockwiseIndex = 0;
 
			foreach (var segmentType in _operations)
			{
				if (segmentType == PathOperation.Move)
				{
					var rotatedPoint = GetRotatedPoint(index++, pivot, angleAsDegrees);
					path.MoveTo(rotatedPoint);
				}
				else if (segmentType == PathOperation.Line)
				{
					var rotatedPoint = GetRotatedPoint(index++, pivot, angleAsDegrees);
					path.LineTo(rotatedPoint.X, rotatedPoint.Y);
				}
				else if (segmentType == PathOperation.Quad)
				{
					var rotatedControlPoint = GetRotatedPoint(index++, pivot, angleAsDegrees);
					var rotatedEndPoint = GetRotatedPoint(index++, pivot, angleAsDegrees);
					path.QuadTo(rotatedControlPoint.X, rotatedControlPoint.Y, rotatedEndPoint.X, rotatedEndPoint.Y);
				}
				else if (segmentType == PathOperation.Cubic)
				{
					var rotatedControlPoint1 = GetRotatedPoint(index++, pivot, angleAsDegrees);
					var rotatedControlPoint2 = GetRotatedPoint(index++, pivot, angleAsDegrees);
					var rotatedEndPoint = GetRotatedPoint(index++, pivot, angleAsDegrees);
					path.CurveTo(rotatedControlPoint1.X, rotatedControlPoint1.Y, rotatedControlPoint2.X, rotatedControlPoint2.Y, rotatedEndPoint.X, rotatedEndPoint.Y);
				}
				else if (segmentType == PathOperation.Arc)
				{
					var topLeft = GetRotatedPoint(index++, pivot, angleAsDegrees);
					var bottomRight = GetRotatedPoint(index++, pivot, angleAsDegrees);
					var startAngle = _arcAngles[arcIndex++];
					var endAngle = _arcAngles[arcIndex++];
					var clockwise = _arcClockwise[clockwiseIndex++];
 
					path.AddArc(topLeft, bottomRight, startAngle, endAngle, clockwise);
				}
				else if (segmentType == PathOperation.Close)
				{
					path.Close();
				}
			}
 
			return path;
		}
 
		public PointF GetRotatedPoint(int pointIndex, PointF pivotPoint, float angle)
		{
			var point = _points[pointIndex];
			return GeometryUtil.RotatePoint(pivotPoint, point, angle);
		}
 
		public void Transform(Matrix3x2 transform)
		{
			for (var i = 0; i < _points.Count; i++)
				_points[i] = Vector2.Transform((Vector2)_points[i], transform);
 
			Invalidate();
		}
 
		public List<PathF> Separate()
		{
			var paths = new List<PathF>();
			if (_points == null || _operations == null)
				return paths;
 
			PathF path = null;
 
			// ReSharper disable PossibleNullReferenceException
			var i = 0;
			var a = 0;
			var c = 0;
 
			foreach (var operation in _operations)
			{
				if (operation == PathOperation.Move)
				{
					path = new PathF();
					paths.Add(path);
					path.MoveTo(_points[i++]);
				}
				else if (operation == PathOperation.Line)
				{
					path.LineTo(_points[i++]);
				}
				else if (operation == PathOperation.Quad)
				{
					path.QuadTo(_points[i++], _points[i++]);
				}
				else if (operation == PathOperation.Cubic)
				{
					path.CurveTo(_points[i++], _points[i++], _points[i++]);
				}
				else if (operation == PathOperation.Arc)
				{
					path.AddArc(_points[i++], _points[i++], _arcAngles[a++], _arcAngles[a++], _arcClockwise[c++]);
				}
				else if (operation == PathOperation.Close)
				{
					path.Close();
					path = null;
				}
			}
			// ReSharper restore PossibleNullReferenceException
 
			return paths;
		}
 
		public PathF Reverse()
		{
			var points = new List<PointF>(_points);
			points.Reverse();
 
			var arcSizes = new List<float>(_arcAngles);
			arcSizes.Reverse();
 
			var arcClockwise = new List<bool>(_arcClockwise);
			arcClockwise.Reverse();
 
			var operations = new List<PathOperation>(_operations);
			operations.Reverse();
 
			var segmentClosed = false;
			var segmentStart = -1;
 
			for (var i = 0; i < operations.Count; i++)
			{
				if (operations[i] == PathOperation.Move)
				{
					if (segmentStart == -1)
					{
						operations.RemoveAt(i);
						operations.Insert(0, PathOperation.Move);
					}
					else if (segmentClosed)
					{
						operations[segmentStart] = PathOperation.Move;
						operations[i] = PathOperation.Close;
					}
 
					segmentStart = i + 1;
				}
				else if (operations[i] == PathOperation.Close)
				{
					segmentStart = i;
					segmentClosed = true;
				}
			}
 
			return new PathF(points, arcSizes, arcClockwise, operations, _subPathCount);
		}
 
		public void AppendEllipse(RectF rect)
		{
			AppendEllipse(rect.X, rect.Y, rect.Width, rect.Height);
		}
 
		public void AppendEllipse(float x, float y, float w, float h)
		{
			var minX = x;
			var minY = y;
			var maxX = minX + w;
			var maxY = minY + h;
			var midX = minX + w / 2;
			var midY = minY + h / 2;
			var offsetY = h / 2 * K_RATIO;
			var offsetX = w / 2 * K_RATIO;
 
			MoveTo(new PointF(minX, midY));
			CurveTo(new PointF(minX, midY - offsetY), new PointF(midX - offsetX, minY), new PointF(midX, minY));
			CurveTo(new PointF(midX + offsetX, minY), new PointF(maxX, midY - offsetY), new PointF(maxX, midY));
			CurveTo(new PointF(maxX, midY + offsetY), new PointF(midX + offsetX, maxY), new PointF(midX, maxY));
			CurveTo(new PointF(midX - offsetX, maxY), new PointF(minX, midY + offsetY), new PointF(minX, midY));
			Close();
		}
 
		public void AppendCircle(PointF center, float r)
		{
			AppendCircle(center.X, center.Y, r);
		}
 
		public void AppendCircle(float cx, float cy, float r)
		{
			var minX = cx - r;
			var minY = cy - r;
			var maxX = cx + r;
			var maxY = cy + r;
			var midX = cx;
			var midY = cy;
			var offsetY = r * K_RATIO;
			var offsetX = r * K_RATIO;
 
			MoveTo(new PointF(minX, midY));
			CurveTo(new PointF(minX, midY - offsetY), new PointF(midX - offsetX, minY), new PointF(midX, minY));
			CurveTo(new PointF(midX + offsetX, minY), new PointF(maxX, midY - offsetY), new PointF(maxX, midY));
			CurveTo(new PointF(maxX, midY + offsetY), new PointF(midX + offsetX, maxY), new PointF(midX, maxY));
			CurveTo(new PointF(midX - offsetX, maxY), new PointF(minX, midY + offsetY), new PointF(minX, midY));
			Close();
		}
 
		public void AppendRectangle(RectF rect, bool includeLast = false)
		{
			AppendRectangle(rect.X, rect.Y, rect.Width, rect.Height, includeLast);
		}
 
		public void AppendRectangle(float x, float y, float w, float h, bool includeLast = false)
		{
			var minX = x;
			var minY = y;
			var maxX = minX + w;
			var maxY = minY + h;
 
			MoveTo(new PointF(minX, minY));
			LineTo(new PointF(maxX, minY));
			LineTo(new PointF(maxX, maxY));
			LineTo(new PointF(minX, maxY));
 
			if (includeLast)
			{
				LineTo(new PointF(minX, minY));
			}
 
			Close();
		}
 
		public void AppendRoundedRectangle(RectF rect, float cornerRadius, bool includeLast = false)
		{
			AppendRoundedRectangle(rect.X, rect.Y, rect.Width, rect.Height, cornerRadius, includeLast);
		}
 
		public void AppendRoundedRectangle(float x, float y, float w, float h, float cornerRadius, bool includeLast = false)
		{
			cornerRadius = ClampCornerRadius(cornerRadius, w, h);
 
			var minX = x;
			var minY = y;
			var maxX = minX + w;
			var maxY = minY + h;
 
			var handleOffset = cornerRadius * K_RATIO;
			var cornerOffset = cornerRadius - handleOffset;
 
			MoveTo(new PointF(minX, minY + cornerRadius));
			CurveTo(new PointF(minX, minY + cornerOffset), new PointF(minX + cornerOffset, minY), new PointF(minX + cornerRadius, minY));
			LineTo(new PointF(maxX - cornerRadius, minY));
			CurveTo(new PointF(maxX - cornerOffset, minY), new PointF(maxX, minY + cornerOffset), new PointF(maxX, minY + cornerRadius));
			LineTo(new PointF(maxX, maxY - cornerRadius));
			CurveTo(new PointF(maxX, maxY - cornerOffset), new PointF(maxX - cornerOffset, maxY), new PointF(maxX - cornerRadius, maxY));
			LineTo(new PointF(minX + cornerRadius, maxY));
			CurveTo(new PointF(minX + cornerOffset, maxY), new PointF(minX, maxY - cornerOffset), new PointF(minX, maxY - cornerRadius));
 
			if (includeLast)
			{
				LineTo(new PointF(minX, minY + cornerRadius));
			}
 
			Close();
		}
 
		public void AppendRoundedRectangle(RectF rect, float topLeftCornerRadius, float topRightCornerRadius, float bottomLeftCornerRadius, float bottomRightCornerRadius, bool includeLast = false)
		{
			AppendRoundedRectangle(rect.X, rect.Y, rect.Width, rect.Height, topLeftCornerRadius, topRightCornerRadius, bottomLeftCornerRadius, bottomRightCornerRadius, includeLast);
		}
 
		public void AppendRoundedRectangle(RectF rect, float xCornerRadius, float yCornerRadius)
		{
			xCornerRadius = Math.Min(xCornerRadius, rect.Width / 2);
			yCornerRadius = Math.Min(yCornerRadius, rect.Height / 2);
 
			float minX = Math.Min(rect.X, rect.X + rect.Width);
			float minY = Math.Min(rect.Y, rect.Y + rect.Height);
			float maxX = Math.Max(rect.X, rect.X + rect.Width);
			float maxY = Math.Max(rect.Y, rect.Y + rect.Height);
 
			var xHandleOffset = xCornerRadius * K_RATIO;
			var xCornerOffset = xCornerRadius - xHandleOffset;
 
			var yHandleOffset = yCornerRadius * K_RATIO;
			var yCornerOffset = yCornerRadius - yHandleOffset;
 
			MoveTo(new PointF(minX, minY + yCornerRadius));
 
			CurveTo(
				new PointF(minX, minY + yCornerOffset),
				new PointF(minX + xCornerOffset, minY),
				new PointF(minX + xCornerRadius, minY));
 
			LineTo(new PointF(maxX - xCornerRadius, minY));
 
			CurveTo(
				new PointF(maxX - xCornerOffset, minY),
				new PointF(maxX, minY + yCornerOffset),
				new PointF(maxX, minY + yCornerRadius));
 
			LineTo(new PointF(maxX, maxY - yCornerRadius));
 
			CurveTo(
				new PointF(maxX, maxY - yCornerOffset),
				new PointF(maxX - xCornerOffset, maxY),
				new PointF(maxX - xCornerRadius, maxY));
 
			LineTo(new PointF(minX + xCornerRadius, maxY));
 
			CurveTo(
				new PointF(minX + xCornerOffset, maxY),
				new PointF(minX, maxY - yCornerOffset),
				new PointF(minX, maxY - yCornerRadius));
 
			LineTo(new PointF(minX, minY + yCornerRadius));
		}
 
		public void AppendRoundedRectangle(float x, float y, float w, float h, float topLeftCornerRadius, float topRightCornerRadius, float bottomLeftCornerRadius, float bottomRightCornerRadius, bool includeLast = false)
		{
			topLeftCornerRadius = ClampCornerRadius(topLeftCornerRadius, w, h);
			topRightCornerRadius = ClampCornerRadius(topRightCornerRadius, w, h);
			bottomLeftCornerRadius = ClampCornerRadius(bottomLeftCornerRadius, w, h);
			bottomRightCornerRadius = ClampCornerRadius(bottomRightCornerRadius, w, h);
 
			var minX = x;
			var minY = y;
			var maxX = minX + w;
			var maxY = minY + h;
 
			var topLeftCornerOffset = topLeftCornerRadius - (topLeftCornerRadius * K_RATIO);
			var topRightCornerOffset = topRightCornerRadius - (topRightCornerRadius * K_RATIO);
			var bottomLeftCornerOffset = bottomLeftCornerRadius - (bottomLeftCornerRadius * K_RATIO);
			var bottomRightCornerOffset = bottomRightCornerRadius - (bottomRightCornerRadius * K_RATIO);
 
			MoveTo(new PointF(minX, minY + topLeftCornerRadius));
			CurveTo(new PointF(minX, minY + topLeftCornerOffset), new PointF(minX + topLeftCornerOffset, minY), new PointF(minX + topLeftCornerRadius, minY));
			LineTo(new PointF(maxX - topRightCornerRadius, minY));
			CurveTo(new PointF(maxX - topRightCornerOffset, minY), new PointF(maxX, minY + topRightCornerOffset), new PointF(maxX, minY + topRightCornerRadius));
			LineTo(new PointF(maxX, maxY - bottomRightCornerRadius));
			CurveTo(new PointF(maxX, maxY - bottomRightCornerOffset), new PointF(maxX - bottomRightCornerOffset, maxY), new PointF(maxX - bottomRightCornerRadius, maxY));
			LineTo(new PointF(minX + bottomLeftCornerRadius, maxY));
			CurveTo(new PointF(minX + bottomLeftCornerOffset, maxY), new PointF(minX, maxY - bottomLeftCornerOffset), new PointF(minX, maxY - bottomLeftCornerRadius));
 
			if (includeLast)
			{
				LineTo(new PointF(minX, minY + topLeftCornerRadius));
			}
 
			Close();
		}
 
		private float ClampCornerRadius(float cornerRadius, float w, float h)
		{
			if (cornerRadius > h / 2)
				cornerRadius = h / 2;
 
			if (cornerRadius > w / 2)
				cornerRadius = w / 2;
 
			return cornerRadius;
		}
 
		public bool IsSubPathClosed(int subPathIndex)
		{
			if (subPathIndex >= 0 && subPathIndex < SubPathCount)
			{
				return _subPathsClosed[subPathIndex];
			}
 
			return false;
		}
 
		public object PlatformPath
		{
			get => _platformPath;
			set
			{
				ReleaseNative();
				_platformPath = value;
			}
		}
 
		public void Invalidate()
		{
			_cachedBounds = null;
			ReleaseNative();
		}
 
		public void Dispose()
		{
			ReleaseNative();
		}
 
		private void ReleaseNative()
		{
			if (_platformPath is IDisposable disposable)
				disposable.Dispose();
 
			_platformPath = null;
		}
 
		public void Move(float x, float y)
		{
			for (var i = 0; i < _points.Count; i++)
			{
				_points[i] = _points[i].Offset(x, y);
			}
 
			Invalidate();
		}
 
		public void MovePoint(int index, float dx, float dy)
		{
			_points[index] = _points[index].Offset(dx, dy);
			Invalidate();
		}
 
		public override bool Equals(object obj)
		{
			if (obj is PathF compareTo)
			{
				if (OperationCount != compareTo.OperationCount)
					return false;
 
				for (var i = 0; i < _operations.Count; i++)
				{
					var segmentType = _operations[i];
					if (segmentType != compareTo.GetSegmentType(i))
						return false;
				}
 
				for (var i = 0; i < _points.Count; i++)
				{
					var point = _points[i];
					if (!point.Equals(compareTo[i], GeometryUtil.Epsilon))
						return false;
				}
 
				if (_arcAngles != null)
				{
					for (var i = 0; i < _arcAngles.Count; i++)
					{
						var arcAngle = _arcAngles[i];
						if (Math.Abs(arcAngle - compareTo.GetArcAngle(i)) > GeometryUtil.Epsilon)
							return false;
					}
				}
 
				if (_arcClockwise != null)
				{
					for (var i = 0; i < _arcClockwise.Count; i++)
					{
						var arcClockwise = _arcClockwise[i];
						if (arcClockwise != compareTo.GetArcClockwise(i))
							return false;
					}
				}
			}
 
			return true;
		}
 
		public override int GetHashCode()
		{
			unchecked
			{
				var hashCode = (_arcAngles != null ? _arcAngles.GetHashCode() : 0);
				hashCode = (hashCode * 397) ^ (_arcClockwise != null ? _arcClockwise.GetHashCode() : 0);
				hashCode = (hashCode * 397) ^ (_points != null ? _points.GetHashCode() : 0);
				hashCode = (hashCode * 397) ^ (_operations != null ? _operations.GetHashCode() : 0);
				return hashCode;
			}
		}
 
		public bool Equals(object obj, float epsilon)
		{
			if (obj is PathF compareTo)
			{
				if (OperationCount != compareTo.OperationCount)
					return false;
 
				for (var i = 0; i < _operations.Count; i++)
				{
					var segmentType = _operations[i];
					if (segmentType != compareTo.GetSegmentType(i))
						return false;
				}
 
				for (var i = 0; i < _points.Count; i++)
				{
					var point = _points[i];
					if (!point.Equals(compareTo[i], epsilon))
						return false;
				}
 
				if (_arcAngles != null)
				{
					for (var i = 0; i < _arcAngles.Count; i++)
					{
						var arcAngle = _arcAngles[i];
						if (Math.Abs(arcAngle - compareTo.GetArcAngle(i)) > epsilon)
							return false;
					}
				}
 
				if (_arcClockwise != null)
				{
					for (var i = 0; i < _arcClockwise.Count; i++)
					{
						var arcClockwise = _arcClockwise[i];
						if (arcClockwise != compareTo.GetArcClockwise(i))
							return false;
					}
				}
			}
 
			return true;
		}
 
		public RectF Bounds
		{
			get
			{
				if (_cachedBounds != null)
					return (RectF)_cachedBounds;
 
#if IOS || MACCATALYST || __IOS__

				if (PlatformPath is not global::CoreGraphics.CGPath cgPath)
				{
					PlatformPath = cgPath = Platform.GraphicsExtensions.AsCGPath(this);
				}
 
				_cachedBounds = Platform.GraphicsExtensions.AsRectangleF(cgPath.PathBoundingBox);
#else
				_cachedBounds = GetBoundsByFlattening();
#endif
 
				return (RectF)_cachedBounds;
			}
		}
 
		public RectF GetBoundsByFlattening(float flatness = 0.001f)
		{
			if (_cachedBounds != null)
				return (RectF)_cachedBounds;
 
			var path = GetFlattenedPath(flatness, true);
 
			float l = 0f;
			float t = 0f;
			float r = l;
			float b = t;
 
			// Make sure the path actually has points in it.
			if (path != null && path.Count > 0)
			{
				l = path[0].X;
				t = path[0].Y;
				r = l;
				b = t;
 
				for (int i = 1; i < path.Count; i++)
				{
					var point = path[i];
					if (point.X < l)
						l = point.X;
					if (point.Y < t)
						t = point.Y;
					if (point.X > r)
						r = point.X;
					if (point.Y > b)
						b = point.Y;
				}
			}
 
			_cachedBounds = new RectF(l, t, r - l, b - t);
			return (RectF)_cachedBounds;
		}
 
		public PathF GetFlattenedPath(float flatness = .001f, bool includeSubPaths = false)
		{
			var flattenedPath = new PathF();
			List<PointF> flattenedPoints = null;
			List<PointF> curvePoints = null;
			bool foundClosed = false;
			var pointIndex = 0;
			int arcAngleIndex = 0;
			int arcClockwiseIndex = 0;
 
			for (var i = 0; i < _operations.Count && !foundClosed; i++)
			{
				var operation = _operations[i];
				switch (operation)
				{
					case PathOperation.Move:
						flattenedPath.MoveTo(_points[pointIndex++]);
						break;
					case PathOperation.Line:
						flattenedPath.LineTo(_points[pointIndex++]);
						break;
					case PathOperation.Quad:
						flattenedPoints ??= new List<PointF>();
						flattenedPoints.Clear();
						curvePoints ??= new List<PointF>();
						curvePoints.Clear();
						QuadToCubic(pointIndex, curvePoints);
						FlattenCubicSegment(0, flatness, curvePoints, flattenedPoints);
						foreach (var point in flattenedPoints)
							flattenedPath.LineTo(point);
						pointIndex += 2;
						break;
					case PathOperation.Cubic:
						flattenedPoints ??= new List<PointF>();
						flattenedPoints.Clear();
						FlattenCubicSegment(pointIndex - 1, flatness, _points, flattenedPoints);
						foreach (var point in flattenedPoints)
							flattenedPath.LineTo(point);
						pointIndex += 3;
						break;
					case PathOperation.Arc:
						var topLeft = _points[pointIndex++];
						var bottomRight = _points[pointIndex++];
						float startAngle = GetArcAngle(arcAngleIndex++);
						float endAngle = GetArcAngle(arcAngleIndex++);
						var clockwise = GetArcClockwise(arcClockwiseIndex++);
						var flattenedArcPath = FlattenArc(topLeft, bottomRight, startAngle, endAngle, clockwise, flatness);
						foreach (var point in flattenedArcPath.Points)
							flattenedPath.LineTo(point);
						break;
					case PathOperation.Close:
						flattenedPath.Close();
						if (!includeSubPaths)
						{
							foundClosed = true;
						}
						break;
					default:
						throw new ArgumentOutOfRangeException();
				}
			}
 
			return flattenedPath;
		}
 
		private PathF FlattenArc(PointF topLeft, PointF bottomRight, float startAngle, float endAngle, bool clockwise, float flattness)
		{
			var arcFlattener = new ArcFlattener(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y, startAngle, endAngle, clockwise);
			var flattenedPath = arcFlattener.CreateFlattenedPath(flattness);
			return flattenedPath.GetFlattenedPath();
		}
 
		private void QuadToCubic(int pointIndex, List<PointF> curvePoints)
		{
			var startPoint = _points[pointIndex - 1];
			var quadControlPoint = _points[pointIndex];
			var endPoint = _points[pointIndex + 1];
 
			var controlPoint1 = new PointF(startPoint.X + 2.0f * (quadControlPoint.X - startPoint.X) / 3.0f, startPoint.Y + 2.0f * (quadControlPoint.Y - startPoint.Y) / 3.0f);
			var controlPoint2 = new PointF(endPoint.X + 2.0f * (quadControlPoint.X - endPoint.X) / 3.0f, endPoint.Y + 2.0f * (quadControlPoint.Y - endPoint.Y) / 3.0f);
 
			curvePoints.Add(startPoint);
			curvePoints.Add(controlPoint1);
			curvePoints.Add(controlPoint2);
			curvePoints.Add(endPoint);
		}
 
		private void FlattenCubicSegment(int index, double flatness, List<PointF> curvePoints, List<PointF> flattenedPoints)
		{
			int i, k;
			var numberOfPoints = 1;
			var vectors = new Vector2[4];
 
			double rCurve = 0;
 
			for (i = index + 1; i <= index + 2; i++)
			{
				vectors[0] = (GetPointAsVector(curvePoints, i - 1) + GetPointAsVector(curvePoints, i + 1)) * 0.5f - GetPointAsVector(curvePoints, i);
 
				double r = vectors[0].Length();
 
				if (r > rCurve)
					rCurve = r;
			}
 
			if (rCurve <= 0.5 * flatness)
			{
				var vector = GetPointAsVector(curvePoints, index + 3);
				flattenedPoints.Add(new Point(vector.X, vector.Y));
				return;
			}
 
			numberOfPoints = (int)(Math.Sqrt(rCurve / flatness)) + 3;
			if (numberOfPoints > 1000)
				numberOfPoints = 1000;
 
			var d = 1.0f / numberOfPoints;
 
			vectors[0] = GetPointAsVector(curvePoints, index);
			for (i = 1; i <= 3; i++)
			{
				vectors[i] = DeCasteljau(curvePoints, index, i * d);
				flattenedPoints.Add(new Point(vectors[i].X, vectors[i].Y));
			}
 
			for (i = 1; i <= 3; i++)
				for (k = 0; k <= (3 - i); k++)
					vectors[k] = vectors[k + 1] - vectors[k];
 
			for (i = 4; i <= numberOfPoints; i++)
			{
				for (k = 1; k <= 3; k++)
					vectors[k] += vectors[k - 1];
 
				flattenedPoints.Add(new Point(vectors[3].X, vectors[3].Y));
			}
		}
 
		private Vector2 DeCasteljau(List<PointF> curvePoints, int index, float t)
		{
			var s = 1.0f - t;
 
			var vector0 = s * GetPointAsVector(curvePoints, index) + t * GetPointAsVector(curvePoints, index + 1);
			var vector1 = s * GetPointAsVector(curvePoints, index + 1) + t * GetPointAsVector(curvePoints, index + 2);
			var vector2 = s * GetPointAsVector(curvePoints, index + 2) + t * GetPointAsVector(curvePoints, index + 3);
 
			vector0 = s * vector0 + t * vector1;
			vector1 = s * vector1 + t * vector2;
			return s * vector0 + t * vector1;
		}
 
		private Vector2 GetPointAsVector(List<PointF> curvePoints, int index)
		{
			var point = curvePoints[index];
			return new Vector2(point.X, point.Y);
		}
	}
}