using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Windows; using System.Windows.Media; namespace HandyControl.Expression.Drawing { internal static class PathSegmentHelper { public static PathSegment ApplyTransform(PathSegment segment, Point start, GeneralTransform transform) { return PathSegmentImplementation.Create(segment, start).ApplyTransform(transform); } public static PathSegment ArcToBezierSegments(ArcSegment arcSegment, Point startPoint) { var isStroked = arcSegment.IsStroked(); ArcToBezierHelper.ArcToBezier(startPoint.X, startPoint.Y, arcSegment.Size.Width, arcSegment.Size.Height, arcSegment.RotationAngle, arcSegment.IsLargeArc, arcSegment.SweepDirection == SweepDirection.Clockwise, arcSegment.Point.X, arcSegment.Point.Y, out var pointArray, out var num); return num switch { -1 => null, 0 => CreateLineSegment(arcSegment.Point, isStroked), 1 => CreateBezierSegment(pointArray[0], pointArray[1], pointArray[2], isStroked), _ => CreatePolyBezierSegment(pointArray, 0, num * 3, isStroked) }; } public static ArcSegment CreateArcSegment(Point point, Size size, bool isLargeArc, bool clockwise, double rotationAngle = 0.0, bool isStroked = true) { var dependencyObject = new ArcSegment(); dependencyObject.SetIfDifferent(ArcSegment.PointProperty, point); dependencyObject.SetIfDifferent(ArcSegment.SizeProperty, size); dependencyObject.SetIfDifferent(ArcSegment.IsLargeArcProperty, isLargeArc); dependencyObject.SetIfDifferent(ArcSegment.SweepDirectionProperty, clockwise ? SweepDirection.Clockwise : SweepDirection.Counterclockwise); dependencyObject.SetIfDifferent(ArcSegment.RotationAngleProperty, rotationAngle); dependencyObject.SetIsStroked(isStroked); return dependencyObject; } public static BezierSegment CreateBezierSegment(Point point1, Point point2, Point point3, bool isStroked = true) { var segment = new BezierSegment { Point1 = point1, Point2 = point2, Point3 = point3 }; segment.SetIsStroked(isStroked); return segment; } public static LineSegment CreateLineSegment(Point point, bool isStroked = true) { var segment = new LineSegment { Point = point }; segment.SetIsStroked(isStroked); return segment; } public static PolyBezierSegment CreatePolyBezierSegment(IList points, int start, int count, bool isStroked = true) { if (points == null) throw new ArgumentNullException(nameof(points)); count = count / 3 * 3; if (count < 0 || points.Count < start + count) throw new ArgumentOutOfRangeException(nameof(count)); var segment = new PolyBezierSegment { Points = new PointCollection() }; for (var i = 0; i < count; i++) segment.Points.Add(points[start + i]); segment.SetIsStroked(isStroked); return segment; } public static PolyLineSegment CreatePolylineSegment(IList points, int start, int count, bool isStroked = true) { if (count < 0 || points.Count < start + count) throw new ArgumentOutOfRangeException(nameof(count)); var segment = new PolyLineSegment { Points = new PointCollection() }; for (var i = 0; i < count; i++) segment.Points.Add(points[start + i]); segment.SetIsStroked(isStroked); return segment; } public static PolyQuadraticBezierSegment CreatePolyQuadraticBezierSegment(IList points, int start, int count, bool isStroked = true) { if (points == null) throw new ArgumentNullException(nameof(points)); count = count / 2 * 2; if (count < 0 || points.Count < start + count) throw new ArgumentOutOfRangeException(nameof(count)); var segment = new PolyQuadraticBezierSegment { Points = new PointCollection() }; for (var i = 0; i < count; i++) segment.Points.Add(points[start + i]); segment.SetIsStroked(isStroked); return segment; } public static QuadraticBezierSegment CreateQuadraticBezierSegment(Point point1, Point point2, bool isStroked = true) { var segment = new QuadraticBezierSegment { Point1 = point1, Point2 = point2 }; segment.SetIsStroked(isStroked); return segment; } public static void FlattenSegment(this PathSegment segment, IList points, Point start, double tolerance) { PathSegmentImplementation.Create(segment, start).Flatten(points, tolerance); } public static Point GetLastPoint(this PathSegment segment) { return segment.GetPoint(-1); } public static Point GetPoint(this PathSegment segment, int index) { return PathSegmentImplementation.Create(segment).GetPoint(index); } public static int GetPointCount(this PathSegment segment) { if (segment is ArcSegment) return 1; if (segment is LineSegment) return 1; if (segment is QuadraticBezierSegment) return 2; if (segment is BezierSegment) return 3; if (segment is PolyLineSegment segment2) return segment2.Points.Count; if (segment is PolyQuadraticBezierSegment segment3) return segment3.Points.Count / 2 * 2; if (segment is PolyBezierSegment segment4) return segment4.Points.Count / 3 * 3; return 0; } public static IEnumerable GetSimpleSegments(this PathSegment segment, Point start) { return PathSegmentImplementation.Create(segment, start).GetSimpleSegments(); } public static bool IsEmpty(this PathSegment segment) { return segment.GetPointCount() == 0; } private static void SetIsStroked(this PathSegment segment, bool isStroked) { if (segment.IsStroked != isStroked) segment.IsStroked = isStroked; } public static bool SyncPolyBezierSegment(PathSegmentCollection collection, int index, IList points, int start, int count) { if (collection == null) throw new ArgumentNullException(nameof(collection)); if (index < 0 || index >= collection.Count) throw new ArgumentOutOfRangeException(nameof(index)); if (points == null) throw new ArgumentNullException(nameof(points)); if (start < 0) throw new ArgumentOutOfRangeException(nameof(start)); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); if (points.Count < start + count) throw new ArgumentOutOfRangeException(nameof(count)); var flag = false; count = count / 3 * 3; var segment = collection[index] as PolyBezierSegment; if (segment == null) { collection[index] = segment = new PolyBezierSegment(); flag = true; } segment.Points.EnsureListCount(count); for (var i = 0; i < count; i++) if (segment.Points[i] != points[i + start]) { segment.Points[i] = points[i + start]; flag = true; } return flag; } public static bool SyncPolylineSegment(PathSegmentCollection collection, int index, IList points, int start, int count) { if (collection == null) throw new ArgumentNullException(nameof(collection)); if (index < 0 || index >= collection.Count) throw new ArgumentOutOfRangeException(nameof(index)); if (points == null) throw new ArgumentNullException(nameof(points)); if (start < 0) throw new ArgumentOutOfRangeException(nameof(start)); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); if (points.Count < start + count) throw new ArgumentOutOfRangeException(nameof(count)); var flag = false; var segment = collection[index] as PolyLineSegment; if (segment == null) { collection[index] = segment = new PolyLineSegment(); flag = true; } flag |= segment.Points.EnsureListCount(count); for (var i = 0; i < count; i++) if (segment.Points[i] != points[i + start]) { segment.Points[i] = points[i + start]; flag = true; } return flag; } private class ArcSegmentImplementation : PathSegmentImplementation { private ArcSegment _segment; public override PathSegment ApplyTransform(GeneralTransform transform) { var segment = ArcToBezierSegments(_segment, Start); if (segment != null) return PathSegmentHelper.ApplyTransform(segment, Start, transform); _segment.Point = transform.Transform(_segment.Point); return _segment; } public static PathSegmentImplementation Create(ArcSegment source) { if (source != null) return new ArcSegmentImplementation { _segment = source }; return null; } public override void Flatten(IList points, double tolerance) { var segment = ArcToBezierSegments(_segment, Start); segment?.FlattenSegment(points, Start, tolerance); } public override Point GetPoint(int index) { if (index < -1 || index > 0) throw new ArgumentOutOfRangeException(nameof(index)); return _segment.Point; } public override IEnumerable GetSimpleSegments() { var segment = ArcToBezierSegments(_segment, Start); if (segment != null) return segment.GetSimpleSegments(Start); return Enumerable.Empty(); } } private static class ArcToBezierHelper { private static bool AcceptRadius(double rHalfChord2, double rFuzz2, ref double rRadius) { var flag = rRadius * rRadius > rHalfChord2 * rFuzz2; if (flag && rRadius < 0.0) rRadius = -rRadius; return flag; } public static void ArcToBezier(double xStart, double yStart, double xRadius, double yRadius, double rRotation, bool fLargeArc, bool fSweepUp, double xEnd, double yEnd, out Point[] pPt, out int cPieces) { var num = 1E-06; pPt = new Point[12]; var num12 = num * num; var flag = false; cPieces = -1; var num2 = 0.5 * (xEnd - xStart); var num3 = 0.5 * (yEnd - yStart); var num4 = num2 * num2 + num3 * num3; if (num4 >= num12) { if (!AcceptRadius(num4, num12, ref xRadius) || !AcceptRadius(num4, num12, ref yRadius)) { cPieces = 0; } else { double num5; double num6; // ReSharper disable once InlineOutVariableDeclaration double num7; // ReSharper disable once InlineOutVariableDeclaration double num8; double num9; double num10; Point point4; if (Math.Abs(rRotation) < num) { num5 = 1.0; num6 = 0.0; } else { rRotation = -rRotation * 3.1415926535897931 / 180.0; num5 = Math.Cos(rRotation); num6 = Math.Sin(rRotation); var num15 = num2 * num5 - num3 * num6; num3 = num2 * num6 + num3 * num5; num2 = num15; } num2 /= xRadius; num3 /= yRadius; num4 = num2 * num2 + num3 * num3; if (num4 > 1.0) { var num16 = Math.Sqrt(num4); xRadius *= num16; yRadius *= num16; num9 = num10 = 0.0; flag = true; num2 /= num16; num3 /= num16; } else { var num17 = Math.Sqrt((1.0 - num4) / num4); if (fLargeArc != fSweepUp) { num9 = -num17 * num3; num10 = num17 * num2; } else { num9 = num17 * num3; num10 = -num17 * num2; } } var ptStart = new Point(-num2 - num9, -num3 - num10); var ptEnd = new Point(num2 - num9, num3 - num10); var matrix = new Matrix(num5 * xRadius, -num6 * xRadius, num6 * yRadius, num5 * yRadius, 0.5 * (xEnd + xStart), 0.5 * (yEnd + yStart)); if (!flag) { matrix.OffsetX += matrix.M11 * num9 + matrix.M21 * num10; matrix.OffsetY += matrix.M12 * num9 + matrix.M22 * num10; } GetArcAngle(ptStart, ptEnd, fLargeArc, fSweepUp, out num7, out num8, out cPieces); var bezierDistance = GetBezierDistance(num7); if (!fSweepUp) bezierDistance = -bezierDistance; var rhs = new Point(-bezierDistance * ptStart.Y, bezierDistance * ptStart.X); var index = 0; pPt = new Point[cPieces * 3]; for (var i = 1; i < cPieces; i++) { var lhs = new Point(ptStart.X * num7 - ptStart.Y * num8, ptStart.X * num8 + ptStart.Y * num7); point4 = new Point(-bezierDistance * lhs.Y, bezierDistance * lhs.X); pPt[index++] = matrix.Transform(ptStart.Plus(rhs)); pPt[index++] = matrix.Transform(lhs.Minus(point4)); pPt[index++] = matrix.Transform(lhs); ptStart = lhs; rhs = point4; } point4 = new Point(-bezierDistance * ptEnd.Y, bezierDistance * ptEnd.X); pPt[index++] = matrix.Transform(ptStart.Plus(rhs)); pPt[index++] = matrix.Transform(ptEnd.Minus(point4)); pPt[index] = new Point(xEnd, yEnd); } } } private static void GetArcAngle(Point ptStart, Point ptEnd, bool fLargeArc, bool fSweepUp, out double rCosArcAngle, out double rSinArcAngle, out int cPieces) { rCosArcAngle = GeometryHelper.Dot(ptStart, ptEnd); rSinArcAngle = GeometryHelper.Determinant(ptStart, ptEnd); if (rCosArcAngle >= 0.0) { if (!fLargeArc) { cPieces = 1; return; } cPieces = 4; } else if (fLargeArc) { cPieces = 3; } else { cPieces = 2; } var d = Math.Atan2(rSinArcAngle, rCosArcAngle); if (fSweepUp) { if (d < 0.0) d += 6.2831853071795862; } else if (d > 0.0) { d -= 6.2831853071795862; } d /= cPieces; rCosArcAngle = Math.Cos(d); rSinArcAngle = Math.Sin(d); } private static double GetBezierDistance(double rDot, double rRadius = 1.0) { var num = rRadius * rRadius; var num5 = 0.0; var d = 0.5 * (num + rDot); if (d < 0.0) return num5; var num3 = num - d; if (num3 <= 0.0) return num5; var num4 = Math.Sqrt(num3); var num2 = 4.0 * (rRadius - Math.Sqrt(d)) / 3.0; if (num2 <= num4 * 1E-06) return 0.0; return num2 / num4; } } private class BezierSegmentImplementation : PathSegmentImplementation { private BezierSegment _segment; public override PathSegment ApplyTransform(GeneralTransform transform) { _segment.Point1 = transform.Transform(_segment.Point1); _segment.Point2 = transform.Transform(_segment.Point2); _segment.Point3 = transform.Transform(_segment.Point3); return _segment; } public static PathSegmentImplementation Create(BezierSegment source) { if (source != null) return new BezierSegmentImplementation { _segment = source }; return null; } public override void Flatten(IList points, double tolerance) { Point[] controlPoints = { Start, _segment.Point1, _segment.Point2, _segment.Point3 }; var resultPolyline = new List(); BezierCurveFlattener.FlattenCubic(controlPoints, tolerance, resultPolyline, true); points.AddRange(resultPolyline); } public override Point GetPoint(int index) { if (index < -1 || index > 2) throw new ArgumentOutOfRangeException(nameof(index)); if (index == 0) return _segment.Point1; if (index == 1) return _segment.Point2; return _segment.Point3; } public override IEnumerable GetSimpleSegments() { yield return SimpleSegment.Create(Start, _segment.Point1, _segment.Point2, _segment.Point3); } } private class LineSegmentImplementation : PathSegmentImplementation { private LineSegment _segment; public override PathSegment ApplyTransform(GeneralTransform transform) { _segment.Point = transform.Transform(_segment.Point); return _segment; } public static PathSegmentImplementation Create(LineSegment source) { if (source != null) return new LineSegmentImplementation { _segment = source }; return null; } public override void Flatten(IList points, double tolerance) { points.Add(_segment.Point); } public override Point GetPoint(int index) { if (index < -1 || index > 0) throw new ArgumentOutOfRangeException(nameof(index)); return _segment.Point; } public override IEnumerable GetSimpleSegments() { yield return SimpleSegment.Create(Start, _segment.Point); } } private abstract class PathSegmentImplementation { protected Point Start { get; private set; } public abstract PathSegment ApplyTransform(GeneralTransform transform); public static PathSegmentImplementation Create(PathSegment segment) { PathSegmentImplementation implementation; if ((implementation = BezierSegmentImplementation.Create(segment as BezierSegment)) == null && (implementation = LineSegmentImplementation.Create(segment as LineSegment)) == null && (implementation = ArcSegmentImplementation.Create(segment as ArcSegment)) == null && (implementation = PolyLineSegmentImplementation.Create(segment as PolyLineSegment)) == null && (implementation = PolyBezierSegmentImplementation.Create(segment as PolyBezierSegment)) == null && (implementation = QuadraticBezierSegmentImplementation.Create(segment as QuadraticBezierSegment)) == null && (implementation = PolyQuadraticBezierSegmentImplementation.Create(segment as PolyQuadraticBezierSegment) ) == null) throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, ExceptionStringTable.TypeNotSupported, new object[] { segment.GetType().FullName })); return implementation; } public static PathSegmentImplementation Create(PathSegment segment, Point start) { var implementation = Create(segment); implementation.Start = start; return implementation; } public abstract void Flatten(IList points, double tolerance); public abstract Point GetPoint(int index); public abstract IEnumerable GetSimpleSegments(); } private class PolyBezierSegmentImplementation : PathSegmentImplementation { private PolyBezierSegment _segment; public override PathSegment ApplyTransform(GeneralTransform transform) { _segment.Points.ApplyTransform(transform); return _segment; } public static PathSegmentImplementation Create(PolyBezierSegment source) { if (source != null) return new PolyBezierSegmentImplementation { _segment = source }; return null; } public override void Flatten(IList points, double tolerance) { var start = Start; var num = _segment.Points.Count / 3 * 3; for (var i = 0; i < num; i += 3) { Point[] controlPoints = { start, _segment.Points[i], _segment.Points[i + 1], _segment.Points[i + 2] }; var resultPolyline = new List(); BezierCurveFlattener.FlattenCubic(controlPoints, tolerance, resultPolyline, true); points.AddRange(resultPolyline); start = _segment.Points[i + 2]; } } public override Point GetPoint(int index) { var num = _segment.Points.Count / 3 * 3; if (index < -1 || index > num - 1) throw new ArgumentOutOfRangeException(nameof(index)); if (index != -1) return _segment.Points[index]; return _segment.Points[num - 1]; } public override IEnumerable GetSimpleSegments() { var start = Start; IList points = _segment.Points; var iteratorVariable2 = _segment.Points.Count / 3; var iteratorVariable3 = 0; while (true) { if (iteratorVariable3 >= iteratorVariable2) yield break; var iteratorVariable4 = iteratorVariable3 * 3; yield return SimpleSegment.Create(start, points[iteratorVariable4], points[iteratorVariable4 + 1], points[iteratorVariable4 + 2]); start = points[iteratorVariable4 + 2]; iteratorVariable3++; } } } private class PolyLineSegmentImplementation : PathSegmentImplementation { private PolyLineSegment _segment; public override PathSegment ApplyTransform(GeneralTransform transform) { _segment.Points.ApplyTransform(transform); return _segment; } public static PathSegmentImplementation Create(PolyLineSegment source) { if (source != null) return new PolyLineSegmentImplementation { _segment = source }; return null; } public override void Flatten(IList points, double tolerance) { points.AddRange(_segment.Points); } public override Point GetPoint(int index) { if (index < -1 || index > _segment.Points.Count - 1) throw new ArgumentOutOfRangeException(nameof(index)); if (index != -1) return _segment.Points[index]; return _segment.Points.Last(); } public override IEnumerable GetSimpleSegments() { var start = Start; // ReSharper disable once GenericEnumeratorNotDisposed var enumerator = _segment.Points.GetEnumerator(); while (enumerator.MoveNext()) { var current = enumerator.Current; yield return SimpleSegment.Create(start, current); start = current; } } } private class PolyQuadraticBezierSegmentImplementation : PathSegmentImplementation { private PolyQuadraticBezierSegment _segment; public override PathSegment ApplyTransform(GeneralTransform transform) { _segment.Points.ApplyTransform(transform); return _segment; } public static PathSegmentImplementation Create(PolyQuadraticBezierSegment source) { if (source != null) return new PolyQuadraticBezierSegmentImplementation { _segment = source }; return null; } public override void Flatten(IList points, double tolerance) { var start = Start; var num = _segment.Points.Count / 2 * 2; for (var i = 0; i < num; i += 2) { Point[] controlPoints = { start, _segment.Points[i], _segment.Points[i + 1] }; var resultPolyline = new List(); BezierCurveFlattener.FlattenQuadratic(controlPoints, tolerance, resultPolyline, true); points.AddRange(resultPolyline); start = _segment.Points[i + 1]; } } public override Point GetPoint(int index) { var num = _segment.Points.Count / 2 * 2; if (index < -1 || index > num - 1) throw new ArgumentOutOfRangeException(nameof(index)); if (index != -1) return _segment.Points[index]; return _segment.Points[num - 1]; } public override IEnumerable GetSimpleSegments() { var start = Start; IList points = _segment.Points; var iteratorVariable2 = _segment.Points.Count / 2; var iteratorVariable3 = 0; while (true) { if (iteratorVariable3 >= iteratorVariable2) yield break; var iteratorVariable4 = iteratorVariable3 * 2; yield return SimpleSegment.Create(start, points[iteratorVariable4], points[iteratorVariable4 + 1]); start = points[iteratorVariable4 + 1]; iteratorVariable3++; } } } private class QuadraticBezierSegmentImplementation : PathSegmentImplementation { private QuadraticBezierSegment _segment; public override PathSegment ApplyTransform(GeneralTransform transform) { _segment.Point1 = transform.Transform(_segment.Point1); _segment.Point2 = transform.Transform(_segment.Point2); return _segment; } public static PathSegmentImplementation Create(QuadraticBezierSegment source) { if (source != null) return new QuadraticBezierSegmentImplementation { _segment = source }; return null; } public override void Flatten(IList points, double tolerance) { Point[] controlPoints = { Start, _segment.Point1, _segment.Point2 }; var resultPolyline = new List(); BezierCurveFlattener.FlattenQuadratic(controlPoints, tolerance, resultPolyline, true); points.AddRange(resultPolyline); } public override Point GetPoint(int index) { if (index < -1 || index > 1) throw new ArgumentOutOfRangeException(nameof(index)); return index == 0 ? _segment.Point1 : _segment.Point2; } public override IEnumerable GetSimpleSegments() { yield return SimpleSegment.Create(Start, _segment.Point1, _segment.Point2); } } } }