Giter Site home page Giter Site logo

bezierkit's Introduction

BezierKit

License: MIT codecov CocoaPods Compatible

BezierKit is a comprehensive Bezier Path library written in Swift.

Warning! Prerelease software!

Please note that BezierKit is currently pre-release software. Its releases follow semantic versioning which means that until it reaches 1.0 status the API may not be stable or backwards compatible.

Features

  • Constructs linear (line segment), quadratic, and cubic Bézier curves
  • Draws curves via CoreGraphics
  • Determines positions, derivatives, and normals along curves
  • Lengths of curves via Legendre-Gauss quadrature
  • Intersects curves and computes cubic curve self-intersection to any degree of accuracy
  • Determines bounding boxes, extrema,
  • Locates nearest on-curve location to point
  • to any degree of accuracy
  • Splits curves into subcurves
  • Offsets and outlines curves
  • Comprehensive Unit and Integration Test Coverage
  • Complete Documentation

Installation

CocoaPods

CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:

$ gem install cocoapods

To integrate BezierKit into your Xcode project using CocoaPods, add it to your target in your Podfile:

target '<Your Target Name>' do
    pod 'BezierKit', '>= 0.15.0'
end

Then, run the following command:

$ pod install

Swift Package Manager

The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler. Once you have your Swift package set up, adding BezierKit as a dependency is as easy as adding it to the dependencies value of your Package.swift.

// swift-tools-version:5.0
import PackageDescription

let package = Package(
    name: "<Your Target Name>",
    dependencies: [
        .package(url: "https://github.com/hfutrell/BezierKit.git", from: "0.15.0"),
    ]
)

Usage

Constructing & Drawing Curves

BezierKit supports cubic Bezier curves (CubicCurve) and quadratic Bezier curves (QuadraticCurve) as well as line segments (LineSegment) each of which adopts the BezierCurve protocol that encompasses most API functionality.

import BezierKit

let curve = CubicCurve(
    p0: CGPoint(x: 100, y: 25),
    p1: CGPoint(x: 10, y: 90),
    p2: CGPoint(x: 110, y: 100),
    p3: CGPoint(x: 150, y: 195)
 )
 
 let context: CGContext = ...       // your graphics context here
 Draw.drawSkeleton(context, curve)  // draws visual representation of curve control points
 Draw.drawCurve(context, curve)     // draws the curve itself

Intersecting Curves

The intersections(with curve: BezierCurve) -> [Intersection] method determines each intersection between self and curve as an array of Intersection objects. Each intersection has two fields: t1 represents the t-value for self at the intersection while t2 represents the t-value for curve at the intersection. You can use the ponit(at:) method on either of the curves to calculate the coordinates of the intersection by passing in the corresponding t-value for the curve.

Cubic curves may self-intersect which can be determined by calling the selfIntersections() method.

let intersections: [Intersection] = curve1.intersections(with: curve2)
let points: [CGPoint] = intersections.map { curve1.point(at: $0.t1) }

Draw.drawCurve(context, curve: curve1)
Draw.drawCurve(context, curve: curve2)
for p in points {
    Draw.drawPoint(context, origin: p)
}

Splitting Curves

The split(from:, to:) method produces a subcurve over a given range of t-values. The split(at:) method can be used to produce a left subcurve and right subcurve created by splitting across a single t-value.

Draw.setColor(context, color: Draw.lightGrey)
Draw.drawSkeleton(context, curve: curve)
Draw.drawCurve(context, curve: curve)
let subcurve = curve.split(from: 0.25, to: 0.75) // or try (leftCurve, rightCurve) = curve.split(at:)
Draw.setColor(context, color: Draw.red)
Draw.drawCurve(context, curve: subcurve)
Draw.drawCircle(context, center: curve.point(at: 0.25), radius: 3)
Draw.drawCircle(context, center: curve.point(at: 0.75), radius: 3)

Determining Bounding Boxes

let boundingBox = curve.boundingBox
Draw.drawSkeleton(context, curve: curve)
Draw.drawCurve(context, curve: curve)
Draw.setColor(context, color: Draw.pinkish)
Draw.drawBoundingBox(context, boundingBox: curve.boundingBox)

More

BezierKit is a powerful library with a lot of functionality. For the time being the best way to see what it offers is to build the MacDemos target and check out each of the provided demos.

License

BezierKit is released under the MIT license. See LICENSE for details.

bezierkit's People

Contributors

ceylo avatar hfutrell avatar kateinoigakukun avatar pedrovgs avatar vincentspitale avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bezierkit's Issues

this contour crashes `crossingsRemoved`

Screen Shot 2021-01-15 at Jan 15  1 44 57 PM

I am making a Path using this contour as a component and passing it through crossingsRemoved(). The two off-curve points are key. Once the upper point gets one unit to the right of the lower point, it triggers this assertion in addNeighbor.

Path.crossingRemoved() produces wrong result

Hey @hfutrell

I randomly get some invalid results from Path.crossingsRemoved(accuracy: 1)
I haven't identified any simple repeatable use case for now.

So here us a dump of a case where it happens. Note that I've used the method you mentioned here (Not sure this issue is linked with issue #64 btw).

Original Path:

component 0: curves = [BezierKit.CubicCurve(p0: (-230.0, 679.0), p1: (-242.0, 611.0), p2: (-245.0, 540.0), p3: (-244.0, 445.0)), BezierKit.CubicCurve(p0: (-244.0, 445.0), p1: (-244.0, 423.0), p2: (-261.0, 405.0), p3: (-284.0, 405.0)), BezierKit.CubicCurve(p0: (-284.0, 405.0), p1: (-306.0, 405.0), p2: (-324.0, 422.0), p3: (-324.0, 445.0)), BezierKit.CubicCurve(p0: (-324.0, 445.0), p1: (-325.0, 543.0), p2: (-321.0, 618.0), p3: (-309.0, 692.0)), BezierKit.CubicCurve(p0: (-309.0, 692.0), p1: (-306.0, 714.0), p2: (-285.0, 729.0), p3: (-263.0, 725.0)), BezierKit.CubicCurve(p0: (-263.0, 725.0), p1: (-241.0, 721.0), p2: (-227.0, 701.0), p3: (-230.0, 679.0))]
component 1: curves = [BezierKit.CubicCurve(p0: (-244.0, 446.0), p1: (-244.0, 434.0), p2: (-243.0, 421.0), p3: (-243.0, 405.0)), BezierKit.CubicCurve(p0: (-243.0, 405.0), p1: (-243.0, 402.0), p2: (-243.0, 399.0), p3: (-243.0, 395.0)), BezierKit.CubicCurve(p0: (-243.0, 395.0), p1: (-243.0, 390.0), p2: (-243.0, 388.0), p3: (-243.0, 385.0)), BezierKit.CubicCurve(p0: (-243.0, 385.0), p1: (-243.0, 378.0), p2: (-242.0, 372.0), p3: (-242.0, 366.0)), BezierKit.CubicCurve(p0: (-242.0, 366.0), p1: (-240.0, 239.0), p2: (-237.0, 179.0), p3: (-232.0, 123.0)), BezierKit.CubicCurve(p0: (-232.0, 123.0), p1: (-230.0, 101.0), p2: (-246.0, 81.0), p3: (-268.0, 79.0)), BezierKit.CubicCurve(p0: (-268.0, 79.0), p1: (-290.0, 77.0), p2: (-310.0, 93.0), p3: (-312.0, 115.0)), BezierKit.CubicCurve(p0: (-312.0, 115.0), p1: (-317.0, 174.0), p2: (-319.0, 235.0), p3: (-322.0, 364.0)), BezierKit.CubicCurve(p0: (-322.0, 364.0), p1: (-322.0, 370.0), p2: (-323.0, 376.0), p3: (-323.0, 384.0)), BezierKit.CubicCurve(p0: (-323.0, 384.0), p1: (-323.0, 386.0), p2: (-323.0, 389.0), p3: (-323.0, 394.0)), BezierKit.CubicCurve(p0: (-323.0, 394.0), p1: (-323.0, 398.0), p2: (-323.0, 401.0), p3: (-323.0, 403.0)), BezierKit.CubicCurve(p0: (-323.0, 403.0), p1: (-323.0, 420.0), p2: (-324.0, 432.0), p3: (-324.0, 444.0)), BezierKit.CubicCurve(p0: (-324.0, 444.0), p1: (-324.0, 466.0), p2: (-307.0, 484.0), p3: (-285.0, 485.0)), BezierKit.CubicCurve(p0: (-285.0, 485.0), p1: (-263.0, 485.0), p2: (-245.0, 468.0), p3: (-244.0, 446.0))]
component 2: curves = [BezierKit.CubicCurve(p0: (-269.0, 159.0), p1: (-125.0, 146.0), p2: (30.0, 150.0), p3: (221.0, 169.0)), BezierKit.CubicCurve(p0: (221.0, 169.0), p1: (243.0, 171.0), p2: (263.0, 155.0), p3: (265.0, 133.0)), BezierKit.CubicCurve(p0: (265.0, 133.0), p1: (267.0, 111.0), p2: (251.0, 91.0), p3: (229.0, 89.0)), BezierKit.CubicCurve(p0: (229.0, 89.0), p1: (33.0, 70.0), p2: (-127.0, 66.0), p3: (-276.0, 79.0)), BezierKit.CubicCurve(p0: (-276.0, 79.0), p1: (-298.0, 81.0), p2: (-314.0, 100.0), p3: (-312.0, 122.0)), BezierKit.CubicCurve(p0: (-312.0, 122.0), p1: (-310.0, 144.0), p2: (-291.0, 161.0), p3: (-269.0, 159.0))]
component 3: curves = [BezierKit.CubicCurve(p0: (220.0, 169.0), p1: (239.0, 171.0), p2: (251.0, 173.0), p3: (259.0, 176.0)), BezierKit.CubicCurve(p0: (259.0, 176.0), p1: (261.0, 177.0), p2: (262.0, 177.0), p3: (262.0, 178.0)), BezierKit.CubicCurve(p0: (262.0, 178.0), p1: (263.0, 178.0), p2: (263.0, 178.0), p3: (263.0, 178.0)), BezierKit.CubicCurve(p0: (263.0, 178.0), p1: (262.0, 178.0), p2: (262.0, 177.0), p3: (262.0, 177.0)), BezierKit.CubicCurve(p0: (262.0, 177.0), p1: (259.0, 174.0), p2: (256.0, 169.0), p3: (256.0, 164.0)), BezierKit.CubicCurve(p0: (256.0, 164.0), p1: (257.0, 186.0), p2: (276.0, 203.0), p3: (298.0, 202.0)), BezierKit.CubicCurve(p0: (298.0, 202.0), p1: (320.0, 201.0), p2: (337.0, 182.0), p3: (336.0, 160.0)), BezierKit.CubicCurve(p0: (336.0, 160.0), p1: (335.0, 144.0), p2: (328.0, 130.0), p3: (317.0, 119.0)), BezierKit.CubicCurve(p0: (317.0, 119.0), p1: (309.0, 111.0), p2: (299.0, 106.0), p3: (287.0, 101.0)), BezierKit.CubicCurve(p0: (287.0, 101.0), p1: (272.0, 95.0), p2: (254.0, 92.0), p3: (230.0, 89.0)), BezierKit.CubicCurve(p0: (230.0, 89.0), p1: (208.0, 87.0), p2: (188.0, 103.0), p3: (185.0, 124.0)), BezierKit.CubicCurve(p0: (185.0, 124.0), p1: (183.0, 146.0), p2: (199.0, 166.0), p3: (220.0, 169.0))]
component 4: curves = [BezierKit.CubicCurve(p0: (256.0, 163.0), p1: (257.0, 210.0), p2: (257.0, 247.0), p3: (258.0, 331.0)), BezierKit.CubicCurve(p0: (258.0, 331.0), p1: (259.0, 434.0), p2: (260.0, 480.0), p3: (261.0, 537.0)), BezierKit.CubicCurve(p0: (261.0, 537.0), p1: (263.0, 613.0), p2: (266.0, 680.0), p3: (271.0, 745.0)), BezierKit.CubicCurve(p0: (271.0, 745.0), p1: (273.0, 767.0), p2: (292.0, 783.0), p3: (314.0, 782.0)), BezierKit.CubicCurve(p0: (314.0, 782.0), p1: (336.0, 780.0), p2: (352.0, 761.0), p3: (351.0, 739.0)), BezierKit.CubicCurve(p0: (351.0, 739.0), p1: (346.0, 676.0), p2: (343.0, 610.0), p3: (341.0, 535.0)), BezierKit.CubicCurve(p0: (341.0, 535.0), p1: (340.0, 478.0), p2: (339.0, 433.0), p3: (338.0, 330.0)), BezierKit.CubicCurve(p0: (338.0, 330.0), p1: (337.0, 246.0), p2: (337.0, 208.0), p3: (336.0, 161.0)), BezierKit.CubicCurve(p0: (336.0, 161.0), p1: (336.0, 139.0), p2: (317.0, 122.0), p3: (295.0, 122.0)), BezierKit.CubicCurve(p0: (295.0, 122.0), p1: (273.0, 122.0), p2: (256.0, 141.0), p3: (256.0, 163.0))]
component 5: curves = [BezierKit.CubicCurve(p0: (271.0, 747.0), p1: (270.0, 734.0), p2: (277.0, 721.0), p3: (287.0, 716.0)), BezierKit.CubicCurve(p0: (287.0, 716.0), p1: (290.0, 714.0), p2: (292.0, 713.0), p3: (294.0, 713.0)), BezierKit.CubicCurve(p0: (294.0, 713.0), p1: (294.0, 713.0), p2: (294.0, 713.0), p3: (294.0, 713.0)), BezierKit.CubicCurve(p0: (294.0, 713.0), p1: (294.0, 713.0), p2: (294.0, 713.0), p3: (294.0, 713.0)), BezierKit.CubicCurve(p0: (294.0, 713.0), p1: (272.0, 713.0), p2: (254.0, 731.0), p3: (254.0, 753.0)), BezierKit.CubicCurve(p0: (254.0, 753.0), p1: (254.0, 775.0), p2: (272.0, 793.0), p3: (294.0, 793.0)), BezierKit.CubicCurve(p0: (294.0, 793.0), p1: (306.0, 793.0), p2: (316.0, 791.0), p3: (326.0, 785.0)), BezierKit.CubicCurve(p0: (326.0, 785.0), p1: (343.0, 776.0), p2: (353.0, 758.0), p3: (351.0, 737.0)), BezierKit.CubicCurve(p0: (351.0, 737.0), p1: (348.0, 715.0), p2: (328.0, 700.0), p3: (306.0, 702.0)), BezierKit.CubicCurve(p0: (306.0, 702.0), p1: (284.0, 705.0), p2: (269.0, 725.0), p3: (271.0, 747.0))]
component 6: curves = [BezierKit.CubicCurve(p0: (292.0, 713.0), p1: (247.0, 715.0), p2: (198.0, 709.0), p3: (136.0, 697.0)), BezierKit.CubicCurve(p0: (136.0, 697.0), p1: (114.0, 692.0), p2: (93.0, 706.0), p3: (89.0, 728.0)), BezierKit.CubicCurve(p0: (89.0, 728.0), p1: (84.0, 750.0), p2: (98.0, 771.0), p3: (120.0, 775.0)), BezierKit.CubicCurve(p0: (120.0, 775.0), p1: (188.0, 789.0), p2: (243.0, 795.0), p3: (296.0, 793.0)), BezierKit.CubicCurve(p0: (296.0, 793.0), p1: (318.0, 792.0), p2: (335.0, 774.0), p3: (334.0, 751.0)), BezierKit.CubicCurve(p0: (334.0, 751.0), p1: (333.0, 729.0), p2: (315.0, 712.0), p3: (292.0, 713.0))]
component 7: curves = [BezierKit.CubicCurve(p0: (136.0, 697.0), p1: (-13.0, 667.0), p2: (-147.0, 614.0), p3: (-270.0, 539.0)), BezierKit.CubicCurve(p0: (-270.0, 539.0), p1: (-289.0, 527.0), p2: (-314.0, 533.0), p3: (-325.0, 552.0)), BezierKit.CubicCurve(p0: (-325.0, 552.0), p1: (-337.0, 571.0), p2: (-331.0, 596.0), p3: (-312.0, 607.0)), BezierKit.CubicCurve(p0: (-312.0, 607.0), p1: (-181.0, 687.0), p2: (-38.0, 743.0), p3: (120.0, 775.0)), BezierKit.CubicCurve(p0: (120.0, 775.0), p1: (142.0, 780.0), p2: (163.0, 766.0), p3: (167.0, 744.0)), BezierKit.CubicCurve(p0: (167.0, 744.0), p1: (172.0, 722.0), p2: (158.0, 701.0), p3: (136.0, 697.0))]
component 8: curves = [BezierKit.CubicCurve(p0: (-269.0, 539.0), p1: (-285.0, 530.0), p2: (-299.0, 522.0), p3: (-324.0, 510.0)), BezierKit.CubicCurve(p0: (-324.0, 510.0), p1: (-325.0, 510.0), p2: (-336.0, 505.0), p3: (-339.0, 503.0)), BezierKit.CubicCurve(p0: (-339.0, 503.0), p1: (-344.0, 501.0), p2: (-349.0, 498.0), p3: (-353.0, 496.0)), BezierKit.CubicCurve(p0: (-353.0, 496.0), p1: (-354.0, 496.0), p2: (-354.0, 496.0), p3: (-355.0, 495.0)), BezierKit.CubicCurve(p0: (-355.0, 495.0), p1: (-375.0, 485.0), p2: (-399.0, 493.0), p3: (-409.0, 513.0)), BezierKit.CubicCurve(p0: (-409.0, 513.0), p1: (-419.0, 533.0), p2: (-411.0, 557.0), p3: (-391.0, 567.0)), BezierKit.CubicCurve(p0: (-391.0, 567.0), p1: (-390.0, 567.0), p2: (-389.0, 567.0), p3: (-389.0, 568.0)), BezierKit.CubicCurve(p0: (-389.0, 568.0), p1: (-384.0, 570.0), p2: (-379.0, 573.0), p3: (-373.0, 575.0)), BezierKit.CubicCurve(p0: (-373.0, 575.0), p1: (-370.0, 577.0), p2: (-359.0, 582.0), p3: (-359.0, 582.0)), BezierKit.CubicCurve(p0: (-359.0, 582.0), p1: (-336.0, 593.0), p2: (-324.0, 599.0), p3: (-313.0, 607.0)), BezierKit.CubicCurve(p0: (-313.0, 607.0), p1: (-294.0, 619.0), p2: (-269.0, 613.0), p3: (-257.0, 595.0)), BezierKit.CubicCurve(p0: (-257.0, 595.0), p1: (-245.0, 576.0), p2: (-251.0, 551.0), p3: (-269.0, 539.0))]
component 9: curves = [BezierKit.CubicCurve(p0: (-350.0, 564.0), p1: (-347.0, 562.0), p2: (-346.0, 561.0), p3: (-344.0, 560.0)), BezierKit.CubicCurve(p0: (-344.0, 560.0), p1: (-344.0, 560.0), p2: (-343.0, 559.0), p3: (-343.0, 559.0)), BezierKit.CubicCurve(p0: (-343.0, 559.0), p1: (-342.0, 559.0), p2: (-342.0, 559.0), p3: (-342.0, 558.0)), BezierKit.CubicCurve(p0: (-342.0, 558.0), p1: (-341.0, 558.0), p2: (-341.0, 558.0), p3: (-340.0, 557.0)), BezierKit.CubicCurve(p0: (-340.0, 557.0), p1: (-339.0, 559.0), p2: (-339.0, 559.0), p3: (-326.0, 527.0)), BezierKit.CubicCurve(p0: (-326.0, 527.0), p1: (-326.0, 504.0), p2: (-344.0, 487.0), p3: (-366.0, 487.0)), BezierKit.CubicCurve(p0: (-366.0, 487.0), p1: (-388.0, 487.0), p2: (-406.0, 504.0), p3: (-406.0, 527.0)), BezierKit.CubicCurve(p0: (-406.0, 527.0), p1: (-393.0, 494.0), p2: (-393.0, 494.0), p3: (-392.0, 496.0)), BezierKit.CubicCurve(p0: (-392.0, 496.0), p1: (-391.0, 495.0), p2: (-391.0, 495.0), p3: (-391.0, 495.0)), BezierKit.CubicCurve(p0: (-391.0, 495.0), p1: (-390.0, 495.0), p2: (-390.0, 495.0), p3: (-390.0, 495.0)), BezierKit.CubicCurve(p0: (-390.0, 495.0), p1: (-390.0, 495.0), p2: (-391.0, 495.0), p3: (-391.0, 495.0)), BezierKit.CubicCurve(p0: (-391.0, 495.0), p1: (-392.0, 496.0), p2: (-394.0, 497.0), p3: (-396.0, 499.0)), BezierKit.CubicCurve(p0: (-396.0, 499.0), p1: (-414.0, 512.0), p2: (-418.0, 536.0), p3: (-405.0, 555.0)), BezierKit.CubicCurve(p0: (-405.0, 555.0), p1: (-393.0, 573.0), p2: (-368.0, 577.0), p3: (-350.0, 564.0))]
component 10: curves = [BezierKit.CubicCurve(p0: (-326.0, 552.0), p1: (-317.0, 550.0), p2: (-309.0, 546.0), p3: (-299.0, 539.0)), BezierKit.CubicCurve(p0: (-299.0, 539.0), p1: (-294.0, 536.0), p2: (-279.0, 526.0), p3: (-278.0, 525.0)), BezierKit.CubicCurve(p0: (-278.0, 525.0), p1: (-259.0, 513.0), p2: (-254.0, 488.0), p3: (-267.0, 470.0)), BezierKit.CubicCurve(p0: (-267.0, 470.0), p1: (-279.0, 451.0), p2: (-304.0, 446.0), p3: (-322.0, 459.0)), BezierKit.CubicCurve(p0: (-322.0, 459.0), p1: (-325.0, 460.0), p2: (-339.0, 470.0), p3: (-342.0, 472.0)), BezierKit.CubicCurve(p0: (-342.0, 472.0), p1: (-344.0, 473.0), p2: (-345.0, 474.0), p3: (-347.0, 475.0)), BezierKit.CubicCurve(p0: (-347.0, 475.0), p1: (-347.0, 475.0), p2: (-347.0, 475.0), p3: (-347.0, 475.0)), BezierKit.CubicCurve(p0: (-347.0, 475.0), p1: (-347.0, 475.0), p2: (-347.0, 475.0), p3: (-347.0, 475.0)), BezierKit.CubicCurve(p0: (-347.0, 475.0), p1: (-368.0, 481.0), p2: (-381.0, 502.0), p3: (-375.0, 524.0)), BezierKit.CubicCurve(p0: (-375.0, 524.0), p1: (-370.0, 545.0), p2: (-348.0, 558.0), p3: (-326.0, 552.0))]
component 11: curves = [BezierKit.CubicCurve(p0: (-278.0, 525.0), p1: (-278.0, 525.0), p2: (-227.0, 491.0), p3: (-214.0, 482.0)), BezierKit.CubicCurve(p0: (-214.0, 482.0), p1: (-190.0, 467.0), p2: (-171.0, 454.0), p3: (-153.0, 443.0)), BezierKit.CubicCurve(p0: (-153.0, 443.0), p1: (-111.0, 416.0), p2: (-76.0, 396.0), p3: (-44.0, 381.0)), BezierKit.CubicCurve(p0: (-44.0, 381.0), p1: (-24.0, 372.0), p2: (-15.0, 348.0), p3: (-25.0, 328.0)), BezierKit.CubicCurve(p0: (-25.0, 328.0), p1: (-34.0, 308.0), p2: (-58.0, 299.0), p3: (-78.0, 309.0)), BezierKit.CubicCurve(p0: (-78.0, 309.0), p1: (-113.0, 325.0), p2: (-151.0, 347.0), p3: (-196.0, 375.0)), BezierKit.CubicCurve(p0: (-196.0, 375.0), p1: (-215.0, 387.0), p2: (-234.0, 400.0), p3: (-258.0, 416.0)), BezierKit.CubicCurve(p0: (-258.0, 416.0), p1: (-272.0, 425.0), p2: (-322.0, 459.0), p3: (-322.0, 459.0)), BezierKit.CubicCurve(p0: (-322.0, 459.0), p1: (-341.0, 471.0), p2: (-346.0, 496.0), p3: (-333.0, 514.0)), BezierKit.CubicCurve(p0: (-333.0, 514.0), p1: (-321.0, 533.0), p2: (-296.0, 538.0), p3: (-278.0, 525.0))]
component 12: curves = [BezierKit.CubicCurve(p0: (-45.0, 382.0), p1: (-40.0, 380.0), p2: (-36.0, 378.0), p3: (-30.0, 375.0)), BezierKit.CubicCurve(p0: (-30.0, 375.0), p1: (-28.0, 374.0), p2: (-22.0, 372.0), p3: (-23.0, 372.0)), BezierKit.CubicCurve(p0: (-23.0, 372.0), p1: (-20.0, 371.0), p2: (-18.0, 370.0), p3: (-16.0, 369.0)), BezierKit.CubicCurve(p0: (-16.0, 369.0), p1: (-4.0, 363.0), p2: (4.0, 360.0), p3: (12.0, 356.0)), BezierKit.CubicCurve(p0: (12.0, 356.0), p1: (33.0, 346.0), p2: (51.0, 339.0), p3: (67.0, 331.0)), BezierKit.CubicCurve(p0: (67.0, 331.0), p1: (113.0, 309.0), p2: (148.0, 290.0), p3: (180.0, 270.0)), BezierKit.CubicCurve(p0: (180.0, 270.0), p1: (198.0, 257.0), p2: (204.0, 233.0), p3: (192.0, 214.0)), BezierKit.CubicCurve(p0: (192.0, 214.0), p1: (179.0, 196.0), p2: (155.0, 190.0), p3: (136.0, 202.0)), BezierKit.CubicCurve(p0: (136.0, 202.0), p1: (108.0, 221.0), p2: (75.0, 238.0), p3: (32.0, 259.0)), BezierKit.CubicCurve(p0: (32.0, 259.0), p1: (17.0, 266.0), p2: (0.0, 274.0), p3: (-20.0, 283.0)), BezierKit.CubicCurve(p0: (-20.0, 283.0), p1: (-28.0, 287.0), p2: (-37.0, 290.0), p3: (-48.0, 296.0)), BezierKit.CubicCurve(p0: (-48.0, 296.0), p1: (-50.0, 296.0), p2: (-53.0, 297.0), p3: (-55.0, 299.0)), BezierKit.CubicCurve(p0: (-55.0, 299.0), p1: (-55.0, 299.0), p2: (-61.0, 301.0), p3: (-63.0, 302.0)), BezierKit.CubicCurve(p0: (-63.0, 302.0), p1: (-69.0, 305.0), p2: (-73.0, 307.0), p3: (-77.0, 308.0)), BezierKit.CubicCurve(p0: (-77.0, 308.0), p1: (-97.0, 317.0), p2: (-107.0, 341.0), p3: (-98.0, 361.0)), BezierKit.CubicCurve(p0: (-98.0, 361.0), p1: (-89.0, 381.0), p2: (-65.0, 391.0), p3: (-45.0, 382.0))]
component 13: curves = [BezierKit.CubicCurve(p0: (180.0, 269.0), p1: (181.0, 269.0), p2: (182.0, 268.0), p3: (183.0, 267.0)), BezierKit.CubicCurve(p0: (183.0, 267.0), p1: (188.0, 264.0), p2: (194.0, 261.0), p3: (201.0, 256.0)), BezierKit.CubicCurve(p0: (201.0, 256.0), p1: (201.0, 256.0), p2: (215.0, 247.0), p3: (219.0, 245.0)), BezierKit.CubicCurve(p0: (219.0, 245.0), p1: (239.0, 233.0), p2: (253.0, 223.0), p3: (265.0, 213.0)), BezierKit.CubicCurve(p0: (265.0, 213.0), p1: (282.0, 199.0), p2: (285.0, 174.0), p3: (271.0, 157.0)), BezierKit.CubicCurve(p0: (271.0, 157.0), p1: (257.0, 140.0), p2: (232.0, 137.0), p3: (215.0, 151.0)), BezierKit.CubicCurve(p0: (215.0, 151.0), p1: (205.0, 159.0), p2: (194.0, 166.0), p3: (177.0, 177.0)), BezierKit.CubicCurve(p0: (177.0, 177.0), p1: (173.0, 179.0), p2: (159.0, 188.0), p3: (159.0, 188.0)), BezierKit.CubicCurve(p0: (159.0, 188.0), p1: (151.0, 193.0), p2: (145.0, 197.0), p3: (140.0, 200.0)), BezierKit.CubicCurve(p0: (140.0, 200.0), p1: (138.0, 201.0), p2: (137.0, 202.0), p3: (136.0, 203.0)), BezierKit.CubicCurve(p0: (136.0, 203.0), p1: (118.0, 215.0), p2: (112.0, 239.0), p3: (125.0, 258.0)), BezierKit.CubicCurve(p0: (125.0, 258.0), p1: (137.0, 276.0), p2: (161.0, 282.0), p3: (180.0, 269.0))]
component 14: curves = [BezierKit.CubicCurve(p0: (204.0, 199.0), p1: (207.0, 206.0), p2: (207.0, 207.0), p3: (209.0, 210.0)), BezierKit.CubicCurve(p0: (209.0, 210.0), p1: (220.0, 229.0), p2: (244.0, 236.0), p3: (263.0, 225.0)), BezierKit.CubicCurve(p0: (263.0, 225.0), p1: (283.0, 214.0), p2: (289.0, 189.0), p3: (278.0, 170.0)), BezierKit.CubicCurve(p0: (278.0, 170.0), p1: (279.0, 171.0), p2: (279.0, 171.0), p3: (279.0, 172.0)), BezierKit.CubicCurve(p0: (279.0, 172.0), p1: (279.0, 172.0), p2: (279.0, 172.0), p3: (279.0, 172.0)), BezierKit.CubicCurve(p0: (279.0, 172.0), p1: (279.0, 172.0), p2: (279.0, 172.0), p3: (279.0, 171.0)), BezierKit.CubicCurve(p0: (279.0, 171.0), p1: (278.0, 170.0), p2: (278.0, 168.0), p3: (277.0, 166.0)), BezierKit.CubicCurve(p0: (277.0, 166.0), p1: (268.0, 146.0), p2: (244.0, 137.0), p3: (224.0, 146.0)), BezierKit.CubicCurve(p0: (224.0, 146.0), p1: (204.0, 155.0), p2: (195.0, 178.0), p3: (204.0, 199.0))]
component 15: curves = [BezierKit.CubicCurve(p0: (-293.0, 721.0), p1: (-267.0, 762.0), p2: (-228.0, 789.0), p3: (-176.0, 803.0)), BezierKit.CubicCurve(p0: (-176.0, 803.0), p1: (-155.0, 808.0), p2: (-133.0, 796.0), p3: (-127.0, 774.0)), BezierKit.CubicCurve(p0: (-127.0, 774.0), p1: (-122.0, 753.0), p2: (-134.0, 731.0), p3: (-156.0, 725.0)), BezierKit.CubicCurve(p0: (-156.0, 725.0), p1: (-188.0, 717.0), p2: (-210.0, 701.0), p3: (-225.0, 679.0)), BezierKit.CubicCurve(p0: (-225.0, 679.0), p1: (-236.0, 660.0), p2: (-261.0, 654.0), p3: (-280.0, 666.0)), BezierKit.CubicCurve(p0: (-280.0, 666.0), p1: (-298.0, 678.0), p2: (-304.0, 702.0), p3: (-293.0, 721.0))]
component 16: curves = [BezierKit.CubicCurve(p0: (-177.0, 803.0), p1: (-133.0, 815.0), p2: (-89.0, 818.0), p3: (-33.0, 817.0)), BezierKit.CubicCurve(p0: (-33.0, 817.0), p1: (-11.0, 816.0), p2: (6.0, 798.0), p3: (6.0, 776.0)), BezierKit.CubicCurve(p0: (6.0, 776.0), p1: (5.0, 754.0), p2: (-13.0, 737.0), p3: (-35.0, 737.0)), BezierKit.CubicCurve(p0: (-35.0, 737.0), p1: (-84.0, 738.0), p2: (-120.0, 735.0), p3: (-155.0, 725.0)), BezierKit.CubicCurve(p0: (-155.0, 725.0), p1: (-177.0, 720.0), p2: (-199.0, 732.0), p3: (-205.0, 753.0)), BezierKit.CubicCurve(p0: (-205.0, 753.0), p1: (-210.0, 775.0), p2: (-198.0, 797.0), p3: (-177.0, 803.0))]
component 17: curves = [BezierKit.CubicCurve(p0: (-34.0, 817.0), p1: (-5.0, 817.0), p2: (19.0, 817.0), p3: (67.0, 818.0)), BezierKit.CubicCurve(p0: (67.0, 818.0), p1: (133.0, 819.0), p2: (160.0, 819.0), p3: (194.0, 818.0)), BezierKit.CubicCurve(p0: (194.0, 818.0), p1: (216.0, 817.0), p2: (233.0, 798.0), p3: (232.0, 776.0)), BezierKit.CubicCurve(p0: (232.0, 776.0), p1: (231.0, 754.0), p2: (212.0, 737.0), p3: (190.0, 738.0)), BezierKit.CubicCurve(p0: (190.0, 738.0), p1: (159.0, 739.0), p2: (133.0, 739.0), p3: (68.0, 738.0)), BezierKit.CubicCurve(p0: (68.0, 738.0), p1: (20.0, 737.0), p2: (-5.0, 737.0), p3: (-34.0, 737.0)), BezierKit.CubicCurve(p0: (-34.0, 737.0), p1: (-57.0, 737.0), p2: (-74.0, 755.0), p3: (-74.0, 777.0)), BezierKit.CubicCurve(p0: (-74.0, 777.0), p1: (-74.0, 800.0), p2: (-56.0, 817.0), p3: (-34.0, 817.0))]
component 18: curves = [BezierKit.CubicCurve(p0: (194.0, 818.0), p1: (229.0, 816.0), p2: (257.0, 796.0), p3: (280.0, 762.0)), BezierKit.CubicCurve(p0: (280.0, 762.0), p1: (292.0, 743.0), p2: (287.0, 718.0), p3: (269.0, 706.0)), BezierKit.CubicCurve(p0: (269.0, 706.0), p1: (250.0, 694.0), p2: (225.0, 699.0), p3: (213.0, 717.0)), BezierKit.CubicCurve(p0: (213.0, 717.0), p1: (208.0, 726.0), p2: (203.0, 731.0), p3: (198.0, 735.0)), BezierKit.CubicCurve(p0: (198.0, 735.0), p1: (195.0, 737.0), p2: (192.0, 738.0), p3: (190.0, 738.0)), BezierKit.CubicCurve(p0: (190.0, 738.0), p1: (168.0, 739.0), p2: (151.0, 758.0), p3: (152.0, 780.0)), BezierKit.CubicCurve(p0: (152.0, 780.0), p1: (153.0, 802.0), p2: (172.0, 819.0), p3: (194.0, 818.0))]

Resulting Path:

component 0: curves = [BezierKit.CubicCurve(p0: (-242.7048464180748, 555.1771373807451), p1: (-243.42071156036235, 538.3183050343076), p2: (-243.85245293148537, 520.7631006313234), p3: (-244.04990918035318, 502.30220184637574)), BezierKit.CubicCurve(p0: (-244.04990918035318, 502.30220184637574), p1: (-231.7251494835195, 494.0386451098102), p2: (-219.4828559441881, 485.7958233459765), p3: (-214.0, 482.0)), BezierKit.CubicCurve(p0: (-214.0, 482.0), p1: (-190.0, 467.0), p2: (-171.0, 454.0), p3: (-153.0, 443.0)), BezierKit.CubicCurve(p0: (-153.0, 443.0), p1: (-113.57235196659968, 417.6536548356712), p2: (-80.31351119290451, 398.4761169310476), p3: (-49.91434078882551, 383.81281784108467)), BezierKit.CubicCurve(p0: (-49.91434078882551, 383.81281784108467), p1: (-48.256762379039984, 383.32985915538274), p2: (-46.61533465921372, 382.7269005966463), p3: (-45.0, 382.0)), BezierKit.CubicCurve(p0: (-45.0, 382.0), p1: (-40.0, 380.0), p2: (-36.0, 378.0), p3: (-30.0, 375.0)), BezierKit.CubicCurve(p0: (-30.0, 375.0), p1: (-28.0, 374.0), p2: (-22.0, 372.0), p3: (-23.0, 372.0)), BezierKit.CubicCurve(p0: (-23.0, 372.0), p1: (-20.0, 371.0), p2: (-18.0, 370.0), p3: (-16.0, 369.0)), BezierKit.CubicCurve(p0: (-16.0, 369.0), p1: (-4.0, 363.0), p2: (4.0, 360.0), p3: (12.0, 356.0)), BezierKit.CubicCurve(p0: (12.0, 356.0), p1: (33.0, 346.0), p2: (51.0, 339.0), p3: (67.0, 331.0)), BezierKit.CubicCurve(p0: (67.0, 331.0), p1: (113.0, 309.0), p2: (148.0, 290.0), p3: (180.0, 270.0)), BezierKit.CubicCurve(p0: (180.0, 270.0), p1: (181.77901447807565, 268.71515621027874), p2: (183.44081071492155, 267.32286236609684), p3: (184.97959612864216, 265.8385653525094)), BezierKit.CubicCurve(p0: (184.97959612864216, 265.8385653525094), p1: (189.5598615691778, 263.1952583846162), p2: (194.9002705922668, 260.3569495769522), p3: (201.0, 256.0)), BezierKit.CubicCurve(p0: (201.0, 256.0), p1: (201.0, 256.0), p2: (215.0, 247.0), p3: (219.0, 245.0)), BezierKit.CubicCurve(p0: (219.0, 245.0), p1: (227.77031876233895, 239.73780874259666), p2: (235.38686015678235, 234.86020994115842), p3: (242.18692402657078, 230.19855367406512)), BezierKit.CubicCurve(p0: (242.18692402657078, 230.19855367406512), p1: (247.1199497303487, 230.38482208680261), p2: (252.12642416612897, 229.62942053258706), p3: (256.94790961428265, 227.84807612189257)), BezierKit.CubicCurve(p0: (256.94790961428265, 227.84807612189257), p1: (257.2164610185148, 254.60244271605063), p2: (257.46525371413315, 286.08131198718957), p3: (258.0, 331.0)), BezierKit.CubicCurve(p0: (258.0, 331.0), p1: (259.0, 434.0), p2: (260.0, 480.0), p3: (261.0, 537.0)), BezierKit.CubicCurve(p0: (261.0, 537.0), p1: (262.59814157455685, 597.7293798331593), p2: (264.8347972721954, 652.712132558583), p3: (268.220185075935, 705.5197840574061)), BezierKit.CubicCurve(p0: (268.220185075935, 705.5197840574061), p1: (252.0608377575346, 695.7882085749465), p2: (231.8173428516103, 698.1111664964208), p3: (218.88881500271162, 710.0882602449426)), BezierKit.CubicCurve(p0: (218.88881500271162, 710.0882602449426), p1: (193.90439733744492, 707.3506076383935), p2: (166.89175102688512, 702.9638948660113), p3: (136.8185833666551, 697.1582323624708)), BezierKit.CubicCurve(p0: (136.8185833666551, 697.1582323624708), p1: (136.54717435829534, 697.102353196791), p2: (136.27420858699207, 697.0498561067259), p3: (136.0, 697.0)), BezierKit.CubicCurve(p0: (136.0, 697.0), p1: (135.35570722014236, 696.8535698227596), p2: (134.712272112157, 696.7234354110924), p3: (134.07009656098558, 696.6093204691006)), BezierKit.CubicCurve(p0: (134.07009656098558, 696.6093204691006), p1: (-3.2178229499304254, 668.7608541191655), p2: (-127.7637593278102, 621.3551034330992), p3: (-242.7048464180748, 555.1771373807451))]
component 1: curves = [BezierKit.CubicCurve(p0: (-243.0012122122419, 405.9677785469315), p1: (-243.00040959717086, 405.64640343661983), p2: (-243.0, 405.32381611406606), p3: (-243.0, 405.0)), BezierKit.CubicCurve(p0: (-243.0, 405.0), p1: (-243.0, 402.0), p2: (-243.0, 399.0), p3: (-243.0, 395.0)), BezierKit.CubicCurve(p0: (-243.0, 395.0), p1: (-243.0, 390.0), p2: (-243.0, 388.0), p3: (-243.0, 385.0)), BezierKit.CubicCurve(p0: (-243.0, 385.0), p1: (-243.0, 378.0), p2: (-242.0, 372.0), p3: (-242.0, 366.0)), BezierKit.CubicCurve(p0: (-242.0, 366.0), p1: (-240.38503852802143, 263.4499465293619), p2: (-238.11805191704912, 204.5855773713073), p3: (-234.6725424279648, 156.23753496138895)), BezierKit.CubicCurve(p0: (-234.6725424279648, 156.23753496138895), p1: (-107.38556967896513, 147.22684143468666), p2: (29.777193971488916, 151.0362669928679), p3: (193.41884264277, 166.3376661785078)), BezierKit.CubicCurve(p0: (193.41884264277, 166.3376661785078), p1: (188.58015663424897, 169.52254272978132), p2: (183.18750239165857, 172.996321981868), p3: (177.0, 177.0)), BezierKit.CubicCurve(p0: (177.0, 177.0), p1: (173.0, 179.0), p2: (159.0, 188.0), p3: (159.0, 188.0)), BezierKit.CubicCurve(p0: (159.0, 188.0), p1: (151.81284014712202, 192.49197490804877), p2: (146.23990738020717, 196.17683627311592), p3: (141.55609494265158, 199.0545840952015)), BezierKit.CubicCurve(p0: (141.55609494265158, 199.0545840952015), p1: (139.66820011659203, 199.87554383038707), p2: (137.81137783083045, 200.85597189631756), p3: (136.0, 202.0)), BezierKit.CubicCurve(p0: (136.0, 202.0), p1: (108.0, 221.0), p2: (75.0, 238.0), p3: (32.0, 259.0)), BezierKit.CubicCurve(p0: (32.0, 259.0), p1: (17.0, 266.0), p2: (0.0, 274.0), p3: (-20.0, 283.0)), BezierKit.CubicCurve(p0: (-20.0, 283.0), p1: (-28.0, 287.0), p2: (-37.0, 290.0), p3: (-48.0, 296.0)), BezierKit.CubicCurve(p0: (-48.0, 296.0), p1: (-50.0, 296.0), p2: (-53.0, 297.0), p3: (-55.0, 299.0)), BezierKit.CubicCurve(p0: (-55.0, 299.0), p1: (-55.0, 299.0), p2: (-61.0, 301.0), p3: (-63.0, 302.0)), BezierKit.CubicCurve(p0: (-63.0, 302.0), p1: (-69.0, 305.0), p2: (-73.0, 307.0), p3: (-77.0, 308.0)), BezierKit.CubicCurve(p0: (-77.0, 308.0), p1: (-79.07686283880575, 308.9345882774626), p2: (-81.04589169633121, 310.0309275268457), p3: (-82.89700853509468, 311.2677418912434)), BezierKit.CubicCurve(p0: (-82.89700853509468, 311.2677418912434), p1: (-116.54494896847837, 327.0565396874017), p2: (-153.09004202674086, 348.3004705944165), p3: (-196.0, 375.0)), BezierKit.CubicCurve(p0: (-196.0, 375.0), p1: (-210.86839285020864, 384.3905639053949), p2: (-225.73678570041727, 394.39350760566003), p3: (-243.0012122122419, 405.9677785469315))]

Super thanks for your support 🙏

droots can have low precision in some cases, which could affect Path.contains(_ point:)

Here is an example where droots gets low precision in 0.11.0

The unit test that demonstrates this issue was removed because it is somewhat low priority and represents a longstanding issue.

 func testDrootsCubicWorldIssue4() {
        // this test was failing due to round-off it seems
        let roots = drootsCubicTestHelper(117.11706850363589,
                                          39.0399142482629,
                                          -2.3525217329734005e-06,
                                          -2.352663614146877e-06)
        
        
        let filtered = roots.filter { $0 >= 0 && $0 <= 1 }
        XCTAssertEqual(filtered.count, 1)
        XCTAssertEqual(filtered.first!, CGFloat(0.999858), accuracy: 1.0e-5)
    }

The answer is gets for the root is 0.9997828474060952 which is incorrect in the 4th digit.

There was another related unit test that failed for the same reason:

    func testCrossingsRemovedFifthRealWorldCase() {
        // this test fails for the same reason that
        // `testDrootsCubicWorldIssue4` fails
        // the path doesn't have any self-intersections
        // but the `contains` call gives the wrong results
        let cgPath = CGMutablePath()
        cgPath.move(to: CGPoint(x: -45.58408505173276, y: 4384.210079234615))
        cgPath.addCurve(to: CGPoint(x: 519.756427723393, y: 4384.14765017776),
                        control1: CGPoint(x: 110.51314747385496, y: 4228.07836809298),
                        control2: CGPoint(x: 363.6247165817579, y: 4228.050417652172))
        cgPath.addCurve(to: CGPoint(x: 519.8188567802481, y: 4949.488162952885),
                        control1: CGPoint(x: 675.8881388650282, y: 4540.244882703348),
                        control2: CGPoint(x: 675.9160893058358, y: 4793.356451811251))
        cgPath.addCurve(to: CGPoint(x: 501.53832994006814, y: 4967.769608589276),
                        control1: CGPoint(x: 513.7155041887056, y: 4955.5928615872745),
                        control2: CGPoint(x: 507.6055609674461, y: 4961.702072477192))
        cgPath.addCurve(to: CGPoint(x: -63.802186103924214, y: 4967.783798079717),
                        control1: CGPoint(x: 345.4277755233733, y: 5123.887999645149),
                        control2: CGPoint(x: 92.31620495194895, y: 5123.894352496412))
        cgPath.addCurve(to: CGPoint(x: -63.81637559436501, y: 4402.443282035724),
                        control1: CGPoint(x: -219.92057715979726, y: 4811.673243663022),
                        control2: CGPoint(x: -219.9269300110598, y: 4558.561673091597))
        cgPath.addCurve(to: CGPoint(x: -45.58408505173276, y: 4384.210079234615),
                        control1: CGPoint(x: -57.725037285805456, y: 4396.351638460308),
                        control2: CGPoint(x: -51.63917972581493, y: 4390.2665134127255))
        let path = Path(cgPath: cgPath)
        let result = path.crossingsRemoved()
//      seems to fail because we get the wrnog results here:
//       we get the right results with 1.0e-5
//        let curve = path.components[0].curves[0]
//        let point = curve.point(at: 0.5) - 1.0e-7 * curve.normal(at: 0.5)
//        XCTAssertFalse(path.contains(point))
        XCTAssertEqual(path, result)
    }

simplifying a curve?

Suppose I delete the marked point on the glyph below. Currently I get the glyph in the second pic, which is not wrong. But it would be more useful to get something closer to the glyph in the third pic (where the remaining Bézier handles have been adjusted to approximate the curve that existed before.).

The question: is there a way to accomplish this in BezierKit? I’m not sure how to characterize this operation. I suppose it would be a sort of curve simplification, where the remaining BCPs are adjusted to produce the curve that’s the closest fit to the one that was there before.

Screen Shot 2020-09-04 at Sep 04  4 43 09 PM

Screen Shot 2020-09-04 at Sep 04  4 43 14 PM

Screen Shot 2020-09-04 at Sep 04  4 43 33 PM

Proposal: Use Sylvester Matrix for finding the intersection of cubic beziers

I want to suggest an improvement of intersection finding algorithm - I have a working solution in C++ and ObjC based on this paper: https://mat.polsl.pl/sjpam/zeszyty/z6/Silesian_J_Pure_Appl_Math_v6_i1_str_155-176.pdf which can be ported to swift.
The solution uses Jenkins-Traub polynomial (9) solving method by Chris Sweeny which requires complex number math.

Question before I start making the PR: do authors of this library consider adding either Eigen C++ template library or swift-numerics library as a dependency (to have complex number math in place)?

Path and PathComponent hash and hashValue is simply memory address

this means that Swift classes that depend on Hashable such as Set will fail to locate instances that are identical but do not share the same address, such as independent copies.

A good unit test of this behavior is to make a Path, add it to a Set, make a copy using Path.copy(using: CGAffineTransform.identity) and see if Set.contains() still locates it.

`union` with nonzero winding rule

Suppose I’d like to reduce a list of Path objects into a single Path with overlaps removed, but winding preserved.

In example A, the crossbar overlaps the sides. If I convert this to an array of Path and reduce into a new Path using union, I get the right result.

In example Q, however, the same approach doesn’t work: union will remove the overlap correctly from the Q tail, but it will also remove the inner contour entirely.

  1. Is there a way to invoke union so that it takes account of nonzero winding?
  2. Is there a way to test the winding count of a Path? (I see there is an internal windingCount property)
  3. Is there a way to combine two Path objects into a new Path without applying any Boolean processing?

Screen Shot 2020-08-05 at Aug 05  10 56 12 AM

Screen Shot 2020-08-05 at Aug 05  11 20 53 AM

Union of 2 overlapping paths returns an empty path

Let consider the 2 following UIBezierPath:

let p1 = UIBezierPath()
p1.move(to: CGPoint(x: 160.30770651563628, y: 827.7553004653367))
p1.addCurve(to: CGPoint(x: 110.26942663248718, y: 568.5268524837642), controlPoint1: CGPoint(x: 181.33161356383755, y: 737.7466984152248), controlPoint2: CGPoint(x: 164.05060972624904, y: 653.3356412085424))
p1.addCurve(to: CGPoint(x: 68.86790851824688, y: 559.2578558910238), controlPoint1: CGPoint(x: 101.39627582752009, y: 554.534576214393), controlPoint2: CGPoint(x: 82.86018478761805, y: 550.3847050860568))
p1.addCurve(to: CGPoint(x: 59.59891192550654, y: 600.6593740052641), controlPoint1: CGPoint(x: 54.875632248875704, y: 568.1310066959909), controlPoint2: CGPoint(x: 50.725761120539445, y: 586.667097735893))
p1.addCurve(to: CGPoint(x: 101.88038125865839, y: 814.1080420111522), controlPoint1: CGPoint(x: 105.11513351206828, y: 672.4349541994575), controlPoint2: CGPoint(x: 119.06082214735332, y: 740.5542794564268))
p1.addCurve(to: CGPoint(x: 124.27041466005505, y: 850.1453338667334), controlPoint1: CGPoint(x: 98.11179489803564, y: 830.2423023675684), controlPoint2: CGPoint(x: 108.13615430363883, y: 846.3767475061106))
p1.addCurve(to: CGPoint(x: 160.30770651563628, y: 827.7553004653367), controlPoint1: CGPoint(x: 140.40467501647126, y: 853.9139202273561), controlPoint2: CGPoint(x: 156.53912015501353, y: 843.889560821753))

and

let p2 = UIBezierPath()
p2.move(to: CGPoint(x: 110.01560660410875, y: 568.1334199999095))
p2.addCurve(to: CGPoint(x: -34.46691786355873, y: 426.72807843882487), controlPoint1: CGPoint(x: 64.94829801467844, y: 499.45942595887277), controlPoint2: CGPoint(x: 20.78869552632778, y: 454.67344613026177))
p2.addCurve(to: CGPoint(x: -69.07061390857123, y: 474.860094579322), controlPoint1: CGPoint(x: -65.01464367993249, y: 411.2786538880151), controlPoint2: CGPoint(x: -93.44515961076199, y: 450.82408423410607))
p2.addLine(to: CGPoint(x: 63.869824962902115, y: 605.9541384664693))
p2.addCurve(to: CGPoint(x: 110.01560660410875, y: 568.1334199999095), controlPoint1: CGPoint(x: 89.4862743247028, y: 631.2148038093562), controlPoint2: CGPoint(x: 129.75430800678396, y: 598.2114411849384))

If we create 2 Paths and perform a union,

let path1 = Path(cgPath: p1.cgPath)
let path2 = Path(cgPath: p2.cgPath)
let final = path1.union(path2)

the final Path is Empty while the 2 Paths are overlapping.

how to quiet the SwiftLint warning?

Screen Shot 2021-01-20 at Jan 20  3 35 09 PM

Since I upgraded to the current version of BezierKit I’m getting this warning. I installed SwiftLint as a package and added it to the BezierKit target as shown below, but the warning persists.

What else is to be done?

Screen Shot 2021-01-20 at Jan 20  3 36 01 PM

`crossingsRemoved` makes these two contours disappear

Text:

path.components.map { $0.curves } =
[[BezierKit.CubicCurve(p0: (28.0, 14.0), p1: (32.0, 21.0), p2: (33.0, 42.0), p3: (30.0, 53.0)), BezierKit.CubicCurve(p0: (30.0, 53.0), p1: (19.0, 32.0), p2: (22.0, 26.0), p3: (28.0, 14.0))], [BezierKit.CubicCurve(p0: (42.0, 38.0), p1: (42.0, 53.0), p2: (36.0, 53.0), p3: (27.0, 53.0)), BezierKit.CubicCurve(p0: (27.0, 53.0), p1: (19.0, 40.0), p2: (29.0, 32.0), p3: (42.0, 38.0))]]
path.crossingsRemoved().components.map { $0.curves } =
[]

Visual:

Screen Shot 2021-01-17 at 11 19 20 AM

`offset` seems to treat straight & curved segments differently

All curves

Here’s an example of a capital O showing what happens when offset(10) is applied: each on-curve point is shifted in the offset direction by 10 units.

Screen Shot 2020-12-17 at Dec 17  10 39 30 AM

Screen Shot 2020-12-17 at Dec 17  10 39 36 AM

All straights

Here’s an example of the same offset(10) applied to a capital H. In this case, the points are only moving 5 units from their original positions:

Screen Shot 2020-12-17 at Dec 17  10 39 16 AM

Screen Shot 2020-12-17 at Dec 17  10 39 20 AM

Mix of curves and straights

Now here’s a capital D with offset(10). Whereas the O was only made of curves, and the H only made of straights, this combines both. The result is that the straight segments are moving only half the distance, so certain segments that should be parallel are not. For instance, the top and bottom edges are no longer horizontal.

Screen Shot 2020-12-17 at Dec 17  10 37 24 AM

Screen Shot 2020-12-17 at Dec 17  10 37 30 AM

Hard to work with ends of simple, unclosed bezier path

I very much like what you are doing with BezierKit and would like to use it for a new project. I plan to draw graphs (boxes connected by lines). The path between boxes will be connected by polylines (converted to BezierKit Paths) and I want to add arrowheads where the path intersects with the box at either end. This seems like it ought to be easy:

  1. Create the polyline as a path going from the center of one box to the center of the next.
  2. Create paths for the boxes at the head and tail of the line.
  3. Subtract the paths of the boxes from the line, removing the part of the line where it intersects the boxes.
  4. Create paths for arrowheads at the ends of the line path, using the derivative of the path at its tail and head for angles for the arrowheads.
  5. Outline the path and the paths for the arrowheads
  6. Perform a union on the path and the arrowhead paths so they can be drawn or manipulated further as a single graphics entity.

Unfortunately, I run into problems at step 3, as the boolean operations only seem to work on closed paths, and the polyline is, in its simplest case a single straight line segment— it might be composed of many segments or other bezier curves, but it is not a closed path. If I first outline the path, the subtraction works as expected, but then I can't simply compute the locations of the path's ends and derivatives to know where to place the arrowheads. I can compute the intersections of the box paths with the line paths, but there doesn't appear to be any way to simply use the PathIntersection or IndexedPathLocation objects to split the path at the intersection points.

As I said, this seems like it ought to be easy. Am I missing something?

Impossible to make closed PathComponent

Hi, maybe this is not a issue, but I don't know make closed PathComponent. I have var curves : [CubicCurve] array where last.endingPoint == first.startPoint. I expect to get closed PathComponent, form PathComponent(curves: curves) but I receive "curves are not contiguous." error.

Newton iteration may fail to converge, producing wrong results in curve.project()

this is a longstanding issue, here is a unit test demonstrating it:

    func testDegree5RealWorldIssue() {
        // this case may return a non-root because Newton iteration
        // does not converge
        let polynomial = BernsteinPolynomial5(b0: -68686.64586343056,
                                              b1: 102389.02112160496,
                                              b2: -163207.59913132348,
                                              b3: 177077.4933777841,
                                              b4: -108411.70135107233,
                                              b5: 57838.81668210728)
        let roots = findDistinctRootsInUnitInterval(of: polynomial)
        XCTAssertEqual(roots.count, 1)
        XCTAssertEqual(roots[0], 0.44454, accuracy: 1.0e-5)
    }

`crossingsRemoved` gets confused by coincident points

Without detracting from your success with #81, I discovered a case that still fails with that patch:

path.components.map { $0.curves } =
[[BezierKit.LineSegment(p0: (100.0, 585.0), p1: (100.0, 585.0)), BezierKit.LineSegment(p0: (100.0, 585.0), p1: (225.0, 585.0)), BezierKit.LineSegment(p0: (225.0, 585.0), p1: (100.0, 680.0)), BezierKit.LineSegment(p0: (100.0, 680.0), p1: (100.0, 585.0))], [BezierKit.LineSegment(p0: (260.0, 392.0), p1: (260.0, 585.0)), BezierKit.LineSegment(p0: (260.0, 585.0), p1: (160.0, 680.0)), BezierKit.LineSegment(p0: (160.0, 680.0), p1: (260.0, 392.0))]]
path.crossingsRemoved().components.map { $0.curves } =
[]

Looks like:

Screen Shot 2021-01-21 at Jan 21  12 30 48 PM

The bugaboo is that we have two points sitting atop each other at (100,585). There is no value of the discriminant that will cure this problem (so it deserves to be considered a distinct issue, I think).

In this case the “crossing” has zero area, so the “removal” (based on behavior of other crossing-removal algorithms I’ve used) should mean that the coincident points are collapsed into a single point.

For that matter, there doesn’t even need to be an actual geometric overlap for the operation to fail. A simpler example:

path.components.map { $0.curves } =
[[BezierKit.LineSegment(p0: (290.0, 467.0), p1: (290.0, 467.0)), BezierKit.LineSegment(p0: (290.0, 467.0), p1: (394.0, 467.0)), BezierKit.LineSegment(p0: (394.0, 467.0), p1: (290.0, 579.0)), BezierKit.LineSegment(p0: (290.0, 579.0), p1: (290.0, 467.0))]]
path.crossingsRemoved().components.map { $0.curves } =
[]

Screen Shot 2021-01-21 at Jan 21  12 44 53 PM

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.