private static void Append(StringBuilder builder, Subpath subpath)
 {
     builder.Append('M');
     builder.Append(PointToString(subpath.StartPoint));
     builder.Append(' ');
     foreach (var segment in subpath.Segments)
     {
         if (segment is LineSegment)
         {
             builder.Append('L');
             builder.Append(PointToString(segment.EndPoint));
             builder.Append(' ');
         }
         else if (segment is CubicBezierSegment)
         {
             var cubicBezierSegment = (CubicBezierSegment)segment;
             builder.Append('C');
             builder.Append(PointToString(cubicBezierSegment.ControlPoint1));
             builder.Append(' ');
             builder.Append(PointToString(cubicBezierSegment.ControlPoint2));
             builder.Append(' ');
             builder.Append(PointToString(cubicBezierSegment.EndPoint));
             builder.Append(' ');
         }
         else if (segment is QuadraticBezierSegment)
         {
             var quadraticBezierSegment = (QuadraticBezierSegment)segment;
             builder.Append('Q');
             builder.Append(PointToString(quadraticBezierSegment.ControlPoint));
             builder.Append(' ');
             builder.Append(PointToString(quadraticBezierSegment.EndPoint));
             builder.Append(' ');
         }
         else if (segment is EllipticalArcSegment)
         {
             var ellipticalArcSegment = (EllipticalArcSegment)segment;
             builder.Append('A');
             builder.Append(PointToString(ellipticalArcSegment.Radii));
             builder.Append(' ');
             builder.Append(DoubleToString(ellipticalArcSegment.XAxisRotation));
             builder.Append(' ');
             builder.Append(ellipticalArcSegment.IsLargeArc ? '1' : '0');
             builder.Append(' ');
             builder.Append(ellipticalArcSegment.IsSweep ? '1' : '0');
             builder.Append(' ');
             builder.Append(PointToString(ellipticalArcSegment.EndPoint));
             builder.Append(' ');
         }
         else
         {
             throw new PathDataConverterException("Unknown segment type.");
         }
     }
     if (subpath.IsClosed)
     {
         builder.Append('Z');
         builder.Append(' ');
     }
 }
 public static IEnumerable<Path> CreatePaths(Subpath data)
 {
     yield return Visualizer.CreateFillPath(PathFormatter.ToString(data));
     foreach (var x in Visualizer.CreatePathsForSubpath(data))
     {
         yield return x;
     }
 }
        public static double CalculateSigned(Subpath subpath)
        {
            var points = subpath.PolygonApproximation.Points;

            double doubleArea = 0;
            var point1 = points.Last();
            foreach (var point2 in points)
            {
                doubleArea += (point2.X - point1.X) * (point1.Y + point2.Y);
                point1 = point2;
            }
            return doubleArea / 2;
        }
 public static Subpath ReverseDirection(Subpath subpath)
 {
     var segments = new SegmentBase[subpath.Segments.Count];
     var startPoint = subpath.StartPoint;
     for (var i = 0; i < segments.Length; i++)
     {
         var segment = subpath.Segments[i];
         segments[i] = segment.Reverse(startPoint);
         startPoint = segment.EndPoint;
     }
     Array.Reverse(segments);
     return new Subpath(startPoint, segments, subpath.IsClosed);
 }
 private static Subpath ChooseConcatenation(Subpath subpath, IReadOnlyList<Subpath> concatenations)
 {
     if (concatenations.Count == 1)
     {
         return concatenations[0];
     }
     return concatenations
         .MinBy(
             x =>
             {
                 var points1 = subpath.PolygonApproximation.Points;
                 var points2 = x.PolygonApproximation.Points;
                 return Angle(points1.Last() - points1.LastButOne(), points2.Second() - points2.First());
             });
 }
        public static IEnumerable<Subpath> SplitByIntersections(Subpath subpath)
        {
            var startPoint = subpath.StartPoint;
            var segments = subpath.ClosedSegments.ToList();

            while (true)
            {
                var i = segments.FindIndex(x => x.Intersections.Count > 0);
                if (i < 0)
                {
                    if (segments.Count > 0)
                    {
                        yield return new Subpath(startPoint, segments, false);
                    }
                    yield break;
                }
                SegmentBase segment1, segment2;
                var intersection = segments[i].SplitByNextIntersection(i > 0 ? segments[i - 1].EndPoint : startPoint, out segment1, out segment2);
                var segments1 = segments.GetRange(0, i);
                if (segment1 != null)
                {
                    if (segment1.Intersections.Count > 0)
                    {
                        throw new PathDataConverterException();
                    }
                    segments1.Add(segment1);
                }
                if (segments1.Count > 0)
                {
                    yield return new Subpath(startPoint, segments1, false);
                }
                i++;
                var segments2 = segments.GetRange(i, segments.Count - i);
                if (segment2 != null)
                {
                    segments2.Insert(0, segment2);
                }

                startPoint = intersection;
                segments = segments2;
            }
        }
 public static bool EqualsIgnoreDirection(this Subpath subpath1, Subpath subpath2)
 {
     if (subpath1.Segments.Count != subpath2.Segments.Count ||
         subpath1.IsClosed != subpath2.IsClosed)
     {
         return false;
     }
     if (subpath1.StartPoint == subpath2.StartPoint &&
         subpath1.EndPoint == subpath2.EndPoint &&
         Equals(subpath1, subpath2))
     {
         return true;
     }
     if (subpath1.StartPoint == subpath2.EndPoint &&
         subpath1.EndPoint == subpath2.StartPoint &&
         Equals(subpath1, SubpathDirectionReverser.ReverseDirection(subpath2)))
     {
         return true;
     }
     return false;
 }
 public static IEnumerable<Subpath> Unify(IEnumerable<Subpath> subpaths)
 {
     var collection = new SubpathCollection();
     foreach (var subpath in subpaths)
     {
         if (subpath.AreSegmentsClosed)
         {
             if (HasFillArea(subpath))
             {
                 yield return subpath;
             }
         }
         else
         {
             collection.Add(subpath);
         }
     }
     var subpath1 = collection.Dequeue();
     while (subpath1 != null)
     {
         var concatenations = collection.GetAll(subpath1.EndPoint).ToArray();
         Debugger.BreakWhen(concatenations.Length == 0);
         if (concatenations.Length > 0)
         {
             var subpath2 = ChooseConcatenation(subpath1, concatenations);
             collection.Remove(subpath2);
             subpath1 = new Subpath(subpath1.StartPoint, subpath1.Segments.Concat(subpath2.Segments), false);
             if (!subpath1.AreSegmentsClosed)
             {
                 continue;
             }
         }
         if (HasFillArea(subpath1))
         {
             yield return subpath1;
         }
         subpath1 = collection.Dequeue();
     }
 }
        public static IEnumerable<Path> CreatePathsForSubpath(Subpath subpath)
        {
            var source = PathFormatter.ToString(subpath);
            var hash = BitConverter.GetBytes(source.GetHashCode());
            var color = ColorHelper.FromHsb(360f * hash[0] / 255, 0.5f * hash[1] / 255 + 0.5f, 0.5f * hash[2] / 255);
            var group1 = new GeometryGroup();
            group1.Children.Add(Geometry.Parse(source));
            group1.Children.Add(new RectangleGeometry(new Rect(subpath.StartPoint.X - 2.5, subpath.StartPoint.Y - 2.5, 5, 5)));
            foreach (var segment in subpath.Segments)
            {
                group1.Children.Add(new EllipseGeometry(new Point(segment.EndPoint.X, segment.EndPoint.Y), 1.5, 1.5));
            }
            yield return new Path
            {
                Data = group1,
                Stroke = new SolidColorBrush(color),
                StrokeThickness = 3,
                Opacity = 0.5
            };

            var intersections = subpath.ClosedSegments.SelectMany(x => x.Intersections).ToArray();
            if (intersections.Length == 0)
            {
                yield break;
            }
            var group2 = new GeometryGroup();
            foreach (var point in intersections)
            {
                group2.Children.Add(new EllipseGeometry(new Point(point.X, point.Y), 1, 1));
            }
            yield return new Path
            {
                Data = group2,
                Stroke = new SolidColorBrush(Colors.Black),
                StrokeThickness = 2,
                Opacity = 0.3
            };
        }
 public static SubpathDirection CalculateDirection(Subpath subpath)
 {
     return SubpathFillAreaCalculator.CalculateSigned(subpath) <= 0 ? SubpathDirection.Clockwise : SubpathDirection.Counterclockwise;
 }
 private static bool Equals(Subpath subpath1, Subpath subpath2)
 {
     return PathFormatter.ToString(subpath1) == PathFormatter.ToString(subpath2);
 }
 private static bool HasFillArea(Subpath subpath)
 {
     // area is larger than 1/20 of a pixel
     return Math.Abs(SubpathFillAreaCalculator.CalculateSigned(subpath)) >= 0.05;
 }
 public void Remove(Subpath subpath)
 {
     var i = _subpaths.IndexOf(subpath);
     if (i < 0)
     {
         i = _reverseSubpaths.IndexOf(subpath);
     }
     _subpaths.RemoveAt(i);
     _reverseSubpaths.RemoveAt(i);
 }
 public void Add(Subpath subpath)
 {
     _subpaths.Add(subpath);
     _reverseSubpaths.Add(SubpathDirectionReverser.ReverseDirection(subpath));
 }