Files
HandyControl/Net35/HandyControl_Net35/Expression/Drawing/PathSegmentHelper.cs
T
2019-01-21 00:43:10 +08:00

755 lines
30 KiB
C#

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);
switch (num)
{
case -1:
return null;
case 0:
return CreateLineSegment(arcSegment.Point, isStroked);
case 1:
return CreateBezierSegment(pointArray[0], pointArray[1], pointArray[2], isStroked);
}
return 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<Point> 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<Point> 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<Point> 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<Point> 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<SimpleSegment> 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<Point> 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<Point> 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<Point> 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<SimpleSegment> GetSimpleSegments()
{
var segment = ArcToBezierSegments(_segment, Start);
if (segment != null) return segment.GetSimpleSegments(Start);
return Enumerable.Empty<SimpleSegment>();
}
}
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<Point> points, double tolerance)
{
Point[] controlPoints = {Start, _segment.Point1, _segment.Point2, _segment.Point3};
var resultPolyline = new List<Point>();
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<SimpleSegment> 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<Point> 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<SimpleSegment> 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<Point> points, double tolerance);
public abstract Point GetPoint(int index);
public abstract IEnumerable<SimpleSegment> 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<Point> 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<Point>();
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<SimpleSegment> GetSimpleSegments()
{
var start = Start;
IList<Point> 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<Point> 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<SimpleSegment> 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<Point> 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<Point>();
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<SimpleSegment> GetSimpleSegments()
{
var start = Start;
IList<Point> 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<Point> points, double tolerance)
{
Point[] controlPoints = {Start, _segment.Point1, _segment.Point2};
var resultPolyline = new List<Point>();
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<SimpleSegment> GetSimpleSegments()
{
yield return SimpleSegment.Create(Start, _segment.Point1, _segment.Point2);
}
}
}
}