From a9f9eeb717f173a6bb9470c3003cd574e805e8ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Go=CC=81mez?= Date: Mon, 10 Jan 2022 15:29:43 +0100 Subject: [PATCH 01/63] First iteration to make the current code compatible with web assembly. Even when these changes doesn't let you use all the library from WASM, these are the mimimum amount of changes needed to make it fully compatible in the future --- BezierKit/BezierKitTests/LockTests.swift | 2 + BezierKit/BezierKitTests/Path+DataTests.swift | 8 +- .../Path+VectorBooleanTests.swift | 3 + BezierKit/BezierKitTests/PathTests.swift | 2 + .../BezierKitTests/PerformanceTests.swift | 2 + .../BezierKitTests/TransformableTests.swift | 2 + BezierKit/Library/Path+Data.swift | 3 + BezierKit/Library/Path+Projection.swift | 26 +++- BezierKit/Library/Path+VectorBoolean.swift | 51 ++++++- BezierKit/Library/Path.swift | 139 ++++++++++++++++-- BezierKit/Library/PathComponent.swift | 41 +++++- 11 files changed, 252 insertions(+), 27 deletions(-) diff --git a/BezierKit/BezierKitTests/LockTests.swift b/BezierKit/BezierKitTests/LockTests.swift index cb013c2c..04eefda6 100644 --- a/BezierKit/BezierKitTests/LockTests.swift +++ b/BezierKit/BezierKitTests/LockTests.swift @@ -9,6 +9,7 @@ import XCTest @testable import BezierKit +#if !os(WASI) class LockTests: XCTestCase { func testPathPropertyAtomicity() { // ensure that lazy properties of Path are only initialized once @@ -53,3 +54,4 @@ class LockTests: XCTestCase { XCTAssertTrue(boundingBoxes.values.allSatisfy { $0 == expectedBoundingBox }) } } +#endif \ No newline at end of file diff --git a/BezierKit/BezierKitTests/Path+DataTests.swift b/BezierKit/BezierKitTests/Path+DataTests.swift index 005019dc..8d3e5486 100644 --- a/BezierKit/BezierKitTests/Path+DataTests.swift +++ b/BezierKit/BezierKitTests/Path+DataTests.swift @@ -195,18 +195,21 @@ class PathDataTests: XCTestCase { } #endif - + + #if !os(WASI) func testEmptyData() { let path = Path(data: Data()) XCTAssertEqual(path, nil) } + #endif let simpleRectangle = { Path(rect: CGRect(x: 1, y: 2, width: 3, height: 4)) }() let expectedSimpleRectangleData = Data(base64Encoded: "JbPlSAUAAAAAAQEBAQAAAAAAAPA/AAAAAAAAAEAAAAAAAAAQQAAAAAAAAABAAAAAAAAAEEAAAAAAAAAYQAAAAAAAAPA/AAAAAAAAGEAAAAAAAADwPwAAAAAAAABA")! - + + #if !os(WASI) func testSimpleRectangle() { XCTAssertEqual(simpleRectangle.data, expectedSimpleRectangleData) } @@ -236,4 +239,5 @@ class PathDataTests: XCTestCase { let corruptData6 = data[0..<10] // commands cut off XCTAssertEqual(Path(data: corruptData6), nil) } + #endif } diff --git a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift index c77433da..e63263fc 100644 --- a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift +++ b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift @@ -9,6 +9,7 @@ @testable import BezierKit import XCTest +#if !os(WASI) private extension Path { /// copies the path in such a way that it's impossible that optimizations would allow the copy to share the same underlying storage func independentCopy() -> Path { @@ -790,3 +791,5 @@ class PathVectorBooleanTests: XCTestCase { #endif } + +#endif \ No newline at end of file diff --git a/BezierKit/BezierKitTests/PathTests.swift b/BezierKit/BezierKitTests/PathTests.swift index a7a769e2..a5072db2 100644 --- a/BezierKit/BezierKitTests/PathTests.swift +++ b/BezierKit/BezierKitTests/PathTests.swift @@ -934,6 +934,7 @@ class PathTests: XCTestCase { XCTAssertEqual(path2.boundingBoxOfPath, BoundingBox(p1: CGPoint(x: 1, y: 0), p2: CGPoint(x: 3, y: 4))) } + #if !os(WASI) func testNSCoder() { // just some random curves, but we ensure they're continuous let l1 = LineSegment(p0: CGPoint(x: 4.9652, y: 8.2774), @@ -963,6 +964,7 @@ class PathTests: XCTestCase { } XCTAssertEqual(path, decodedPath) } + #endif func testIndexedPathLocation() { let location1 = IndexedPathLocation(componentIndex: 0, elementIndex: 1, t: 0.5) diff --git a/BezierKit/BezierKitTests/PerformanceTests.swift b/BezierKit/BezierKitTests/PerformanceTests.swift index b11045d2..f44ebf76 100644 --- a/BezierKit/BezierKitTests/PerformanceTests.swift +++ b/BezierKit/BezierKitTests/PerformanceTests.swift @@ -9,6 +9,7 @@ import BezierKit import XCTest +#if !os(WASI) private extension PerformanceTests { func generateRandomCurves(count: Int, selfIntersect: Bool? = nil, reseed: Int? = nil) -> [CubicCurve] { @@ -229,3 +230,4 @@ class PerformanceTests: XCTestCase { #endif } +#endif \ No newline at end of file diff --git a/BezierKit/BezierKitTests/TransformableTests.swift b/BezierKit/BezierKitTests/TransformableTests.swift index bce2c7d1..458c958d 100644 --- a/BezierKit/BezierKitTests/TransformableTests.swift +++ b/BezierKit/BezierKitTests/TransformableTests.swift @@ -9,6 +9,7 @@ import XCTest @testable import BezierKit +#if canImport(CoreGraphics) class TransformableTests: XCTestCase { // rotates by 90 degrees ccw and then shifts (-1, 1) @@ -82,3 +83,4 @@ class TransformableTests: XCTestCase { } } +#endif \ No newline at end of file diff --git a/BezierKit/Library/Path+Data.swift b/BezierKit/Library/Path+Data.swift index dac8e618..8ee329ac 100644 --- a/BezierKit/Library/Path+Data.swift +++ b/BezierKit/Library/Path+Data.swift @@ -6,6 +6,8 @@ // Copyright © 2019 Holmes Futrell. All rights reserved. // +#if !os(WASI) + import Foundation #if canImport(CoreGraphics) import CoreGraphics @@ -146,3 +148,4 @@ private struct SerializationTypes { return result } } +#endif diff --git a/BezierKit/Library/Path+Projection.swift b/BezierKit/Library/Path+Projection.swift index 90f601e0..6347a78f 100644 --- a/BezierKit/Library/Path+Projection.swift +++ b/BezierKit/Library/Path+Projection.swift @@ -47,7 +47,18 @@ public extension Path { func project(_ point: CGPoint) -> (point: CGPoint, location: IndexedPathLocation)? { return self.searchForClosestLocation(to: point, maximumDistance: .infinity, requireBest: true) } - @objc(point:isWithinDistanceOfBoundary:) func pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { + + #if os(WASI) + func pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { + return _pointIsWithinDistanceOfBoundary(point, distance: distance) + } + #else + @objc(point:isWithinDistanceOfBoundary:) func pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { + return _pointIsWithinDistanceOfBoundary(point, distance: distance) + } + #endif + + fileprivate func _pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { return self.searchForClosestLocation(to: point, maximumDistance: distance, requireBest: false) != nil } } @@ -104,7 +115,18 @@ public extension PathComponent { } return result } - @objc(point:isWithinDistanceOfBoundary:) func pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { + + #if os(WASI) + func pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { + return _pointIsWithinDistanceOfBoundary(point, distance: distance) + } + #else + @objc(point:isWithinDistanceOfBoundary:) func pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { + return _pointIsWithinDistanceOfBoundary(point, distance: distance) + } + #endif + + fileprivate func _pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { return self.searchForClosestLocation(to: point, maximumDistance: distance, requireBest: false) != nil } } diff --git a/BezierKit/Library/Path+VectorBoolean.swift b/BezierKit/Library/Path+VectorBoolean.swift index 8b4869e4..49080b35 100644 --- a/BezierKit/Library/Path+VectorBoolean.swift +++ b/BezierKit/Library/Path+VectorBoolean.swift @@ -12,12 +12,32 @@ import CoreGraphics import Foundation public extension Path { + + #if os(WASI) + func subtract(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + return _subtract(other, accuracy: accuracy) + } + #else + @objc(subtractPath:accuracy:) func subtract(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + return _subtract(other, accuracy: accuracy) + } + #endif - @objc(subtractPath:accuracy:) func subtract(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + fileprivate func _subtract(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { return self.performBooleanOperation(.subtract, with: other.reversed(), accuracy: accuracy) } + + #if os(WASI) + func `union`(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + return _union(other, accuracy: accuracy) + } + #else + @objc(unionPath:accuracy:) func `union`(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + return _union(other, accuracy: accuracy) + } + #endif - @objc(unionPath:accuracy:) func `union`(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + fileprivate func `_union`(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { guard self.isEmpty == false else { return other } @@ -27,16 +47,35 @@ public extension Path { return self.performBooleanOperation(.union, with: other, accuracy: accuracy) } - @objc(intersectPath:accuracy:) func intersect(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + #if os(WASI) + func intersect(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + return _intersect(other, accuracy: accuracy) + } + #else + @objc(intersectPath:accuracy:) func intersect(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + return _intersect(other, accuracy: accuracy) + } + #endif + + fileprivate func _intersect(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { return self.performBooleanOperation(.intersect, with: other, accuracy: accuracy) } - - @objc(crossingsRemovedWithAccuracy:) func crossingsRemoved(accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + + #if os(WASI) + func crossingsRemoved(accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + return _crossingsRemoved(accuracy: accuracy) + } + #else + @objc(crossingsRemovedWithAccuracy:) func crossingsRemoved(accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + return _crossingsRemoved(accuracy: accuracy) + } + #endif + + fileprivate func _crossingsRemoved(accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { let intersections = self.selfIntersections(accuracy: accuracy) let augmentedGraph = AugmentedGraph(path1: self, path2: self, intersections: intersections, operation: .removeCrossings) return augmentedGraph.performOperation() } - } private extension Path { diff --git a/BezierKit/Library/Path.swift b/BezierKit/Library/Path.swift index 526f7a1b..012184f1 100644 --- a/BezierKit/Library/Path.swift +++ b/BezierKit/Library/Path.swift @@ -20,9 +20,15 @@ private extension Array { } } -@objc(BezierKitPathFillRule) public enum PathFillRule: NSInteger { - case winding=0, evenOdd -} +#if os(WASI) + public enum PathFillRule: NSInteger { + case winding=0, evenOdd + } +#else + @objc(BezierKitPathFillRule) public enum PathFillRule: NSInteger { + case winding=0, evenOdd + } +#endif internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillRule) -> Bool { switch rule { @@ -33,7 +39,7 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR } } -@objc(BezierKitPath) open class Path: NSObject, NSSecureCoding { +open class Path: NSObject, NSSecureCoding { /// lock to make external accessing of lazy vars threadsafe private let lock = UnfairLock() @@ -77,7 +83,17 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR }() #endif - @objc public var isEmpty: Bool { + #if os(WASI) + public var isEmpty: Bool { + return _isEmpty + } + #else + @objc public var isEmpty: Bool { + return _isEmpty + } + #endif + + fileprivate var _isEmpty: Bool { return self.components.isEmpty // components are not allowed to be empty } @@ -104,9 +120,23 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR private var _hash: Int? + #if os(WASI) + public let components: [PathComponent] + #else @objc public let components: [PathComponent] + #endif + #if os(WASI) + public func selfIntersects(accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + return _selfIntersects(accuracy: accuracy) + } + #else @objc(selfIntersectsWithAccuracy:) public func selfIntersects(accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + return _selfIntersects(accuracy: accuracy) + } + #endif + + fileprivate func _selfIntersects(accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { return !self.selfIntersections(accuracy: accuracy).isEmpty } @@ -127,7 +157,17 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR return intersections } + #if os(WASI) + public func intersects(_ other: Path, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + return _intersects(other, accuracy: accuracy) + } + #else @objc(intersectsPath:accuracy:) public func intersects(_ other: Path, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + return _intersects(other, accuracy: accuracy) + } + #endif + + fileprivate func _intersects(_ other: Path, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { return !self.intersections(with: other, accuracy: accuracy).isEmpty } @@ -150,13 +190,23 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR return intersections } - @objc public convenience override init() { + #if os(WASI) + public convenience override init() { self.init(components: []) } + required public init(components: [PathComponent]) { + self.components = components + } + #else + @objc public convenience override init() { + self.init(components: []) + } + @objc required public init(components: [PathComponent]) { self.components = components } + #endif #if canImport(CoreGraphics) @objc(initWithCGPath:) convenience public init(cgPath: CGPath) { @@ -238,7 +288,8 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR public static var supportsSecureCoding: Bool { return true } - + + #if !os(WASI) public func encode(with aCoder: NSCoder) { aCoder.encode(self.data) } @@ -247,6 +298,7 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR guard let data = aDecoder.decodeData() else { return nil } self.init(data: data) } + #endif // MARK: - @@ -288,12 +340,33 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR return windingCount } - @objc(containsPoint:usingRule:) public func contains(_ point: CGPoint, using rule: PathFillRule = .winding) -> Bool { + #if os(WASI) + public func contains(_ point: CGPoint, using rule: PathFillRule = .winding) -> Bool { + let count = self.windingCount(point) + return windingCountImpliesContainment(count, using: rule) + } + #else + @objc(containsPoint:usingRule:) public func contains(_ point: CGPoint, using rule: PathFillRule = .winding) -> Bool { + return _contains(point, using: rule) + } + #endif + + fileprivate func _contains(_ point: CGPoint, using rule: PathFillRule = .winding) -> Bool { let count = self.windingCount(point) return windingCountImpliesContainment(count, using: rule) } - @objc(containsPath:usingRule:accuracy:) public func contains(_ other: Path, using rule: PathFillRule = .winding, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + #if os(WASI) + public func contains(_ other: Path, using rule: PathFillRule = .winding, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + return _contains(other, using: rule, accuracy: accuracy) + } + #else + @objc(containsPath:usingRule:accuracy:) public func contains(_ other: Path, using rule: PathFillRule = .winding, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + return _contains(other, using: rule, accuracy: accuracy) + } + #endif + + fileprivate func _contains(_ other: Path, using rule: PathFillRule = .winding, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { // first, check that each component of `other` starts inside self for component in other.components { let p = component.startingPoint @@ -307,13 +380,33 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR return !self.intersects(other, accuracy: accuracy) } - @objc(offsetWithDistance:) public func offset(distance d: CGFloat) -> Path { + #if os(WASI) + public func offset(distance d: CGFloat) -> Path { + return _offset(distance: d) + } + #else + @objc(offsetWithDistance:) public func offset(distance d: CGFloat) -> Path { + return _offset(distance: d) + } + #endif + + fileprivate func _offset(distance d: CGFloat) -> Path { return Path(components: self.components.compactMap { $0.offset(distance: d) }) } - @objc public func disjointComponents() -> [Path] { + #if os(WASI) + public func disjointComponents() -> [Path] { + return _disjointComponents() + } + #else + @objc public func disjointComponents() -> [Path] { + return _disjointComponents() + } + #endif + + fileprivate func _disjointComponents() -> [Path] { let rule: PathFillRule = .evenOdd var outerComponents: [PathComponent: [PathComponent]] = [:] var innerComponents: [PathComponent] = [] @@ -345,18 +438,39 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR } } +#if !os(WASI) +@objc(BezierKitPath) extension Path { } +#endif + +#if canImport(CoreGraphics) @objc extension Path: Transformable { - @objc(copyUsingTransform:) public func copy(using t: CGAffineTransform) -> Self { + public func copy(using t: CGAffineTransform) -> Self { return type(of: self).init(components: self.components.map { $0.copy(using: t)}) } } +#endif +#if os(WASI) +extension Path: Reversible { + public func reversed() -> Self { + return _reversed() + } +} +#else @objc extension Path: Reversible { public func reversed() -> Self { + return _reversed() + } +} +#endif + +fileprivate extension Path { + func _reversed() -> Self { return type(of: self).init(components: self.components.map { $0.reversed() }) } } +#if !os(WASI) extension Path { public override var hash: Int { // override is needed because NSObject hashing is independent of Swift's Hashable @@ -372,6 +486,7 @@ extension Path { } } } +#endif public struct IndexedPathLocation: Equatable, Comparable { public let componentIndex: Int diff --git a/BezierKit/Library/PathComponent.swift b/BezierKit/Library/PathComponent.swift index 36f3573d..e4570eea 100644 --- a/BezierKit/Library/PathComponent.swift +++ b/BezierKit/Library/PathComponent.swift @@ -11,7 +11,7 @@ import CoreGraphics #endif import Foundation -@objc(BezierKitPathComponent) open class PathComponent: NSObject, Reversible, Transformable { +open class PathComponent: NSObject, Reversible, Transformable { private let offsets: [Int] public let points: [CGPoint] @@ -45,12 +45,29 @@ import Foundation public var numberOfElements: Int { return self.orders.count } - - @objc public var startingPoint: CGPoint { + #if os(WASI) + public var startingPoint: CGPoint { + return _startingPoint + } + #else + @objc public var startingPoint: CGPoint { + return _startingPoint + } + #endif + fileprivate var _startingPoint: CGPoint { return self.points[0] } - @objc public var endingPoint: CGPoint { + #if os(WASI) + public var endingPoint: CGPoint { + return _endingPoint + } + #else + @objc public var endingPoint: CGPoint { + return _endingPoint + } + #endif + fileprivate var _endingPoint: CGPoint { return self.points.last! } @@ -507,8 +524,18 @@ import Foundation let windingCount = self.windingCount(at: point) return windingCountImpliesContainment(windingCount, using: rule) } + + #if os(WASI) + public func enumeratePoints(includeControlPoints: Bool, using block: (CGPoint) -> Void) { + return _enumeratePoints(includeControlPoints: includeControlPoints, using: block) + } + #else + @objc(enumeratePointsIncludingControlPoints:usingBlock:) public func enumeratePoints(includeControlPoints: Bool, using block: (CGPoint) -> Void) { + return _enumeratePoints(includeControlPoints: includeControlPoints, using: block) + } + #endif - @objc(enumeratePointsIncludingControlPoints:usingBlock:) public func enumeratePoints(includeControlPoints: Bool, using block: (CGPoint) -> Void) { + fileprivate func _enumeratePoints(includeControlPoints: Bool, using block: (CGPoint) -> Void) { if includeControlPoints { for p in points { block(p) @@ -587,6 +614,10 @@ import Foundation } } +#if !os(WASI) +@objc(BezierKitPathComponent) extension PathComponent { } +#endif + public struct IndexedPathComponentLocation: Equatable, Comparable { public let elementIndex: Int public let t: CGFloat From 8cf1cd41be2cb28152a42eda2c24829718636fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Go=CC=81mez?= Date: Fri, 14 Jan 2022 13:00:17 +0100 Subject: [PATCH 02/63] Add CI and get the maximum amount of tests passing without changing the library implementation --- .github/workflows/wasmBuild.yml | 15 ++++++++ .travis.yml | 2 ++ .../BezierKitTests/CubicCurveTests.swift | 3 +- .../Path+VectorBooleanTests.swift | 3 ++ .../BezierKitTests/PolynomialTests.swift | 4 +-- BezierKit/Library/Path.swift | 34 +++++++++---------- BezierKit/LinuxMain.swift | 8 ----- travis/before_install.sh | 8 +++-- travis/script.sh | 8 +++-- 9 files changed, 52 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/wasmBuild.yml delete mode 100644 BezierKit/LinuxMain.swift diff --git a/.github/workflows/wasmBuild.yml b/.github/workflows/wasmBuild.yml new file mode 100644 index 00000000..feb37655 --- /dev/null +++ b/.github/workflows/wasmBuild.yml @@ -0,0 +1,15 @@ +name: WebAssmelby build +on: [push, pull_request] +jobs: + test: + name: BezierKit WASM support + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Install dependencies + run: docker pull ghcr.io/swiftwasm/carton:0.12.1 + - name: Test + run: docker run -v `pwd`:`pwd` -w `pwd` ghcr.io/swiftwasm/carton:latest /bin/sh -c 'carton test' + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index ee2526d3..169f4177 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,8 @@ jobs: os: osx osx_image: xcode11.2 env: DESTINATION="arch=x86_64"SCHEME="$OSX_SCHEME" SDK="$OSX_SDK" RUN_TESTS="YES" POD_LINT="NO" + - name: WebAssembly + os: linux before_install: - ./travis/before_install.sh script: diff --git a/BezierKit/BezierKitTests/CubicCurveTests.swift b/BezierKit/BezierKitTests/CubicCurveTests.swift index c2b72b53..d7bd2e08 100644 --- a/BezierKit/BezierKitTests/CubicCurveTests.swift +++ b/BezierKit/BezierKitTests/CubicCurveTests.swift @@ -8,7 +8,7 @@ import XCTest @testable import BezierKit - +#if !os(WASI) class CubicCurveTests: XCTestCase { override func setUp() { @@ -655,3 +655,4 @@ class CubicCurveTests: XCTestCase { XCTAssertNotEqual(c1, c5) } } +#endif diff --git a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift index e63263fc..636f2773 100644 --- a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift +++ b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift @@ -8,6 +8,9 @@ @testable import BezierKit import XCTest +#if canImport(CoreGraphics) +import CoreGraphics +#endif #if !os(WASI) private extension Path { diff --git a/BezierKit/BezierKitTests/PolynomialTests.swift b/BezierKit/BezierKitTests/PolynomialTests.swift index e63b901f..ed7527d1 100644 --- a/BezierKit/BezierKitTests/PolynomialTests.swift +++ b/BezierKit/BezierKitTests/PolynomialTests.swift @@ -93,7 +93,7 @@ class PolynomialTests: XCTestCase { XCTAssertEqual(roots[2], 1, accuracy: accuracy) XCTAssertEqual(roots[3], 1.2, accuracy: accuracy) } - + #if !os(WASI) func testDegree4RepeatedRoots() { // x^4 - 2x^2 + 1 let polynomial = BernsteinPolynomial4(b0: 1, b1: 1, b2: 2.0 / 3.0, b3: 0, b4: 0) @@ -102,7 +102,7 @@ class PolynomialTests: XCTestCase { XCTAssertEqual(roots[0], -1, accuracy: accuracy) XCTAssertEqual(roots[1], 1, accuracy: accuracy) } - + #endif func testDegree5() { // 0.2x^5 - 0.813333x^3 - 8.56x let polynomial = BernsteinPolynomial5(b0: 0, b1: -1.712, b2: -3.424, b3: -5.2173333, b4: -7.1733332, b5: -9.173333) diff --git a/BezierKit/Library/Path.swift b/BezierKit/Library/Path.swift index 012184f1..cd05b790 100644 --- a/BezierKit/Library/Path.swift +++ b/BezierKit/Library/Path.swift @@ -436,6 +436,22 @@ open class Path: NSObject, NSSecureCoding { } return outerComponents.values.map { Path(components: $0) } } + + #if !os(WASI) + public override var hash: Int { + // override is needed because NSObject hashing is independent of Swift's Hashable + return lock.sync { + if let _hash = _hash { return _hash } + var hasher = Hasher() + for component in components { + hasher.combine(component) + } + let h = hasher.finalize() + _hash = h + return h + } + } + #endif } #if !os(WASI) @@ -470,24 +486,6 @@ fileprivate extension Path { } } -#if !os(WASI) -extension Path { - public override var hash: Int { - // override is needed because NSObject hashing is independent of Swift's Hashable - return lock.sync { - if let _hash = _hash { return _hash } - var hasher = Hasher() - for component in components { - hasher.combine(component) - } - let h = hasher.finalize() - _hash = h - return h - } - } -} -#endif - public struct IndexedPathLocation: Equatable, Comparable { public let componentIndex: Int public let elementIndex: Int diff --git a/BezierKit/LinuxMain.swift b/BezierKit/LinuxMain.swift deleted file mode 100644 index 2e27b6ac..00000000 --- a/BezierKit/LinuxMain.swift +++ /dev/null @@ -1,8 +0,0 @@ -import XCTest - -import BezierKitTests - -var tests = [XCTestCaseEntry]() -tests += BezierKitTests.__allTests() - -XCTMain(tests) diff --git a/travis/before_install.sh b/travis/before_install.sh index 96652429..63b0ecf6 100755 --- a/travis/before_install.sh +++ b/travis/before_install.sh @@ -4,6 +4,10 @@ if [[ $TRAVIS_OS_NAME = 'osx' ]]; then # install macOS prerequistes : elif [[ $TRAVIS_OS_NAME = 'linux' ]]; then - wget https://swift.org/builds/swift-5.3-release/ubuntu1804/${SWIFT_VERSION}/${SWIFT_VERSION}-ubuntu18.04.tar.gz - tar xzf ${SWIFT_VERSION}-ubuntu18.04.tar.gz + if [[ $TRAVIS_JOB_NAME = 'WebAssembly' ]]; then + docker pull ghcr.io/swiftwasm/carton:0.12.1 + else + wget https://swift.org/builds/swift-5.3-release/ubuntu1804/${SWIFT_VERSION}/${SWIFT_VERSION}-ubuntu18.04.tar.gz + tar xzf ${SWIFT_VERSION}-ubuntu18.04.tar.gz + fi fi diff --git a/travis/script.sh b/travis/script.sh index 7efeabc7..101c351b 100755 --- a/travis/script.sh +++ b/travis/script.sh @@ -16,6 +16,10 @@ if [[ $TRAVIS_OS_NAME = 'osx' ]]; then pod spec lint; fi elif [[ $TRAVIS_OS_NAME = 'linux' ]]; then - export PATH="${PWD}/${SWIFT_VERSION}-ubuntu18.04/usr/bin:${PATH}" - swift test --enable-test-discovery + if [[ $TRAVIS_JOB_NAME = 'WebAssembly' ]]; then + docker run -v `pwd`:`pwd` -w `pwd` ghcr.io/swiftwasm/carton:latest /bin/sh -c 'carton test' + else + export PATH="${PWD}/${SWIFT_VERSION}-ubuntu18.04/usr/bin:${PATH}" + swift test --enable-test-discovery + fi fi From 4dba6a4277e6f7484ab154ff90ac69ba9c26d6f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Go=CC=81mez?= Date: Fri, 14 Jan 2022 13:35:26 +0100 Subject: [PATCH 03/63] Simplify objc compatibility as much as possible using if os(wasi) --- BezierKit/Library/Path+Projection.swift | 28 ++--- BezierKit/Library/Path+VectorBoolean.swift | 56 ++++------ BezierKit/Library/Path.swift | 124 +++++++-------------- BezierKit/Library/PathComponent.swift | 42 +++---- 4 files changed, 86 insertions(+), 164 deletions(-) diff --git a/BezierKit/Library/Path+Projection.swift b/BezierKit/Library/Path+Projection.swift index 6347a78f..ae185b76 100644 --- a/BezierKit/Library/Path+Projection.swift +++ b/BezierKit/Library/Path+Projection.swift @@ -48,17 +48,13 @@ public extension Path { return self.searchForClosestLocation(to: point, maximumDistance: .infinity, requireBest: true) } - #if os(WASI) - func pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { - return _pointIsWithinDistanceOfBoundary(point, distance: distance) - } - #else - @objc(point:isWithinDistanceOfBoundary:) func pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { - return _pointIsWithinDistanceOfBoundary(point, distance: distance) - } + #if !os(WASI) + @objc(point:isWithinDistanceOfBoundary:) func _pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { + return pointIsWithinDistanceOfBoundary(point, distance: distance) + } #endif - fileprivate func _pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { + func pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { return self.searchForClosestLocation(to: point, maximumDistance: distance, requireBest: false) != nil } } @@ -116,17 +112,13 @@ public extension PathComponent { return result } - #if os(WASI) - func pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { - return _pointIsWithinDistanceOfBoundary(point, distance: distance) - } - #else - @objc(point:isWithinDistanceOfBoundary:) func pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { - return _pointIsWithinDistanceOfBoundary(point, distance: distance) - } + #if !os(WASI) + @objc(point:isWithinDistanceOfBoundary:) func _pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { + return pointIsWithinDistanceOfBoundary(point, distance: distance) + } #endif - fileprivate func _pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { + func pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { return self.searchForClosestLocation(to: point, maximumDistance: distance, requireBest: false) != nil } } diff --git a/BezierKit/Library/Path+VectorBoolean.swift b/BezierKit/Library/Path+VectorBoolean.swift index 49080b35..8ae3cf68 100644 --- a/BezierKit/Library/Path+VectorBoolean.swift +++ b/BezierKit/Library/Path+VectorBoolean.swift @@ -13,31 +13,23 @@ import Foundation public extension Path { - #if os(WASI) - func subtract(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { - return _subtract(other, accuracy: accuracy) - } - #else - @objc(subtractPath:accuracy:) func subtract(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { - return _subtract(other, accuracy: accuracy) - } + #if !os(WASI) + @objc(subtractPath:accuracy:) func _subtract(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + return subtract(other, accuracy: accuracy) + } #endif - fileprivate func _subtract(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + func subtract(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { return self.performBooleanOperation(.subtract, with: other.reversed(), accuracy: accuracy) } - #if os(WASI) - func `union`(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { - return _union(other, accuracy: accuracy) - } - #else - @objc(unionPath:accuracy:) func `union`(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { - return _union(other, accuracy: accuracy) - } + #if !os(WASI) + @objc(unionPath:accuracy:) func `_union`(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + return union(other, accuracy: accuracy) + } #endif - fileprivate func `_union`(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + func `union`(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { guard self.isEmpty == false else { return other } @@ -47,31 +39,23 @@ public extension Path { return self.performBooleanOperation(.union, with: other, accuracy: accuracy) } - #if os(WASI) - func intersect(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { - return _intersect(other, accuracy: accuracy) - } - #else - @objc(intersectPath:accuracy:) func intersect(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { - return _intersect(other, accuracy: accuracy) - } + #if !os(WASI) + @objc(intersectPath:accuracy:) func _intersect(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + return intersect(other, accuracy: accuracy) + } #endif - fileprivate func _intersect(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + func intersect(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { return self.performBooleanOperation(.intersect, with: other, accuracy: accuracy) } - #if os(WASI) - func crossingsRemoved(accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { - return _crossingsRemoved(accuracy: accuracy) - } - #else - @objc(crossingsRemovedWithAccuracy:) func crossingsRemoved(accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { - return _crossingsRemoved(accuracy: accuracy) - } + #if !os(WASI) + @objc(crossingsRemovedWithAccuracy:) func _crossingsRemoved(accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + return crossingsRemoved(accuracy: accuracy) + } #endif - fileprivate func _crossingsRemoved(accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + func crossingsRemoved(accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { let intersections = self.selfIntersections(accuracy: accuracy) let augmentedGraph = AugmentedGraph(path1: self, path2: self, intersections: intersections, operation: .removeCrossings) return augmentedGraph.performOperation() diff --git a/BezierKit/Library/Path.swift b/BezierKit/Library/Path.swift index cd05b790..0deb0970 100644 --- a/BezierKit/Library/Path.swift +++ b/BezierKit/Library/Path.swift @@ -20,15 +20,9 @@ private extension Array { } } -#if os(WASI) - public enum PathFillRule: NSInteger { - case winding=0, evenOdd - } -#else - @objc(BezierKitPathFillRule) public enum PathFillRule: NSInteger { +@objc(BezierKitPathFillRule) public enum PathFillRule: NSInteger { case winding=0, evenOdd - } -#endif +} internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillRule) -> Bool { switch rule { @@ -83,17 +77,13 @@ open class Path: NSObject, NSSecureCoding { }() #endif - #if os(WASI) - public var isEmpty: Bool { - return _isEmpty - } - #else - @objc public var isEmpty: Bool { - return _isEmpty - } + #if !os(WASI) + @objc(isEmpty) public var _isEmpty: Bool { + return isEmpty + } #endif - fileprivate var _isEmpty: Bool { + public var isEmpty: Bool { return self.components.isEmpty // components are not allowed to be empty } @@ -126,17 +116,13 @@ open class Path: NSObject, NSSecureCoding { @objc public let components: [PathComponent] #endif - #if os(WASI) - public func selfIntersects(accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { - return _selfIntersects(accuracy: accuracy) - } - #else - @objc(selfIntersectsWithAccuracy:) public func selfIntersects(accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { - return _selfIntersects(accuracy: accuracy) + #if !os(WASI) + @objc(selfIntersectsWithAccuracy:) public func _selfIntersects(accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + return selfIntersects(accuracy: accuracy) } #endif - fileprivate func _selfIntersects(accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + public func selfIntersects(accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { return !self.selfIntersections(accuracy: accuracy).isEmpty } @@ -157,17 +143,13 @@ open class Path: NSObject, NSSecureCoding { return intersections } - #if os(WASI) - public func intersects(_ other: Path, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { - return _intersects(other, accuracy: accuracy) - } - #else - @objc(intersectsPath:accuracy:) public func intersects(_ other: Path, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { - return _intersects(other, accuracy: accuracy) + #if !os(WASI) + @objc(intersectsPath:accuracy:) public func _intersects(_ other: Path, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + return intersects(other, accuracy: accuracy) } #endif - fileprivate func _intersects(_ other: Path, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + public func intersects(_ other: Path, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { return !self.intersections(with: other, accuracy: accuracy).isEmpty } @@ -340,33 +322,24 @@ open class Path: NSObject, NSSecureCoding { return windingCount } - #if os(WASI) - public func contains(_ point: CGPoint, using rule: PathFillRule = .winding) -> Bool { - let count = self.windingCount(point) - return windingCountImpliesContainment(count, using: rule) - } - #else - @objc(containsPoint:usingRule:) public func contains(_ point: CGPoint, using rule: PathFillRule = .winding) -> Bool { - return _contains(point, using: rule) - } + #if !os(WASI) + @objc(containsPoint:usingRule:) public func _contains(_ point: CGPoint, using rule: PathFillRule = .winding) -> Bool { + return contains(point, using: rule) + } #endif - fileprivate func _contains(_ point: CGPoint, using rule: PathFillRule = .winding) -> Bool { + public func contains(_ point: CGPoint, using rule: PathFillRule = .winding) -> Bool { let count = self.windingCount(point) return windingCountImpliesContainment(count, using: rule) } - #if os(WASI) - public func contains(_ other: Path, using rule: PathFillRule = .winding, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { - return _contains(other, using: rule, accuracy: accuracy) - } - #else - @objc(containsPath:usingRule:accuracy:) public func contains(_ other: Path, using rule: PathFillRule = .winding, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { - return _contains(other, using: rule, accuracy: accuracy) - } + #if !os(WASI) + @objc(containsPath:usingRule:accuracy:) public func _contains(_ other: Path, using rule: PathFillRule = .winding, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + return contains(other, using: rule, accuracy: accuracy) + } #endif - fileprivate func _contains(_ other: Path, using rule: PathFillRule = .winding, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + public func contains(_ other: Path, using rule: PathFillRule = .winding, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { // first, check that each component of `other` starts inside self for component in other.components { let p = component.startingPoint @@ -380,33 +353,25 @@ open class Path: NSObject, NSSecureCoding { return !self.intersects(other, accuracy: accuracy) } - #if os(WASI) - public func offset(distance d: CGFloat) -> Path { - return _offset(distance: d) - } - #else - @objc(offsetWithDistance:) public func offset(distance d: CGFloat) -> Path { - return _offset(distance: d) - } + #if !os(WASI) + @objc(offsetWithDistance:) public func _offset(distance d: CGFloat) -> Path { + return offset(distance: d) + } #endif - fileprivate func _offset(distance d: CGFloat) -> Path { + public func offset(distance d: CGFloat) -> Path { return Path(components: self.components.compactMap { $0.offset(distance: d) }) } - #if os(WASI) - public func disjointComponents() -> [Path] { - return _disjointComponents() - } - #else - @objc public func disjointComponents() -> [Path] { - return _disjointComponents() - } + #if !os(WASI) + @objc public func _disjointComponents() -> [Path] { + return disjointComponents() + } #endif - fileprivate func _disjointComponents() -> [Path] { + public func disjointComponents() -> [Path] { let rule: PathFillRule = .evenOdd var outerComponents: [PathComponent: [PathComponent]] = [:] var innerComponents: [PathComponent] = [] @@ -466,25 +431,18 @@ open class Path: NSObject, NSSecureCoding { } #endif -#if os(WASI) -extension Path: Reversible { +extension Path { public func reversed() -> Self { - return _reversed() + return type(of: self).init(components: self.components.map { $0.reversed() }) } } + +#if os(WASI) +extension Path: Reversible { } #else -@objc extension Path: Reversible { - public func reversed() -> Self { - return _reversed() - } -} +@objc extension Path: Reversible { } #endif -fileprivate extension Path { - func _reversed() -> Self { - return type(of: self).init(components: self.components.map { $0.reversed() }) - } -} public struct IndexedPathLocation: Equatable, Comparable { public let componentIndex: Int diff --git a/BezierKit/Library/PathComponent.swift b/BezierKit/Library/PathComponent.swift index e4570eea..e5758bae 100644 --- a/BezierKit/Library/PathComponent.swift +++ b/BezierKit/Library/PathComponent.swift @@ -45,29 +45,21 @@ open class PathComponent: NSObject, Reversible, Transformable { public var numberOfElements: Int { return self.orders.count } - #if os(WASI) - public var startingPoint: CGPoint { - return _startingPoint - } - #else - @objc public var startingPoint: CGPoint { - return _startingPoint - } + #if !os(WASI) + @objc(startingPoint) public var _startingPoint: CGPoint { + return startingPoint + } #endif - fileprivate var _startingPoint: CGPoint { + public var startingPoint: CGPoint { return self.points[0] } - #if os(WASI) - public var endingPoint: CGPoint { - return _endingPoint - } - #else - @objc public var endingPoint: CGPoint { - return _endingPoint - } + #if !os(WASI) + @objc(endingPoint) public var _endingPoint: CGPoint { + return endingPoint + } #endif - fileprivate var _endingPoint: CGPoint { + public var endingPoint: CGPoint { return self.points.last! } @@ -525,17 +517,13 @@ open class PathComponent: NSObject, Reversible, Transformable { return windingCountImpliesContainment(windingCount, using: rule) } - #if os(WASI) - public func enumeratePoints(includeControlPoints: Bool, using block: (CGPoint) -> Void) { - return _enumeratePoints(includeControlPoints: includeControlPoints, using: block) - } - #else - @objc(enumeratePointsIncludingControlPoints:usingBlock:) public func enumeratePoints(includeControlPoints: Bool, using block: (CGPoint) -> Void) { - return _enumeratePoints(includeControlPoints: includeControlPoints, using: block) - } + #if !os(WASI) + @objc(enumeratePointsIncludingControlPoints:usingBlock:) public func _enumeratePoints(includeControlPoints: Bool, using block: (CGPoint) -> Void) { + return enumeratePoints(includeControlPoints: includeControlPoints, using: block) + } #endif - fileprivate func _enumeratePoints(includeControlPoints: Bool, using block: (CGPoint) -> Void) { + public func enumeratePoints(includeControlPoints: Bool, using block: (CGPoint) -> Void) { if includeControlPoints { for p in points { block(p) From 3ff88bf5c2f6c3171a3c174a2d233580e03968a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Go=CC=81mez?= Date: Fri, 14 Jan 2022 15:17:58 +0100 Subject: [PATCH 04/63] Simplify GitHub action config --- .github/workflows/wasmBuild.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/wasmBuild.yml b/.github/workflows/wasmBuild.yml index feb37655..d4038c4a 100644 --- a/.github/workflows/wasmBuild.yml +++ b/.github/workflows/wasmBuild.yml @@ -4,12 +4,10 @@ jobs: test: name: BezierKit WASM support runs-on: ubuntu-latest + container: ghcr.io/swiftwasm/carton:0.12.1 + steps: - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Install dependencies - run: docker pull ghcr.io/swiftwasm/carton:0.12.1 + - name: Test - run: docker run -v `pwd`:`pwd` -w `pwd` ghcr.io/swiftwasm/carton:latest /bin/sh -c 'carton test' - \ No newline at end of file + run: carton test \ No newline at end of file From bc2fb8fae5047cdeed003d1a5085f6156c434a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Go=CC=81mez?= Date: Fri, 4 Mar 2022 14:53:23 +0100 Subject: [PATCH 05/63] Recover LinuxMain.swift and disable the usage of this file when running WASM tests on CI --- .github/workflows/wasmBuild.yml | 4 ++++ BezierKit/LinuxMain.swift | 8 ++++++++ 2 files changed, 12 insertions(+) create mode 100644 BezierKit/LinuxMain.swift diff --git a/.github/workflows/wasmBuild.yml b/.github/workflows/wasmBuild.yml index d4038c4a..282be473 100644 --- a/.github/workflows/wasmBuild.yml +++ b/.github/workflows/wasmBuild.yml @@ -8,6 +8,10 @@ jobs: steps: - uses: actions/checkout@v2 + + # Carton is not able to find our tests if LinuxMain.swift file is defined + - name: Remove LinuxMain.swift file + run: rm BezierKit/LinuxMain.swift - name: Test run: carton test \ No newline at end of file diff --git a/BezierKit/LinuxMain.swift b/BezierKit/LinuxMain.swift new file mode 100644 index 00000000..77ab6a5c --- /dev/null +++ b/BezierKit/LinuxMain.swift @@ -0,0 +1,8 @@ +import XCTest + +import BezierKitTests + +var tests = [XCTestCaseEntry]() +tests += BezierKitTests.__allTests() + +XCTMain(tests) \ No newline at end of file From 8fa1e4c85a031c6bf958c27207fe84569bb87e18 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 15:03:03 -0800 Subject: [PATCH 06/63] resolve SwiftLint warnings. --- BezierKit/BezierKit.xcodeproj/project.pbxproj | 2 +- .../xcschemes/BezierKitMacDemos.xcscheme | 2 +- .../xcshareddata/xcschemes/BezierKit_Mac.xcscheme | 2 +- .../xcschemes/BezierKit_MacTests.xcscheme | 2 +- .../xcshareddata/xcschemes/BezierKit_iOS.xcscheme | 2 +- .../xcschemes/BezierKit_iOSTests.xcscheme | 2 +- BezierKit/BezierKitTests/LockTests.swift | 2 +- BezierKit/BezierKitTests/Path+DataTests.swift | 4 ++-- .../BezierKitTests/Path+VectorBooleanTests.swift | 2 +- BezierKit/BezierKitTests/PerformanceTests.swift | 2 +- BezierKit/BezierKitTests/TransformableTests.swift | 2 +- BezierKit/Library/Path+Projection.swift | 2 +- BezierKit/Library/Path+VectorBoolean.swift | 12 ++++++------ BezierKit/Library/Path.swift | 5 ++--- BezierKit/Library/PathComponent.swift | 2 +- BezierKit/LinuxMain.swift | 2 +- 16 files changed, 23 insertions(+), 24 deletions(-) diff --git a/BezierKit/BezierKit.xcodeproj/project.pbxproj b/BezierKit/BezierKit.xcodeproj/project.pbxproj index 58cbb812..2291ecae 100644 --- a/BezierKit/BezierKit.xcodeproj/project.pbxproj +++ b/BezierKit/BezierKit.xcodeproj/project.pbxproj @@ -536,7 +536,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0830; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1320; ORGANIZATIONNAME = "Holmes Futrell"; TargetAttributes = { FD0F54F41DC43FFB0084CDCD = { diff --git a/BezierKit/BezierKit.xcodeproj/xcshareddata/xcschemes/BezierKitMacDemos.xcscheme b/BezierKit/BezierKit.xcodeproj/xcshareddata/xcschemes/BezierKitMacDemos.xcscheme index 97cd762e..04bc0b6a 100644 --- a/BezierKit/BezierKit.xcodeproj/xcshareddata/xcschemes/BezierKitMacDemos.xcscheme +++ b/BezierKit/BezierKit.xcodeproj/xcshareddata/xcschemes/BezierKitMacDemos.xcscheme @@ -1,6 +1,6 @@ Bool { return pointIsWithinDistanceOfBoundary(point, distance: distance) - } + } #endif func pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { diff --git a/BezierKit/Library/Path+VectorBoolean.swift b/BezierKit/Library/Path+VectorBoolean.swift index 8ae3cf68..f4452e5e 100644 --- a/BezierKit/Library/Path+VectorBoolean.swift +++ b/BezierKit/Library/Path+VectorBoolean.swift @@ -12,7 +12,7 @@ import CoreGraphics import Foundation public extension Path { - + #if !os(WASI) @objc(subtractPath:accuracy:) func _subtract(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { return subtract(other, accuracy: accuracy) @@ -22,7 +22,7 @@ public extension Path { func subtract(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { return self.performBooleanOperation(.subtract, with: other.reversed(), accuracy: accuracy) } - + #if !os(WASI) @objc(unionPath:accuracy:) func `_union`(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { return union(other, accuracy: accuracy) @@ -41,20 +41,20 @@ public extension Path { #if !os(WASI) @objc(intersectPath:accuracy:) func _intersect(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { - return intersect(other, accuracy: accuracy) - } + return intersect(other, accuracy: accuracy) + } #endif func intersect(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { return self.performBooleanOperation(.intersect, with: other, accuracy: accuracy) } - + #if !os(WASI) @objc(crossingsRemovedWithAccuracy:) func _crossingsRemoved(accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { return crossingsRemoved(accuracy: accuracy) } #endif - + func crossingsRemoved(accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { let intersections = self.selfIntersections(accuracy: accuracy) let augmentedGraph = AugmentedGraph(path1: self, path2: self, intersections: intersections, operation: .removeCrossings) diff --git a/BezierKit/Library/Path.swift b/BezierKit/Library/Path.swift index 0deb0970..cc947faa 100644 --- a/BezierKit/Library/Path.swift +++ b/BezierKit/Library/Path.swift @@ -184,7 +184,7 @@ open class Path: NSObject, NSSecureCoding { @objc public convenience override init() { self.init(components: []) } - + @objc required public init(components: [PathComponent]) { self.components = components } @@ -270,7 +270,7 @@ open class Path: NSObject, NSSecureCoding { public static var supportsSecureCoding: Bool { return true } - + #if !os(WASI) public func encode(with aCoder: NSCoder) { aCoder.encode(self.data) @@ -443,7 +443,6 @@ extension Path: Reversible { } @objc extension Path: Reversible { } #endif - public struct IndexedPathLocation: Equatable, Comparable { public let componentIndex: Int public let elementIndex: Int diff --git a/BezierKit/Library/PathComponent.swift b/BezierKit/Library/PathComponent.swift index e5758bae..9d83e85e 100644 --- a/BezierKit/Library/PathComponent.swift +++ b/BezierKit/Library/PathComponent.swift @@ -516,7 +516,7 @@ open class PathComponent: NSObject, Reversible, Transformable { let windingCount = self.windingCount(at: point) return windingCountImpliesContainment(windingCount, using: rule) } - + #if !os(WASI) @objc(enumeratePointsIncludingControlPoints:usingBlock:) public func _enumeratePoints(includeControlPoints: Bool, using block: (CGPoint) -> Void) { return enumeratePoints(includeControlPoints: includeControlPoints, using: block) diff --git a/BezierKit/LinuxMain.swift b/BezierKit/LinuxMain.swift index 77ab6a5c..2e27b6ac 100644 --- a/BezierKit/LinuxMain.swift +++ b/BezierKit/LinuxMain.swift @@ -5,4 +5,4 @@ import BezierKitTests var tests = [XCTestCaseEntry]() tests += BezierKitTests.__allTests() -XCTMain(tests) \ No newline at end of file +XCTMain(tests) From 8257f3356e700a2aea77bcc141cbcd4a1f3f33ab Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 15:28:00 -0800 Subject: [PATCH 07/63] basic GitHub actions for Mac + Linux. --- .github/workflows/ci.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..2f5a8750 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,18 @@ +name: BezierKit Mac + Linux + +on: [push, pull_request] + +jobs: + build: + name: BezierKit on ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + - name: Build + run: swift build + - name: Run tests + run: swift test From 9f7aec5ac21d10ef43677051e64a97d807c31ec8 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 16:00:57 -0800 Subject: [PATCH 08/63] try to move as much obj-c code from Path to category as possible. --- BezierKit/BezierKit.xcodeproj/project.pbxproj | 6 ++ BezierKit/Library/BezierKit-ObjC.swift | 67 ++++++++++++++++ BezierKit/Library/Path.swift | 76 ++----------------- 3 files changed, 81 insertions(+), 68 deletions(-) create mode 100644 BezierKit/Library/BezierKit-ObjC.swift diff --git a/BezierKit/BezierKit.xcodeproj/project.pbxproj b/BezierKit/BezierKit.xcodeproj/project.pbxproj index 2291ecae..5436f873 100644 --- a/BezierKit/BezierKit.xcodeproj/project.pbxproj +++ b/BezierKit/BezierKit.xcodeproj/project.pbxproj @@ -28,6 +28,8 @@ FD26253F1EAC7B9A00C64652 /* BezierKit_iOS.h in Headers */ = {isa = PBXBuildFile; fileRef = FD26253D1EAC7B9A00C64652 /* BezierKit_iOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; FD26915C238B628B002036A6 /* ReversibleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26915B238B628B002036A6 /* ReversibleTests.swift */; }; FD26915D238B628B002036A6 /* ReversibleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26915B238B628B002036A6 /* ReversibleTests.swift */; }; + FD3530F327DC140F006E9726 /* BezierKit-ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3530F227DC140E006E9726 /* BezierKit-ObjC.swift */; }; + FD3530F427DC140F006E9726 /* BezierKit-ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3530F227DC140E006E9726 /* BezierKit-ObjC.swift */; }; FD4024502110CF5100FA723C /* QuadraticCurveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD40244F2110CF5100FA723C /* QuadraticCurveTests.swift */; }; FD4024512110CF5100FA723C /* QuadraticCurveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD40244F2110CF5100FA723C /* QuadraticCurveTests.swift */; }; FD4A0DC41EAAD01F0031A393 /* BezierKit_Mac.h in Headers */ = {isa = PBXBuildFile; fileRef = FD4A0DC21EAAD01F0031A393 /* BezierKit_Mac.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -176,6 +178,7 @@ FD26253D1EAC7B9A00C64652 /* BezierKit_iOS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BezierKit_iOS.h; sourceTree = ""; }; FD26253E1EAC7B9A00C64652 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; FD26915B238B628B002036A6 /* ReversibleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReversibleTests.swift; sourceTree = ""; }; + FD3530F227DC140E006E9726 /* BezierKit-ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BezierKit-ObjC.swift"; sourceTree = ""; }; FD40244F2110CF5100FA723C /* QuadraticCurveTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuadraticCurveTests.swift; sourceTree = ""; }; FD4A0DC01EAAD01F0031A393 /* BezierKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BezierKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FD4A0DC21EAAD01F0031A393 /* BezierKit_Mac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BezierKit_Mac.h; sourceTree = ""; }; @@ -378,6 +381,7 @@ FD4A63FD200AA50B00930E10 /* Shape.swift */, FDB6B3FF1EAFD6DF00001C61 /* Types.swift */, FDB6B4001EAFD6DF00001C61 /* Utils.swift */, + FD3530F227DC140E006E9726 /* BezierKit-ObjC.swift */, ); path = Library; sourceTree = ""; @@ -704,6 +708,7 @@ FDB6B4031EAFD6DF00001C61 /* BezierCurve.swift in Sources */, FDB6B4051EAFD6DF00001C61 /* CubicCurve.swift in Sources */, FDE6CD8D1EC8F2F800FAB479 /* LineSegment.swift in Sources */, + FD3530F427DC140F006E9726 /* BezierKit-ObjC.swift in Sources */, FD149EBB2135CBFF009E791D /* AugmentedGraph.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -735,6 +740,7 @@ FDB6B4021EAFD6DF00001C61 /* BezierCurve.swift in Sources */, FDB6B4041EAFD6DF00001C61 /* CubicCurve.swift in Sources */, FDE6CD8C1EC8F2F800FAB479 /* LineSegment.swift in Sources */, + FD3530F327DC140F006E9726 /* BezierKit-ObjC.swift in Sources */, FD149EBA2135CBFF009E791D /* AugmentedGraph.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/BezierKit/Library/BezierKit-ObjC.swift b/BezierKit/Library/BezierKit-ObjC.swift new file mode 100644 index 00000000..ae6fb64f --- /dev/null +++ b/BezierKit/Library/BezierKit-ObjC.swift @@ -0,0 +1,67 @@ +// +// BezierKit-ObjC.swift +// BezierKit +// +// Created by Holmes Futrell on 3/11/22. +// Copyright © 2022 Holmes Futrell. All rights reserved. +// + +#if canImport(CoreGraphics) +import CoreGraphics +#endif + +import Foundation + +#if !os(WASI) && !os(Linux) + +@available (*, unavailable) +@objc(BezierKitPath) public extension Path { + + @objc(isEmpty) var _objc_isEmpty: Bool { + return isEmpty + } + + @objc(components) var _objc_components: [PathComponent] { + return components + } + + @objc(selfIntersectsWithAccuracy:) func _objc_selfIntersects(accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + return selfIntersects(accuracy: accuracy) + } + + @objc(intersectsPath:accuracy:) func _objc_intersects(_ other: Path, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + return intersects(other, accuracy: accuracy) + } + + @objc(initWithComponents:) convenience init(_objc_components components: [PathComponent]) { + self.init(components: components) + } + + #if canImport(CoreGraphics) + @objc(initWithCGPath:) convenience init(_objc_cgPath cgPath: CGPath) { + self.init(cgPath: cgPath) + } + #endif + + @objc(containsPoint:usingRule:) func _objc_contains(_ point: CGPoint, using rule: PathFillRule = .winding) -> Bool { + return contains(point, using: rule) + } + + @objc(containsPath:usingRule:accuracy:) func _objc_contains(_ other: Path, using rule: PathFillRule = .winding, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + return contains(other, using: rule, accuracy: accuracy) + } + + @objc(offsetWithDistance:) func _objc_offset(distance d: CGFloat) -> Path { + return offset(distance: d) + } + + @objc(disjointComponents) func _objc_disjointComponents() -> [Path] { + return disjointComponents() + } + + @objc(CGPath) var _objc_cgPath: CGPath { + return cgPath + } +} + +#endif diff --git a/BezierKit/Library/Path.swift b/BezierKit/Library/Path.swift index cc947faa..85c7c33f 100644 --- a/BezierKit/Library/Path.swift +++ b/BezierKit/Library/Path.swift @@ -21,7 +21,7 @@ private extension Array { } @objc(BezierKitPathFillRule) public enum PathFillRule: NSInteger { - case winding=0, evenOdd + case winding = 0, evenOdd } internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillRule) -> Bool { @@ -64,7 +64,7 @@ open class Path: NSObject, NSSecureCoding { } #if canImport(CoreGraphics) - @objc(CGPath) public var cgPath: CGPath { + public var cgPath: CGPath { return self.lock.sync { self._cgPath } } @@ -77,12 +77,6 @@ open class Path: NSObject, NSSecureCoding { }() #endif - #if !os(WASI) - @objc(isEmpty) public var _isEmpty: Bool { - return isEmpty - } - #endif - public var isEmpty: Bool { return self.components.isEmpty // components are not allowed to be empty } @@ -110,17 +104,7 @@ open class Path: NSObject, NSSecureCoding { private var _hash: Int? - #if os(WASI) public let components: [PathComponent] - #else - @objc public let components: [PathComponent] - #endif - - #if !os(WASI) - @objc(selfIntersectsWithAccuracy:) public func _selfIntersects(accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { - return selfIntersects(accuracy: accuracy) - } - #endif public func selfIntersects(accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { return !self.selfIntersections(accuracy: accuracy).isEmpty @@ -143,12 +127,6 @@ open class Path: NSObject, NSSecureCoding { return intersections } - #if !os(WASI) - @objc(intersectsPath:accuracy:) public func _intersects(_ other: Path, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { - return intersects(other, accuracy: accuracy) - } - #endif - public func intersects(_ other: Path, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { return !self.intersections(with: other, accuracy: accuracy).isEmpty } @@ -172,26 +150,22 @@ open class Path: NSObject, NSSecureCoding { return intersections } - #if os(WASI) + #if os(WASI) || os(Linux) public convenience override init() { self.init(components: []) } - - required public init(components: [PathComponent]) { - self.components = components - } #else @objc public convenience override init() { self.init(components: []) } + #endif - @objc required public init(components: [PathComponent]) { + required public init(components: [PathComponent]) { self.components = components } - #endif #if canImport(CoreGraphics) - @objc(initWithCGPath:) convenience public init(cgPath: CGPath) { + convenience public init(cgPath: CGPath) { let context = PathApplierFunctionContext() func applierFunction(_ ctx: UnsafeMutableRawPointer?, _ element: UnsafePointer) { guard let context = ctx?.assumingMemoryBound(to: PathApplierFunctionContext.self).pointee else { @@ -322,23 +296,11 @@ open class Path: NSObject, NSSecureCoding { return windingCount } - #if !os(WASI) - @objc(containsPoint:usingRule:) public func _contains(_ point: CGPoint, using rule: PathFillRule = .winding) -> Bool { - return contains(point, using: rule) - } - #endif - public func contains(_ point: CGPoint, using rule: PathFillRule = .winding) -> Bool { let count = self.windingCount(point) return windingCountImpliesContainment(count, using: rule) } - #if !os(WASI) - @objc(containsPath:usingRule:accuracy:) public func _contains(_ other: Path, using rule: PathFillRule = .winding, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { - return contains(other, using: rule, accuracy: accuracy) - } - #endif - public func contains(_ other: Path, using rule: PathFillRule = .winding, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { // first, check that each component of `other` starts inside self for component in other.components { @@ -353,24 +315,12 @@ open class Path: NSObject, NSSecureCoding { return !self.intersects(other, accuracy: accuracy) } - #if !os(WASI) - @objc(offsetWithDistance:) public func _offset(distance d: CGFloat) -> Path { - return offset(distance: d) - } - #endif - public func offset(distance d: CGFloat) -> Path { return Path(components: self.components.compactMap { $0.offset(distance: d) }) } - #if !os(WASI) - @objc public func _disjointComponents() -> [Path] { - return disjointComponents() - } - #endif - public func disjointComponents() -> [Path] { let rule: PathFillRule = .evenOdd var outerComponents: [PathComponent: [PathComponent]] = [:] @@ -419,30 +369,20 @@ open class Path: NSObject, NSSecureCoding { #endif } -#if !os(WASI) -@objc(BezierKitPath) extension Path { } -#endif - #if canImport(CoreGraphics) -@objc extension Path: Transformable { +extension Path: Transformable { public func copy(using t: CGAffineTransform) -> Self { return type(of: self).init(components: self.components.map { $0.copy(using: t)}) } } #endif -extension Path { +extension Path: Reversible { public func reversed() -> Self { return type(of: self).init(components: self.components.map { $0.reversed() }) } } -#if os(WASI) -extension Path: Reversible { } -#else -@objc extension Path: Reversible { } -#endif - public struct IndexedPathLocation: Equatable, Comparable { public let componentIndex: Int public let elementIndex: Int From b032ec1f9cf310edd6d2d73339b1d85d2d4b80e1 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 16:11:40 -0800 Subject: [PATCH 09/63] move more Obj-C code to isolated file ... --- BezierKit/Library/BezierKit-ObjC.swift | 55 ++++++++++++++++++++++ BezierKit/Library/Path+Data.swift | 4 +- BezierKit/Library/Path+Projection.swift | 12 ----- BezierKit/Library/Path+VectorBoolean.swift | 24 ---------- BezierKit/Library/PathComponent.swift | 21 +-------- 5 files changed, 58 insertions(+), 58 deletions(-) diff --git a/BezierKit/Library/BezierKit-ObjC.swift b/BezierKit/Library/BezierKit-ObjC.swift index ae6fb64f..28132a9f 100644 --- a/BezierKit/Library/BezierKit-ObjC.swift +++ b/BezierKit/Library/BezierKit-ObjC.swift @@ -14,6 +14,7 @@ import Foundation #if !os(WASI) && !os(Linux) +// MARK: Path.swift @available (*, unavailable) @objc(BezierKitPath) public extension Path { @@ -64,4 +65,58 @@ import Foundation } } +// MARK: Path+VectorBoolean.swift +@available (*, unavailable) +public extension Path { + @objc(subtractPath:accuracy:) func _objc_subtract(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + return subtract(other, accuracy: accuracy) + } + @objc(unionPath:accuracy:) func `_objc_union`(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + return union(other, accuracy: accuracy) + } + @objc(intersectPath:accuracy:) func _objc_intersect(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + return intersect(other, accuracy: accuracy) + } + @objc(crossingsRemovedWithAccuracy:) func _objc_crossingsRemoved(accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { + return crossingsRemoved(accuracy: accuracy) + } +} + +// MARK: Path+Data.swift +@available (*, unavailable) +public extension Path { + @objc(initWithData:) convenience init?(_objc_data data: Data) { + self.init(data: data) + } +} + +// MARK: Path+Projection.swift +@available (*, unavailable) +public extension Path { + @objc(point:isWithinDistanceOfBoundary:) func _objc_pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { + return pointIsWithinDistanceOfBoundary(point, distance: distance) + } +} + +@available(*, unavailable) +public extension PathComponent { + @objc(point:isWithinDistanceOfBoundary:) func _objc_pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { + return pointIsWithinDistanceOfBoundary(point, distance: distance) + } +} + +// MARK: PathComponent.swift +@available(*, unavailable) +@objc(BezierKitPathComponent) public extension PathComponent { + @objc(startingPoint) var _objc_startingPoint: CGPoint { + return startingPoint + } + @objc(endingPoint) var _objc_endingPoint: CGPoint { + return endingPoint + } + @objc(enumeratePointsIncludingControlPoints:usingBlock:) func _objc_enumeratePoints(includeControlPoints: Bool, using block: (CGPoint) -> Void) { + return enumeratePoints(includeControlPoints: includeControlPoints, using: block) + } +} + #endif diff --git a/BezierKit/Library/Path+Data.swift b/BezierKit/Library/Path+Data.swift index 8ee329ac..bbe2e879 100644 --- a/BezierKit/Library/Path+Data.swift +++ b/BezierKit/Library/Path+Data.swift @@ -51,14 +51,14 @@ private struct SerializationTypes { typealias Coordinate = Float64 } -@objc public extension Path { +public extension Path { private struct SerializationConstants { static let magicNumberVersion1: SerializationTypes.MagicNumber = 1223013157 // just a random number that helps us identify if the data is OK and saved in compatible version static let startComponentCommand: SerializationTypes.Command = 0 } - @objc(initWithData:) convenience init?(data: Data) { + convenience init?(data: Data) { var components: [PathComponent] = [] diff --git a/BezierKit/Library/Path+Projection.swift b/BezierKit/Library/Path+Projection.swift index f6875763..d500f533 100644 --- a/BezierKit/Library/Path+Projection.swift +++ b/BezierKit/Library/Path+Projection.swift @@ -48,12 +48,6 @@ public extension Path { return self.searchForClosestLocation(to: point, maximumDistance: .infinity, requireBest: true) } - #if !os(WASI) - @objc(point:isWithinDistanceOfBoundary:) func _pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { - return pointIsWithinDistanceOfBoundary(point, distance: distance) - } - #endif - func pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { return self.searchForClosestLocation(to: point, maximumDistance: distance, requireBest: false) != nil } @@ -112,12 +106,6 @@ public extension PathComponent { return result } - #if !os(WASI) - @objc(point:isWithinDistanceOfBoundary:) func _pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { - return pointIsWithinDistanceOfBoundary(point, distance: distance) - } - #endif - func pointIsWithinDistanceOfBoundary(_ point: CGPoint, distance: CGFloat) -> Bool { return self.searchForClosestLocation(to: point, maximumDistance: distance, requireBest: false) != nil } diff --git a/BezierKit/Library/Path+VectorBoolean.swift b/BezierKit/Library/Path+VectorBoolean.swift index f4452e5e..87a0d354 100644 --- a/BezierKit/Library/Path+VectorBoolean.swift +++ b/BezierKit/Library/Path+VectorBoolean.swift @@ -13,22 +13,10 @@ import Foundation public extension Path { - #if !os(WASI) - @objc(subtractPath:accuracy:) func _subtract(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { - return subtract(other, accuracy: accuracy) - } - #endif - func subtract(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { return self.performBooleanOperation(.subtract, with: other.reversed(), accuracy: accuracy) } - #if !os(WASI) - @objc(unionPath:accuracy:) func `_union`(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { - return union(other, accuracy: accuracy) - } - #endif - func `union`(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { guard self.isEmpty == false else { return other @@ -39,22 +27,10 @@ public extension Path { return self.performBooleanOperation(.union, with: other, accuracy: accuracy) } - #if !os(WASI) - @objc(intersectPath:accuracy:) func _intersect(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { - return intersect(other, accuracy: accuracy) - } - #endif - func intersect(_ other: Path, accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { return self.performBooleanOperation(.intersect, with: other, accuracy: accuracy) } - #if !os(WASI) - @objc(crossingsRemovedWithAccuracy:) func _crossingsRemoved(accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { - return crossingsRemoved(accuracy: accuracy) - } - #endif - func crossingsRemoved(accuracy: CGFloat=BezierKit.defaultIntersectionAccuracy) -> Path { let intersections = self.selfIntersections(accuracy: accuracy) let augmentedGraph = AugmentedGraph(path1: self, path2: self, intersections: intersections, operation: .removeCrossings) diff --git a/BezierKit/Library/PathComponent.swift b/BezierKit/Library/PathComponent.swift index 9d83e85e..bf064419 100644 --- a/BezierKit/Library/PathComponent.swift +++ b/BezierKit/Library/PathComponent.swift @@ -45,20 +45,11 @@ open class PathComponent: NSObject, Reversible, Transformable { public var numberOfElements: Int { return self.orders.count } - #if !os(WASI) - @objc(startingPoint) public var _startingPoint: CGPoint { - return startingPoint - } - #endif + public var startingPoint: CGPoint { return self.points[0] } - #if !os(WASI) - @objc(endingPoint) public var _endingPoint: CGPoint { - return endingPoint - } - #endif public var endingPoint: CGPoint { return self.points.last! } @@ -517,12 +508,6 @@ open class PathComponent: NSObject, Reversible, Transformable { return windingCountImpliesContainment(windingCount, using: rule) } - #if !os(WASI) - @objc(enumeratePointsIncludingControlPoints:usingBlock:) public func _enumeratePoints(includeControlPoints: Bool, using block: (CGPoint) -> Void) { - return enumeratePoints(includeControlPoints: includeControlPoints, using: block) - } - #endif - public func enumeratePoints(includeControlPoints: Bool, using block: (CGPoint) -> Void) { if includeControlPoints { for p in points { @@ -602,10 +587,6 @@ open class PathComponent: NSObject, Reversible, Transformable { } } -#if !os(WASI) -@objc(BezierKitPathComponent) extension PathComponent { } -#endif - public struct IndexedPathComponentLocation: Equatable, Comparable { public let elementIndex: Int public let t: CGFloat From c6b9a727ce32de337f54a7ca790861241f03b175 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 16:19:36 -0800 Subject: [PATCH 10/63] re-enable some code on Linux ... --- BezierKit/BezierKitTests/Path+VectorBooleanTests.swift | 3 --- BezierKit/BezierKitTests/TransformableTests.swift | 2 -- BezierKit/Library/Path.swift | 2 -- 3 files changed, 7 deletions(-) diff --git a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift index 59d2d81c..0719d071 100644 --- a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift +++ b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift @@ -12,7 +12,6 @@ import XCTest import CoreGraphics #endif -#if !os(WASI) private extension Path { /// copies the path in such a way that it's impossible that optimizations would allow the copy to share the same underlying storage func independentCopy() -> Path { @@ -794,5 +793,3 @@ class PathVectorBooleanTests: XCTestCase { #endif } - -#endif diff --git a/BezierKit/BezierKitTests/TransformableTests.swift b/BezierKit/BezierKitTests/TransformableTests.swift index d7150907..bce2c7d1 100644 --- a/BezierKit/BezierKitTests/TransformableTests.swift +++ b/BezierKit/BezierKitTests/TransformableTests.swift @@ -9,7 +9,6 @@ import XCTest @testable import BezierKit -#if canImport(CoreGraphics) class TransformableTests: XCTestCase { // rotates by 90 degrees ccw and then shifts (-1, 1) @@ -83,4 +82,3 @@ class TransformableTests: XCTestCase { } } -#endif diff --git a/BezierKit/Library/Path.swift b/BezierKit/Library/Path.swift index 85c7c33f..5a085cb7 100644 --- a/BezierKit/Library/Path.swift +++ b/BezierKit/Library/Path.swift @@ -369,13 +369,11 @@ open class Path: NSObject, NSSecureCoding { #endif } -#if canImport(CoreGraphics) extension Path: Transformable { public func copy(using t: CGAffineTransform) -> Self { return type(of: self).init(components: self.components.map { $0.copy(using: t)}) } } -#endif extension Path: Reversible { public func reversed() -> Self { From 630df268fb67067fc7b5f70dbc303c10b1814973 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 16:26:18 -0800 Subject: [PATCH 11/63] Linux why no run? --- BezierKit/BezierKitTests/Path+VectorBooleanTests.swift | 2 +- BezierKit/BezierKitTests/TransformableTests.swift | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift index 0719d071..15fe9378 100644 --- a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift +++ b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift @@ -7,10 +7,10 @@ // @testable import BezierKit -import XCTest #if canImport(CoreGraphics) import CoreGraphics #endif +import XCTest private extension Path { /// copies the path in such a way that it's impossible that optimizations would allow the copy to share the same underlying storage diff --git a/BezierKit/BezierKitTests/TransformableTests.swift b/BezierKit/BezierKitTests/TransformableTests.swift index bce2c7d1..1bc1cabd 100644 --- a/BezierKit/BezierKitTests/TransformableTests.swift +++ b/BezierKit/BezierKitTests/TransformableTests.swift @@ -37,8 +37,8 @@ class TransformableTests: XCTestCase { p1: CGPoint(x: 3, y: 1), p2: CGPoint(x: 7, y: -1)) XCTAssertEqual(q.copy(using: transform), QuadraticCurve(p0: CGPoint(x: 0, y: 0), - p1: CGPoint(x: -2, y: 4), - p2: CGPoint(x: 0, y: 8))) + p1: CGPoint(x: -2, y: 4), + p2: CGPoint(x: 0, y: 8))) } func testTransformCubicCurve() { @@ -47,9 +47,9 @@ class TransformableTests: XCTestCase { p2: CGPoint(x: 7, y: -1), p3: CGPoint(x: 8, y: 0)) XCTAssertEqual(c.copy(using: transform), CubicCurve(p0: CGPoint(x: 0, y: 0), - p1: CGPoint(x: -2, y: 4), - p2: CGPoint(x: 0, y: 8), - p3: CGPoint(x: -1, y: 9))) + p1: CGPoint(x: -2, y: 4), + p2: CGPoint(x: 0, y: 8), + p3: CGPoint(x: -1, y: 9))) } func testTransformPathComponent() { From 404c6a92f03a58b716ddc02f28b319f263221c0c Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 16:28:46 -0800 Subject: [PATCH 12/63] once again disable transformable tests if no core graphics. --- BezierKit/BezierKitTests/TransformableTests.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/BezierKit/BezierKitTests/TransformableTests.swift b/BezierKit/BezierKitTests/TransformableTests.swift index 1bc1cabd..d7150907 100644 --- a/BezierKit/BezierKitTests/TransformableTests.swift +++ b/BezierKit/BezierKitTests/TransformableTests.swift @@ -9,6 +9,7 @@ import XCTest @testable import BezierKit +#if canImport(CoreGraphics) class TransformableTests: XCTestCase { // rotates by 90 degrees ccw and then shifts (-1, 1) @@ -37,8 +38,8 @@ class TransformableTests: XCTestCase { p1: CGPoint(x: 3, y: 1), p2: CGPoint(x: 7, y: -1)) XCTAssertEqual(q.copy(using: transform), QuadraticCurve(p0: CGPoint(x: 0, y: 0), - p1: CGPoint(x: -2, y: 4), - p2: CGPoint(x: 0, y: 8))) + p1: CGPoint(x: -2, y: 4), + p2: CGPoint(x: 0, y: 8))) } func testTransformCubicCurve() { @@ -47,9 +48,9 @@ class TransformableTests: XCTestCase { p2: CGPoint(x: 7, y: -1), p3: CGPoint(x: 8, y: 0)) XCTAssertEqual(c.copy(using: transform), CubicCurve(p0: CGPoint(x: 0, y: 0), - p1: CGPoint(x: -2, y: 4), - p2: CGPoint(x: 0, y: 8), - p3: CGPoint(x: -1, y: 9))) + p1: CGPoint(x: -2, y: 4), + p2: CGPoint(x: 0, y: 8), + p3: CGPoint(x: -1, y: 9))) } func testTransformPathComponent() { @@ -82,3 +83,4 @@ class TransformableTests: XCTestCase { } } +#endif From 70ba115b01c68242704c0f0f7173281166455ebc Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 16:29:41 -0800 Subject: [PATCH 13/63] fix typo. --- .github/workflows/wasmBuild.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wasmBuild.yml b/.github/workflows/wasmBuild.yml index 282be473..d7ae09f4 100644 --- a/.github/workflows/wasmBuild.yml +++ b/.github/workflows/wasmBuild.yml @@ -1,4 +1,4 @@ -name: WebAssmelby build +name: WebAssembly build on: [push, pull_request] jobs: test: From 4859e2155ec4fd04d50f012dca722a4a3e9aab66 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 16:30:09 -0800 Subject: [PATCH 14/63] Revert "once again disable transformable tests if no core graphics." This reverts commit 404c6a92f03a58b716ddc02f28b319f263221c0c. --- BezierKit/BezierKitTests/TransformableTests.swift | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/BezierKit/BezierKitTests/TransformableTests.swift b/BezierKit/BezierKitTests/TransformableTests.swift index d7150907..1bc1cabd 100644 --- a/BezierKit/BezierKitTests/TransformableTests.swift +++ b/BezierKit/BezierKitTests/TransformableTests.swift @@ -9,7 +9,6 @@ import XCTest @testable import BezierKit -#if canImport(CoreGraphics) class TransformableTests: XCTestCase { // rotates by 90 degrees ccw and then shifts (-1, 1) @@ -38,8 +37,8 @@ class TransformableTests: XCTestCase { p1: CGPoint(x: 3, y: 1), p2: CGPoint(x: 7, y: -1)) XCTAssertEqual(q.copy(using: transform), QuadraticCurve(p0: CGPoint(x: 0, y: 0), - p1: CGPoint(x: -2, y: 4), - p2: CGPoint(x: 0, y: 8))) + p1: CGPoint(x: -2, y: 4), + p2: CGPoint(x: 0, y: 8))) } func testTransformCubicCurve() { @@ -48,9 +47,9 @@ class TransformableTests: XCTestCase { p2: CGPoint(x: 7, y: -1), p3: CGPoint(x: 8, y: 0)) XCTAssertEqual(c.copy(using: transform), CubicCurve(p0: CGPoint(x: 0, y: 0), - p1: CGPoint(x: -2, y: 4), - p2: CGPoint(x: 0, y: 8), - p3: CGPoint(x: -1, y: 9))) + p1: CGPoint(x: -2, y: 4), + p2: CGPoint(x: 0, y: 8), + p3: CGPoint(x: -1, y: 9))) } func testTransformPathComponent() { @@ -83,4 +82,3 @@ class TransformableTests: XCTestCase { } } -#endif From 610d0e9b2eb6323247b65b33505bc8d4dbfed97d Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 16:30:21 -0800 Subject: [PATCH 15/63] Revert "Linux why no run?" This reverts commit 630df268fb67067fc7b5f70dbc303c10b1814973. --- BezierKit/BezierKitTests/Path+VectorBooleanTests.swift | 2 +- BezierKit/BezierKitTests/TransformableTests.swift | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift index 15fe9378..0719d071 100644 --- a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift +++ b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift @@ -7,10 +7,10 @@ // @testable import BezierKit +import XCTest #if canImport(CoreGraphics) import CoreGraphics #endif -import XCTest private extension Path { /// copies the path in such a way that it's impossible that optimizations would allow the copy to share the same underlying storage diff --git a/BezierKit/BezierKitTests/TransformableTests.swift b/BezierKit/BezierKitTests/TransformableTests.swift index 1bc1cabd..bce2c7d1 100644 --- a/BezierKit/BezierKitTests/TransformableTests.swift +++ b/BezierKit/BezierKitTests/TransformableTests.swift @@ -37,8 +37,8 @@ class TransformableTests: XCTestCase { p1: CGPoint(x: 3, y: 1), p2: CGPoint(x: 7, y: -1)) XCTAssertEqual(q.copy(using: transform), QuadraticCurve(p0: CGPoint(x: 0, y: 0), - p1: CGPoint(x: -2, y: 4), - p2: CGPoint(x: 0, y: 8))) + p1: CGPoint(x: -2, y: 4), + p2: CGPoint(x: 0, y: 8))) } func testTransformCubicCurve() { @@ -47,9 +47,9 @@ class TransformableTests: XCTestCase { p2: CGPoint(x: 7, y: -1), p3: CGPoint(x: 8, y: 0)) XCTAssertEqual(c.copy(using: transform), CubicCurve(p0: CGPoint(x: 0, y: 0), - p1: CGPoint(x: -2, y: 4), - p2: CGPoint(x: 0, y: 8), - p3: CGPoint(x: -1, y: 9))) + p1: CGPoint(x: -2, y: 4), + p2: CGPoint(x: 0, y: 8), + p3: CGPoint(x: -1, y: 9))) } func testTransformPathComponent() { From c03ff24619a55a6f3e4971367044e11e08476248 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 16:30:32 -0800 Subject: [PATCH 16/63] Revert "re-enable some code on Linux ..." This reverts commit c6b9a727ce32de337f54a7ca790861241f03b175. --- BezierKit/BezierKitTests/Path+VectorBooleanTests.swift | 3 +++ BezierKit/BezierKitTests/TransformableTests.swift | 2 ++ BezierKit/Library/Path.swift | 2 ++ 3 files changed, 7 insertions(+) diff --git a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift index 0719d071..59d2d81c 100644 --- a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift +++ b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift @@ -12,6 +12,7 @@ import XCTest import CoreGraphics #endif +#if !os(WASI) private extension Path { /// copies the path in such a way that it's impossible that optimizations would allow the copy to share the same underlying storage func independentCopy() -> Path { @@ -793,3 +794,5 @@ class PathVectorBooleanTests: XCTestCase { #endif } + +#endif diff --git a/BezierKit/BezierKitTests/TransformableTests.swift b/BezierKit/BezierKitTests/TransformableTests.swift index bce2c7d1..d7150907 100644 --- a/BezierKit/BezierKitTests/TransformableTests.swift +++ b/BezierKit/BezierKitTests/TransformableTests.swift @@ -9,6 +9,7 @@ import XCTest @testable import BezierKit +#if canImport(CoreGraphics) class TransformableTests: XCTestCase { // rotates by 90 degrees ccw and then shifts (-1, 1) @@ -82,3 +83,4 @@ class TransformableTests: XCTestCase { } } +#endif diff --git a/BezierKit/Library/Path.swift b/BezierKit/Library/Path.swift index 5a085cb7..85c7c33f 100644 --- a/BezierKit/Library/Path.swift +++ b/BezierKit/Library/Path.swift @@ -369,11 +369,13 @@ open class Path: NSObject, NSSecureCoding { #endif } +#if canImport(CoreGraphics) extension Path: Transformable { public func copy(using t: CGAffineTransform) -> Self { return type(of: self).init(components: self.components.map { $0.copy(using: t)}) } } +#endif extension Path: Reversible { public func reversed() -> Self { From ac05f48b7df7399c630e5a067e0e753652d5eb4a Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 16:34:21 -0800 Subject: [PATCH 17/63] plz run --- .../BezierKitTests/Path+VectorBooleanTests.swift | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift index 59d2d81c..4f9116e0 100644 --- a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift +++ b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift @@ -12,13 +12,14 @@ import XCTest import CoreGraphics #endif -#if !os(WASI) +#if canImport(CoreGraphics) private extension Path { /// copies the path in such a way that it's impossible that optimizations would allow the copy to share the same underlying storage func independentCopy() -> Path { return self.copy(using: CGAffineTransform(translationX: 1, y: 0)).copy(using: CGAffineTransform(translationX: -1, y: 0)) } } +#endif class PathVectorBooleanTests: XCTestCase { @@ -140,6 +141,8 @@ class PathVectorBooleanTests: XCTestCase { ) } + #if canImport(CoreGraphics) // many of these tests rely on CGPath to build the test Paths + func testUnionSelf() { let square = createSquare1() let copy = square.independentCopy() @@ -147,8 +150,6 @@ class PathVectorBooleanTests: XCTestCase { XCTAssertEqual(square.union(copy), square) } - #if canImport(CoreGraphics) // many of these tests rely on CGPath to build the test Paths - func testUnionCoincidentEdges1() { // a simple test of union'ing two squares where the max/min x edge are coincident let square1 = Path(cgPath: CGPath(rect: CGRect(x: 0, y: 0, width: 1, height: 1), transform: nil)) @@ -356,6 +357,8 @@ class PathVectorBooleanTests: XCTestCase { ) } + #if canImport(CoreGraphics) + func testIntersectingSelf() { let square = createSquare1() XCTAssertEqual(square.intersect(square), square) @@ -368,9 +371,7 @@ class PathVectorBooleanTests: XCTestCase { XCTAssertEqual(square.subtract(square), expectedResult) XCTAssertEqual(square.subtract(square.independentCopy()), expectedResult) } - - #if canImport(CoreGraphics) - + func testSubtractingWindingDirection() { // this is a specific test of `subtracting` to ensure that when a component creates a "hole" // the order of the hole is reversed so that it is not contained in the shape when using .winding fill rule From 19f3193adb17678011957081eb5a36034d944e43 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 16:35:29 -0800 Subject: [PATCH 18/63] fix compilation issue. --- BezierKit/BezierKitTests/Path+VectorBooleanTests.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift index 4f9116e0..d1cb0c1a 100644 --- a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift +++ b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift @@ -795,5 +795,3 @@ class PathVectorBooleanTests: XCTestCase { #endif } - -#endif From b85f93016acf413f0f9cba4fbb7091a44250e7b7 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 16:41:38 -0800 Subject: [PATCH 19/63] enable test discovery --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f5a8750..99deeec3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,4 +15,4 @@ jobs: - name: Build run: swift build - name: Run tests - run: swift test + run: swift test --enable-test-discovery From 211e5b87d82a1519cfd2d5ffec79575f35f5705e Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 16:54:54 -0800 Subject: [PATCH 20/63] WIP --- BezierKit/BezierKitTests/Path+VectorBooleanTests.swift | 8 +++----- BezierKit/Library/Path.swift | 2 -- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift index d1cb0c1a..aa24ad2d 100644 --- a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift +++ b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift @@ -7,19 +7,17 @@ // @testable import BezierKit -import XCTest #if canImport(CoreGraphics) import CoreGraphics #endif +import XCTest -#if canImport(CoreGraphics) private extension Path { /// copies the path in such a way that it's impossible that optimizations would allow the copy to share the same underlying storage func independentCopy() -> Path { return self.copy(using: CGAffineTransform(translationX: 1, y: 0)).copy(using: CGAffineTransform(translationX: -1, y: 0)) } } -#endif class PathVectorBooleanTests: XCTestCase { @@ -142,7 +140,7 @@ class PathVectorBooleanTests: XCTestCase { } #if canImport(CoreGraphics) // many of these tests rely on CGPath to build the test Paths - + func testUnionSelf() { let square = createSquare1() let copy = square.independentCopy() @@ -371,7 +369,7 @@ class PathVectorBooleanTests: XCTestCase { XCTAssertEqual(square.subtract(square), expectedResult) XCTAssertEqual(square.subtract(square.independentCopy()), expectedResult) } - + func testSubtractingWindingDirection() { // this is a specific test of `subtracting` to ensure that when a component creates a "hole" // the order of the hole is reversed so that it is not contained in the shape when using .winding fill rule diff --git a/BezierKit/Library/Path.swift b/BezierKit/Library/Path.swift index 85c7c33f..5a085cb7 100644 --- a/BezierKit/Library/Path.swift +++ b/BezierKit/Library/Path.swift @@ -369,13 +369,11 @@ open class Path: NSObject, NSSecureCoding { #endif } -#if canImport(CoreGraphics) extension Path: Transformable { public func copy(using t: CGAffineTransform) -> Self { return type(of: self).init(components: self.components.map { $0.copy(using: t)}) } } -#endif extension Path: Reversible { public func reversed() -> Self { From 937f70fa7cb692cd766d09886e4019347b72934c Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 16:56:02 -0800 Subject: [PATCH 21/63] revert change --- BezierKit/BezierKitTests/Path+VectorBooleanTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift index aa24ad2d..a13a5a11 100644 --- a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift +++ b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift @@ -139,8 +139,6 @@ class PathVectorBooleanTests: XCTestCase { ) } - #if canImport(CoreGraphics) // many of these tests rely on CGPath to build the test Paths - func testUnionSelf() { let square = createSquare1() let copy = square.independentCopy() @@ -148,6 +146,8 @@ class PathVectorBooleanTests: XCTestCase { XCTAssertEqual(square.union(copy), square) } + #if canImport(CoreGraphics) // many of these tests rely on CGPath to build the test Paths + func testUnionCoincidentEdges1() { // a simple test of union'ing two squares where the max/min x edge are coincident let square1 = Path(cgPath: CGPath(rect: CGRect(x: 0, y: 0, width: 1, height: 1), transform: nil)) From 844aed7340251deff5b728ba60fa19efcf27b628 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 16:56:50 -0800 Subject: [PATCH 22/63] revert change --- BezierKit/BezierKitTests/Path+VectorBooleanTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift index a13a5a11..1a40d47f 100644 --- a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift +++ b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift @@ -355,8 +355,6 @@ class PathVectorBooleanTests: XCTestCase { ) } - #if canImport(CoreGraphics) - func testIntersectingSelf() { let square = createSquare1() XCTAssertEqual(square.intersect(square), square) @@ -369,6 +367,8 @@ class PathVectorBooleanTests: XCTestCase { XCTAssertEqual(square.subtract(square), expectedResult) XCTAssertEqual(square.subtract(square.independentCopy()), expectedResult) } + + #if canImport(CoreGraphics) func testSubtractingWindingDirection() { // this is a specific test of `subtracting` to ensure that when a component creates a "hole" From 88afbbccc7be697d4db5a2cc0cc447e72f7bc051 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 17:05:54 -0800 Subject: [PATCH 23/63] clarified obj-c initializers to try and not confuse compiler. --- BezierKit/Library/BezierKit-ObjC.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/BezierKit/Library/BezierKit-ObjC.swift b/BezierKit/Library/BezierKit-ObjC.swift index 28132a9f..6b1ae388 100644 --- a/BezierKit/Library/BezierKit-ObjC.swift +++ b/BezierKit/Library/BezierKit-ObjC.swift @@ -34,13 +34,13 @@ import Foundation return intersects(other, accuracy: accuracy) } - @objc(initWithComponents:) convenience init(_objc_components components: [PathComponent]) { - self.init(components: components) + @objc(initWithComponents:) convenience init(_objc_components: [PathComponent]) { + self.init(components: _objc_components) } #if canImport(CoreGraphics) - @objc(initWithCGPath:) convenience init(_objc_cgPath cgPath: CGPath) { - self.init(cgPath: cgPath) + @objc(initWithCGPath:) convenience init(_objc_cgPath: CGPath) { + self.init(cgPath: _objc_cgPath) } #endif @@ -85,8 +85,8 @@ public extension Path { // MARK: Path+Data.swift @available (*, unavailable) public extension Path { - @objc(initWithData:) convenience init?(_objc_data data: Data) { - self.init(data: data) + @objc(initWithData:) convenience init?(_objc_data: Data) { + self.init(data: _objc_data) } } From 20d22dda356187a5b1343d6af6d414db92ae15a1 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 17:06:15 -0800 Subject: [PATCH 24/63] Linting --- BezierKit/BezierKitTests/Path+VectorBooleanTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift index 1a40d47f..15fe9378 100644 --- a/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift +++ b/BezierKit/BezierKitTests/Path+VectorBooleanTests.swift @@ -367,7 +367,7 @@ class PathVectorBooleanTests: XCTestCase { XCTAssertEqual(square.subtract(square), expectedResult) XCTAssertEqual(square.subtract(square.independentCopy()), expectedResult) } - + #if canImport(CoreGraphics) func testSubtractingWindingDirection() { From 2057da604e32957d1e4e07721dd3523664e45579 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 17:31:38 -0800 Subject: [PATCH 25/63] get rid of Obj-C classnames because they arent exported anyway. --- BezierKit/Library/BezierKit-ObjC.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BezierKit/Library/BezierKit-ObjC.swift b/BezierKit/Library/BezierKit-ObjC.swift index 6b1ae388..9143d301 100644 --- a/BezierKit/Library/BezierKit-ObjC.swift +++ b/BezierKit/Library/BezierKit-ObjC.swift @@ -16,7 +16,7 @@ import Foundation // MARK: Path.swift @available (*, unavailable) -@objc(BezierKitPath) public extension Path { +@objc public extension Path { @objc(isEmpty) var _objc_isEmpty: Bool { return isEmpty @@ -107,7 +107,7 @@ public extension PathComponent { // MARK: PathComponent.swift @available(*, unavailable) -@objc(BezierKitPathComponent) public extension PathComponent { +public extension PathComponent { @objc(startingPoint) var _objc_startingPoint: CGPoint { return startingPoint } From c8cc827e57ad7b918cd0d543dcb646dd29b48e11 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 17:33:32 -0800 Subject: [PATCH 26/63] no longer using travis for CI, using Git actions instead. --- .travis.yml | 36 ------------------------------------ README.md | 1 - travis/before_install.sh | 13 ------------- travis/script.sh | 25 ------------------------- 4 files changed, 75 deletions(-) delete mode 100644 .travis.yml delete mode 100755 travis/before_install.sh delete mode 100755 travis/script.sh diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 169f4177..00000000 --- a/.travis.yml +++ /dev/null @@ -1,36 +0,0 @@ -env: - global: - - LC_CTYPE=en_US.UTF-8 - - LANG=en_US.UTF-8 - - PROJECT=BezierKit/BezierKit.xcodeproj - - IOS_SCHEME=BezierKit_iOS - - IOS_SDK=iphonesimulator13.2 - - OSX_SCHEME=BezierKit_Mac - - OSX_SDK=macosx10.15 -jobs: - include: - - name: Linux SPM - os: linux - dist: bionic - env: SWIFT_VERSION=swift-5.3-RELEASE - - name: iOS Min System - os: osx - osx_image: xcode11.2 - env: DESTINATION="OS=10.3.1,name=iPhone 5" SCHEME="$IOS_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" POD_LINT="NO" - - name: iOS - os: osx - osx_image: xcode11.2 - env: DESTINATION="OS=13.2.2,name=iPad Pro (12.9-inch) (3rd generation)" SCHEME="$IOS_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" POD_LINT="NO" - - name: MacOS - os: osx - osx_image: xcode11.2 - env: DESTINATION="arch=x86_64"SCHEME="$OSX_SCHEME" SDK="$OSX_SDK" RUN_TESTS="YES" POD_LINT="NO" - - name: WebAssembly - os: linux -before_install: - - ./travis/before_install.sh -script: - - ./travis/script.sh -after_success: - - bash <(curl -s https://codecov.io/bash) - - sleep 5 diff --git a/README.md b/README.md index 5e0a60c8..71be4b12 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # BezierKit -[![Build Status](https://travis-ci.org/hfutrell/BezierKit.svg?branch=master)](https://travis-ci.org/hfutrell/BezierKit) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![codecov](https://codecov.io/gh/hfutrell/BezierKit/branch/master/graph/badge.svg)](https://codecov.io/gh/hfutrell/BezierKit) [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/BezierKit.svg)](https://img.shields.io/cocoapods/v/BezierKit.svg) diff --git a/travis/before_install.sh b/travis/before_install.sh deleted file mode 100755 index 63b0ecf6..00000000 --- a/travis/before_install.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -if [[ $TRAVIS_OS_NAME = 'osx' ]]; then - # install macOS prerequistes - : -elif [[ $TRAVIS_OS_NAME = 'linux' ]]; then - if [[ $TRAVIS_JOB_NAME = 'WebAssembly' ]]; then - docker pull ghcr.io/swiftwasm/carton:0.12.1 - else - wget https://swift.org/builds/swift-5.3-release/ubuntu1804/${SWIFT_VERSION}/${SWIFT_VERSION}-ubuntu18.04.tar.gz - tar xzf ${SWIFT_VERSION}-ubuntu18.04.tar.gz - fi -fi diff --git a/travis/script.sh b/travis/script.sh deleted file mode 100755 index 101c351b..00000000 --- a/travis/script.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -if [[ $TRAVIS_OS_NAME = 'osx' ]]; then - echo "project = $PROJECT, DESTINATION = $DESTINATION" - set -o pipefail - xcodebuild -version - xcodebuild -showsdks - # Build Framework in Debug and Run Tests if specified - if [ $RUN_TESTS == "YES" ]; then - xcodebuild -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO test | xcpretty -c; - else - xcodebuild -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty -c; - fi - # Run `pod spec lint` if specified - if [ $POD_LINT == "YES" ]; then - pod spec lint; - fi -elif [[ $TRAVIS_OS_NAME = 'linux' ]]; then - if [[ $TRAVIS_JOB_NAME = 'WebAssembly' ]]; then - docker run -v `pwd`:`pwd` -w `pwd` ghcr.io/swiftwasm/carton:latest /bin/sh -c 'carton test' - else - export PATH="${PWD}/${SWIFT_VERSION}-ubuntu18.04/usr/bin:${PATH}" - swift test --enable-test-discovery - fi -fi From a755d0843e85726d650f63521e90a5e9cb699a36 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 11 Mar 2022 17:35:53 -0800 Subject: [PATCH 27/63] increment version # --- BezierKit.podspec | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BezierKit.podspec b/BezierKit.podspec index ef689d48..77d7011b 100644 --- a/BezierKit.podspec +++ b/BezierKit.podspec @@ -5,7 +5,7 @@ Pod::Spec.new do |s| s.name = "BezierKit" - s.version = "0.13.0" + s.version = "0.14.0" s.summary = "comprehensive Bezier Path library written in Swift" s.homepage = "https://github.com/hfutrell/BezierKit" s.license = "MIT" diff --git a/README.md b/README.md index 71be4b12..1aaa0184 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ To integrate BezierKit into your Xcode project using CocoaPods, add it to your t ```ruby target '' do - pod 'BezierKit', '>= 0.13.0' + pod 'BezierKit', '>= 0.14.0' end ``` @@ -66,7 +66,7 @@ import PackageDescription let package = Package( name: "", dependencies: [ - .package(url: "https://github.com/hfutrell/BezierKit.git", from: "0.13.0"), + .package(url: "https://github.com/hfutrell/BezierKit.git", from: "0.14.0"), ] ) ``` From 1e9d8982a08ba98668436c752d2058cbc03398c3 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Mon, 21 Mar 2022 17:38:29 -0700 Subject: [PATCH 28/63] attempt to re-engage code coverage --- .github/workflows/ci.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99deeec3..2e4ce2a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,16 @@ jobs: steps: - uses: actions/checkout@v2 + - uses: codecov/codecov-action@v2 + with: + fail_ci_if_error: true # optional (default = false) + verbose: true # optional (default = false) - name: Build run: swift build - name: Run tests - run: swift test --enable-test-discovery + run: swift test --enable-test-discovery --enable-code-coverage + - name: Mac upload coverage to codecov.io + if: startsWith(matrix.os, 'macos') + run: | + xcrun llvm-cov export -format="lcov" .build/debug/BezierKitPackageTests.xctest/Contents/MacOS/BezierKitPackageTests -instr-profile .build/debug/codecov/default.profdata + bash <(curl -s https://codecov.io/bash) \ No newline at end of file From b7418de92e5bf62a85e22588b1499ff3037a8dde Mon Sep 17 00:00:00 2001 From: hfutrell Date: Mon, 21 Mar 2022 17:47:43 -0700 Subject: [PATCH 29/63] try codecov again. --- .github/workflows/ci.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e4ce2a1..673711c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,16 +12,13 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: codecov/codecov-action@v2 - with: - fail_ci_if_error: true # optional (default = false) - verbose: true # optional (default = false) - name: Build run: swift build - name: Run tests run: swift test --enable-test-discovery --enable-code-coverage - name: Mac upload coverage to codecov.io + uses: codecov/codecov-action@v1.0.13 if: startsWith(matrix.os, 'macos') - run: | - xcrun llvm-cov export -format="lcov" .build/debug/BezierKitPackageTests.xctest/Contents/MacOS/BezierKitPackageTests -instr-profile .build/debug/codecov/default.profdata - bash <(curl -s https://codecov.io/bash) \ No newline at end of file + run: xcrun llvm-cov export -format="lcov" .build/debug/BezierKitPackageTests.xctest/Contents/MacOS/BezierKitPackageTests -instr-profile .build/debug/codecov/default.profdata > info.lcov + with: + file: info.lcov \ No newline at end of file From 6210eac731048987f5b2748f3d7beb9a48542895 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Mon, 21 Mar 2022 17:52:04 -0700 Subject: [PATCH 30/63] try again. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 673711c4..1ca146b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: run: swift test --enable-test-discovery --enable-code-coverage - name: Mac upload coverage to codecov.io uses: codecov/codecov-action@v1.0.13 + with: + file: info.lcov if: startsWith(matrix.os, 'macos') run: xcrun llvm-cov export -format="lcov" .build/debug/BezierKitPackageTests.xctest/Contents/MacOS/BezierKitPackageTests -instr-profile .build/debug/codecov/default.profdata > info.lcov - with: - file: info.lcov \ No newline at end of file From 6ecdffb3f7433f87413d101487a851571f2c2044 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Mon, 21 Mar 2022 18:01:39 -0700 Subject: [PATCH 31/63] try again. --- .github/workflows/ci.yml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ca146b8..3326732a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,21 +4,17 @@ on: [push, pull_request] jobs: build: - name: BezierKit on ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest] - runs-on: ${{ matrix.os }} - + name: BezierKit on Mac + runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: Build run: swift build - name: Run tests - run: swift test --enable-test-discovery --enable-code-coverage - - name: Mac upload coverage to codecov.io + run: | + swift test --enable-test-discovery --enable-code-coverage + xcrun llvm-cov export -format="lcov" .build/debug/BezierKitPackageTests.xctest/Contents/MacOS/BezierKitPackageTests -instr-profile .build/debug/codecov/default.profdata > info.lcov + - name: code coverage upload to codecov.io uses: codecov/codecov-action@v1.0.13 with: file: info.lcov - if: startsWith(matrix.os, 'macos') - run: xcrun llvm-cov export -format="lcov" .build/debug/BezierKitPackageTests.xctest/Contents/MacOS/BezierKitPackageTests -instr-profile .build/debug/codecov/default.profdata > info.lcov From 201abae21382838134073d0908ee81b4ca218f84 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Mon, 21 Mar 2022 18:06:26 -0700 Subject: [PATCH 32/63] re-enable Linux tests. --- .github/workflows/ci.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3326732a..12631a92 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ on: [push, pull_request] jobs: build: - name: BezierKit on Mac + name: Mac runs-on: macos-latest steps: - uses: actions/checkout@v2 @@ -18,3 +18,11 @@ jobs: uses: codecov/codecov-action@v1.0.13 with: file: info.lcov + name: Linux + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build + run: swift build + - name: Run tests + run: swift test --enable-test-discovery \ No newline at end of file From 60a7b99a5fc9f35ed871a75a2b112f83f8be2006 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Mon, 21 Mar 2022 18:10:10 -0700 Subject: [PATCH 33/63] attempt to fix syntax error. --- .github/workflows/ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12631a92..6ec2a2e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,8 +3,7 @@ name: BezierKit Mac + Linux on: [push, pull_request] jobs: - build: - name: Mac + Mac: runs-on: macos-latest steps: - uses: actions/checkout@v2 @@ -18,7 +17,7 @@ jobs: uses: codecov/codecov-action@v1.0.13 with: file: info.lcov - name: Linux + Linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 From c1f2a7ea185fdfa6b25a15267a03f1c350ad9721 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 25 Mar 2022 15:42:20 -0700 Subject: [PATCH 34/63] WIP --- .../Library/BezierCurve+Implicitization.swift | 10 +- .../Library/BezierCurve+Intersection.swift | 91 ++++++++++++------- 2 files changed, 68 insertions(+), 33 deletions(-) diff --git a/BezierKit/Library/BezierCurve+Implicitization.swift b/BezierKit/Library/BezierCurve+Implicitization.swift index 2404559f..517ebf07 100644 --- a/BezierKit/Library/BezierCurve+Implicitization.swift +++ b/BezierKit/Library/BezierCurve+Implicitization.swift @@ -113,7 +113,7 @@ internal struct ImplicitPolynomial { } } -private struct ImplicitLineProduct { +struct ImplicitLineProduct { var a20, a11, a10, a02, a01, a00: CGFloat static func * (left: ImplicitLine, right: ImplicitLineProduct) -> ImplicitPolynomial { let a00 = left.a00 * right.a00 @@ -152,7 +152,7 @@ private struct ImplicitLineProduct { } } -private struct ImplicitLine { +struct ImplicitLine { var a10, a01, a00: CGFloat static func * (left: ImplicitLine, right: ImplicitLine) -> ImplicitLineProduct { return ImplicitLineProduct(a20: left.a10 * right.a10, @@ -170,6 +170,12 @@ private struct ImplicitLine { a01: left.a01 + right.a01, a00: left.a00 + right.a00) } + func value(at point: CGPoint) -> CGFloat { + return a10 * point.x + a01 * point.y + a00 + } + var d: CGPoint { + return CGPoint(x: a10, y: a01) + } } private extension BezierCurve { diff --git a/BezierKit/Library/BezierCurve+Intersection.swift b/BezierKit/Library/BezierCurve+Intersection.swift index a73fefc1..e974f666 100644 --- a/BezierKit/Library/BezierCurve+Intersection.swift +++ b/BezierKit/Library/BezierCurve+Intersection.swift @@ -115,6 +115,11 @@ fileprivate extension BezierCurve { } } +private func implicitLine(_ pi: CGPoint, _ pj: CGPoint, _ k: Int) -> ImplicitLine { + return CGFloat(k) * ImplicitLine(a10: pi.y - pj.y, a01: pj.x - pi.x, a00: pi.x * pj.y - pj.x * pi.y) +} + + internal func helperIntersectsCurveCurve(_ curve1: Subcurve, _ curve2: Subcurve, accuracy: CGFloat) -> [Intersection] where U: NonlinearBezierCurve, T: NonlinearBezierCurve { // try intersecting using subdivision @@ -122,45 +127,69 @@ internal func helperIntersectsCurveCurve(_ curve1: Subcurve, _ curve2: let rb = curve2.curve.boundingBox var pairIntersections: [Intersection] = [] var subdivisionIterations = 0 - if Utils.pairiteration(curve1, curve2, lb, rb, &pairIntersections, accuracy, &subdivisionIterations) { - return pairIntersections.sortedAndUniqued() - } - - // subdivision failed, check if the curves are coincident +// if Utils.pairiteration(curve1, curve2, lb, rb, &pairIntersections, accuracy, &subdivisionIterations) { +// return pairIntersections.sortedAndUniqued() +// } +// +// // subdivision failed, check if the curves are coincident let insignificantDistance: CGFloat = 0.5 * accuracy - if let coincidence = coincidenceCheck(curve1.curve, curve2.curve, accuracy: 0.1 * accuracy) { - return coincidence - } - - // find any intersections using curve implicitization - let transform = CGAffineTransform(translationX: -curve2.curve.startingPoint.x, y: -curve2.curve.startingPoint.y) - let c2 = curve2.curve.downgradedIfPossible(maximumError: insignificantDistance).copy(using: transform) - - let c1 = curve1.curve.copy(using: transform) - let equation: BernsteinPolynomialN = c2.implicitPolynomial.value(c1.xPolynomial, c1.yPolynomial) +// if let coincidence = coincidenceCheck(curve1.curve, curve2.curve, accuracy: 0.1 * accuracy) { +// return coincidence +// } + + let k = implicitLine(curve2.curve.points[2], curve2.curve.points[0], 1) + let l = implicitLine(curve2.curve.points[2], curve2.curve.points[1], 2) + let m = implicitLine(curve2.curve.points[1], curve2.curve.points[0], 2) + //let lineProduct = k * k - l * m + + let pStart = curve1.curve.startingPoint + let pEnd = curve1.curve.endingPoint + + func f(_ t: CGFloat) -> CGFloat { + let p = curve1.curve.point(at: t) + return k.value(at: p) * k.value(at: p) - l.value(at: p) * m.value(at: p) + } + func df(_ t: CGFloat) -> CGFloat { + let p = curve1.curve.point(at: t) + let pp = curve1.curve.derivative(at: t) + let temp = 2.0 * k.value(at: p) * k.d - l.value(at: p) * m.d - m.value(at: p) * l.d + #warning(" i haev no idea why / 2 is needed") + return temp.dot(pp) / 2 + } + func ddf(_ t: CGFloat) -> CGFloat { + let p = curve1.curve.point(at: t) + let pp = curve1.curve.derivative(at: t) + + let ppp = 2.0 * (curve1.curve.points[2] - 2.0 * curve1.curve.points[1] + curve1.curve.points[0]) + + let temp = (2.0 * k.value(at: p) * k.d - l.value(at: p) * m.d - m.value(at: p) * l.d).dot(ppp) / 2 + return temp + (2.0 * k.d.dot(k.d) - 2.0 * l.d.dot(m.d)) * p.dot(pp) / 2 + } + + let n = 2 + let p0 = f(0) + let p1 = df(0) / CGFloat(n) + p0 + let p2 = ddf(1) / CGFloat(n) / CGFloat(n-1) + 2 * p1 - p0 + let p4 = f(1) + let p3 = -df(1) / CGFloat(n) + p4 + + let c1 = curve1.curve + let c2 = curve2.curve + + let equation = BernsteinPolynomialN(coefficients: [p0, p1, p2, p3, p4]) + + let equation2: BernsteinPolynomialN = -1 * c2.implicitPolynomial.value(c1.xPolynomial, c1.yPolynomial) + + let roots = equation.distinctRealRootsInUnitInterval(configuration: RootFindingConfiguration(errorThreshold: RootFindingConfiguration.minimumErrorThreshold)) - let t1Tolerance = insignificantDistance / c1.derivativeBounds - let t2Tolerance = insignificantDistance / c2.derivativeBounds - func intersectionIfCloseEnough(at t1: CGFloat) -> Intersection? { let point = c1.point(at: t1) - guard c2.boundingBox.contains(point) else { return nil } - var t2 = c2.project(point).t - if t2 < t2Tolerance { - t2 = 0 - } else if t2 > 1 - t2Tolerance { - t2 = 1 - } - guard distance(point, c2.point(at: t2)) < accuracy else { return nil } + let t2 = c2.project(point).t return Intersection(t1: t1, t2: t2) } + var intersections = roots.compactMap { t1 -> Intersection? in - if t1 < t1Tolerance { - return nil // (t1 near 0 handled explicitly) - } else if t1 > 1 - t1Tolerance { - return nil // (t1 near 1 handled explicitly) - } return intersectionIfCloseEnough(at: t1) } if intersections.contains(where: { $0.t1 == 0 }) == false { From bc4c1d851762f42acb22126790c9bd342b65bf2a Mon Sep 17 00:00:00 2001 From: Holmes Futrell Date: Mon, 28 Mar 2022 16:14:09 -0700 Subject: [PATCH 35/63] fix derivatives. --- .../Library/BezierCurve+Intersection.swift | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/BezierKit/Library/BezierCurve+Intersection.swift b/BezierKit/Library/BezierCurve+Intersection.swift index e974f666..fc5b4811 100644 --- a/BezierKit/Library/BezierCurve+Intersection.swift +++ b/BezierKit/Library/BezierCurve+Intersection.swift @@ -119,6 +119,27 @@ private func implicitLine(_ pi: CGPoint, _ pj: CGPoint, _ k: Int) -> ImplicitLin return CGFloat(k) * ImplicitLine(a10: pi.y - pj.y, a01: pj.x - pi.x, a00: pi.x * pj.y - pj.x * pi.y) } +func f(_ k: ImplicitLine, _ l: ImplicitLine, _ m: ImplicitLine, _ curve: U, _ t: CGFloat) -> CGFloat { + let p = curve.point(at: t) + return k.value(at: p) * k.value(at: p) - l.value(at: p) * m.value(at: p) +} + +func df(_ k: ImplicitLine, _ l: ImplicitLine, _ m: ImplicitLine, _ curve: U, _ t: CGFloat) -> CGFloat { + let p = curve.point(at: t) + let pp = curve.derivative(at: t) + let temp = 2.0 * k.value(at: p) * k.d - l.value(at: p) * m.d - m.value(at: p) * l.d + return temp.dot(pp) +} + +func ddf(_ k: ImplicitLine, _ l: ImplicitLine, _ m: ImplicitLine, _ curve: U, _ t: CGFloat) -> CGFloat { + let p = curve.point(at: t) + let pp = curve.derivative(at: t) + let ppp = 2.0 * (curve.points[2] - 2.0 * curve.points[1] + curve.points[0]) + let temp1 = (2.0 * k.value(at: p) * k.d - l.value(at: p) * m.d - m.value(at: p) * l.d).dot(ppp) + let temp2 = (2.0 * k.d.dot(pp) * k.d - l.d.dot(pp) * m.d - m.d.dot(pp) * l.d).dot(pp) + print("temp1 = \(temp1), temp2 = \(temp2), sum = \(temp1 + temp2)") + return temp1 + temp2 +} internal func helperIntersectsCurveCurve(_ curve1: Subcurve, _ curve2: Subcurve, accuracy: CGFloat) -> [Intersection] where U: NonlinearBezierCurve, T: NonlinearBezierCurve { @@ -132,7 +153,7 @@ internal func helperIntersectsCurveCurve(_ curve1: Subcurve, _ curve2: // } // // // subdivision failed, check if the curves are coincident - let insignificantDistance: CGFloat = 0.5 * accuracy +// let insignificantDistance: CGFloat = 0.5 * accuracy // if let coincidence = coincidenceCheck(curve1.curve, curve2.curve, accuracy: 0.1 * accuracy) { // return coincidence // } @@ -145,42 +166,21 @@ internal func helperIntersectsCurveCurve(_ curve1: Subcurve, _ curve2: let pStart = curve1.curve.startingPoint let pEnd = curve1.curve.endingPoint - func f(_ t: CGFloat) -> CGFloat { - let p = curve1.curve.point(at: t) - return k.value(at: p) * k.value(at: p) - l.value(at: p) * m.value(at: p) - } - func df(_ t: CGFloat) -> CGFloat { - let p = curve1.curve.point(at: t) - let pp = curve1.curve.derivative(at: t) - let temp = 2.0 * k.value(at: p) * k.d - l.value(at: p) * m.d - m.value(at: p) * l.d - #warning(" i haev no idea why / 2 is needed") - return temp.dot(pp) / 2 - } - func ddf(_ t: CGFloat) -> CGFloat { - let p = curve1.curve.point(at: t) - let pp = curve1.curve.derivative(at: t) - - let ppp = 2.0 * (curve1.curve.points[2] - 2.0 * curve1.curve.points[1] + curve1.curve.points[0]) - - let temp = (2.0 * k.value(at: p) * k.d - l.value(at: p) * m.d - m.value(at: p) * l.d).dot(ppp) / 2 - return temp + (2.0 * k.d.dot(k.d) - 2.0 * l.d.dot(m.d)) * p.dot(pp) / 2 - } - - let n = 2 - let p0 = f(0) - let p1 = df(0) / CGFloat(n) + p0 - let p2 = ddf(1) / CGFloat(n) / CGFloat(n-1) + 2 * p1 - p0 - let p4 = f(1) - let p3 = -df(1) / CGFloat(n) + p4 + let n = 4 + let p0 = f(k, l, m, curve1.curve, 0) + let p1 = df(k, l, m, curve1.curve, 0) / CGFloat(n) + p0 + let p2 = ddf(k, l, m, curve1.curve, 0) / CGFloat(n) / CGFloat(n-1) + 2 * p1 - p0 + let p4 = f(k, l, m, curve1.curve, 1) + let p3 = -df(k, l, m, curve1.curve, 1) / CGFloat(n) + p4 let c1 = curve1.curve let c2 = curve2.curve let equation = BernsteinPolynomialN(coefficients: [p0, p1, p2, p3, p4]) - let equation2: BernsteinPolynomialN = -1 * c2.implicitPolynomial.value(c1.xPolynomial, c1.yPolynomial) - + print("expected \(equation2.derivative.derivative.value(at: 0))") + let roots = equation.distinctRealRootsInUnitInterval(configuration: RootFindingConfiguration(errorThreshold: RootFindingConfiguration.minimumErrorThreshold)) func intersectionIfCloseEnough(at t1: CGFloat) -> Intersection? { @@ -188,7 +188,7 @@ internal func helperIntersectsCurveCurve(_ curve1: Subcurve, _ curve2: let t2 = c2.project(point).t return Intersection(t1: t1, t2: t2) } - + var intersections = roots.compactMap { t1 -> Intersection? in return intersectionIfCloseEnough(at: t1) } From 4514451424e67434bce95094c724cf2ec04c3de5 Mon Sep 17 00:00:00 2001 From: Holmes Futrell Date: Mon, 16 May 2022 13:19:39 -0700 Subject: [PATCH 36/63] Obj-C getter for data --- BezierKit/Library/BezierKit-ObjC.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/BezierKit/Library/BezierKit-ObjC.swift b/BezierKit/Library/BezierKit-ObjC.swift index 9143d301..90d2ec4a 100644 --- a/BezierKit/Library/BezierKit-ObjC.swift +++ b/BezierKit/Library/BezierKit-ObjC.swift @@ -88,6 +88,9 @@ public extension Path { @objc(initWithData:) convenience init?(_objc_data: Data) { self.init(data: _objc_data) } + @objc(data) var _objc_data: Data { + return data + } } // MARK: Path+Projection.swift From a8ad3385f0134d41cc112912cbdb16384e66f699 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 15 Jun 2022 16:05:34 +0000 Subject: [PATCH 37/63] Add lightweight Stream API to avoid using InputStream All `Stream` families are not available WebAssembly toolchain for now since it depends on `RunLoop` and libdispatch, which is unavailable on Wasm. --- BezierKit/Library/Path+Data.swift | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/BezierKit/Library/Path+Data.swift b/BezierKit/Library/Path+Data.swift index bbe2e879..d7c9fd12 100644 --- a/BezierKit/Library/Path+Data.swift +++ b/BezierKit/Library/Path+Data.swift @@ -24,14 +24,30 @@ fileprivate extension Data { } } -fileprivate extension InputStream { - func readNativeValue(_ value: UnsafeMutablePointer) -> Bool { +fileprivate struct DataStream { + var dataCursor: Data.Index + let data: Data + + init(data: Data) { + self.data = data + self.dataCursor = data.startIndex + } + + mutating func read(_ buffer: UnsafeMutablePointer, maxLength: Int) -> Int { + let startIndex = dataCursor + let endIndex = min(dataCursor + maxLength, data.count) + data.copyBytes(to: buffer, from: startIndex..(_ value: UnsafeMutablePointer) -> Bool { let size = MemoryLayout.size return value.withMemoryRebound(to: UInt8.self, capacity: size) { self.read($0, maxLength: size) == size } } - func appendNativeValues(to array: inout [T], count: Int) -> Bool { + mutating func appendNativeValues(to array: inout [T], count: Int) -> Bool { guard count > 0 else { return true } let size = count * MemoryLayout.stride let buffer = UnsafeMutableBufferPointer.allocate(capacity: size) @@ -65,8 +81,7 @@ public extension Path { var commandCount: SerializationTypes.CommandCount = 0 var commands: [SerializationTypes.Command] = [] - let stream = InputStream(data: data) - stream.open() + var stream = DataStream(data: data) // check the magic number var magic = SerializationTypes.MagicNumber.max From 76b6e5a6bfe14b91d95730943c69b0329367be61 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 15 Jun 2022 16:09:06 +0000 Subject: [PATCH 38/63] Unlock Path+Data.swift on os(WASI) NSCoder APIs are still unavailable on WASI since PropertyListSerialization, which is used internally, is also unavailable for now. --- BezierKit/BezierKitTests/Path+DataTests.swift | 4 ---- BezierKit/Library/Path+Data.swift | 3 --- 2 files changed, 7 deletions(-) diff --git a/BezierKit/BezierKitTests/Path+DataTests.swift b/BezierKit/BezierKitTests/Path+DataTests.swift index bc2871d2..005019dc 100644 --- a/BezierKit/BezierKitTests/Path+DataTests.swift +++ b/BezierKit/BezierKitTests/Path+DataTests.swift @@ -196,12 +196,10 @@ class PathDataTests: XCTestCase { #endif - #if !os(WASI) func testEmptyData() { let path = Path(data: Data()) XCTAssertEqual(path, nil) } - #endif let simpleRectangle = { Path(rect: CGRect(x: 1, y: 2, width: 3, height: 4)) @@ -209,7 +207,6 @@ class PathDataTests: XCTestCase { let expectedSimpleRectangleData = Data(base64Encoded: "JbPlSAUAAAAAAQEBAQAAAAAAAPA/AAAAAAAAAEAAAAAAAAAQQAAAAAAAAABAAAAAAAAAEEAAAAAAAAAYQAAAAAAAAPA/AAAAAAAAGEAAAAAAAADwPwAAAAAAAABA")! - #if !os(WASI) func testSimpleRectangle() { XCTAssertEqual(simpleRectangle.data, expectedSimpleRectangleData) } @@ -239,5 +236,4 @@ class PathDataTests: XCTestCase { let corruptData6 = data[0..<10] // commands cut off XCTAssertEqual(Path(data: corruptData6), nil) } - #endif } diff --git a/BezierKit/Library/Path+Data.swift b/BezierKit/Library/Path+Data.swift index d7c9fd12..48f93678 100644 --- a/BezierKit/Library/Path+Data.swift +++ b/BezierKit/Library/Path+Data.swift @@ -6,8 +6,6 @@ // Copyright © 2019 Holmes Futrell. All rights reserved. // -#if !os(WASI) - import Foundation #if canImport(CoreGraphics) import CoreGraphics @@ -163,4 +161,3 @@ public extension Path { return result } } -#endif From 8055542ee9685875c5e44b56a8bfd3005a4f0b8e Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 17 Jun 2022 11:37:05 +0000 Subject: [PATCH 39/63] Unlock NSObject.hash API in Path on os(WASI) --- BezierKit/Library/Path.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/BezierKit/Library/Path.swift b/BezierKit/Library/Path.swift index 5a085cb7..1c750bbe 100644 --- a/BezierKit/Library/Path.swift +++ b/BezierKit/Library/Path.swift @@ -352,7 +352,6 @@ open class Path: NSObject, NSSecureCoding { return outerComponents.values.map { Path(components: $0) } } - #if !os(WASI) public override var hash: Int { // override is needed because NSObject hashing is independent of Swift's Hashable return lock.sync { @@ -366,7 +365,6 @@ open class Path: NSObject, NSSecureCoding { return h } } - #endif } extension Path: Transformable { From 81544cc84b873f8e859871fdfdb926d97684098e Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 17 Jun 2022 13:12:32 +0000 Subject: [PATCH 40/63] Support platforms where CGFloat is 32bit on tests --- BezierKit/BezierKitTests/PolynomialTests.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/BezierKit/BezierKitTests/PolynomialTests.swift b/BezierKit/BezierKitTests/PolynomialTests.swift index ed7527d1..2fbcdb76 100644 --- a/BezierKit/BezierKitTests/PolynomialTests.swift +++ b/BezierKit/BezierKitTests/PolynomialTests.swift @@ -93,16 +93,24 @@ class PolynomialTests: XCTestCase { XCTAssertEqual(roots[2], 1, accuracy: accuracy) XCTAssertEqual(roots[3], 1.2, accuracy: accuracy) } - #if !os(WASI) + func testDegree4RepeatedRoots() { // x^4 - 2x^2 + 1 let polynomial = BernsteinPolynomial4(b0: 1, b1: 1, b2: 2.0 / 3.0, b3: 0, b4: 0) let roots = findDistinctRoots(of: polynomial, between: -2, and: 2) + // On platforms where CGFloat is 32bit + #if arch(i386) || arch(arm) || arch(wasm32) + XCTAssertEqual(roots.count, 3) + XCTAssertEqual(roots[0], -1.0000075, accuracy: accuracy) + XCTAssertEqual(roots[1], -0.99948174, accuracy: accuracy) + XCTAssertEqual(roots[2], 1, accuracy: accuracy) + #else XCTAssertEqual(roots.count, 2) XCTAssertEqual(roots[0], -1, accuracy: accuracy) XCTAssertEqual(roots[1], 1, accuracy: accuracy) + #endif } - #endif + func testDegree5() { // 0.2x^5 - 0.813333x^3 - 8.56x let polynomial = BernsteinPolynomial5(b0: 0, b1: -1.712, b2: -3.424, b3: -5.2173333, b4: -7.1733332, b5: -9.173333) From e610c84dfa8d0e7d0cbffe9a067b350d7735f5fb Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 17 Jun 2022 14:07:05 +0000 Subject: [PATCH 41/63] Add stub implementations NSCoder encode/decode to satisfy protocol requirement --- BezierKit/Library/Path.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/BezierKit/Library/Path.swift b/BezierKit/Library/Path.swift index 1c750bbe..40d697f7 100644 --- a/BezierKit/Library/Path.swift +++ b/BezierKit/Library/Path.swift @@ -245,7 +245,17 @@ open class Path: NSObject, NSSecureCoding { return true } - #if !os(WASI) + #if os(WASI) + @available(*, deprecated, message: "unavailable on WASI due to missing plist support") + public func encode(with aCoder: NSCoder) { + fatalError("unavailable on WASI due to missing plist support") + } + + @available(*, deprecated, message: "unavailable on WASI due to missing plist support") + required public convenience init?(coder aDecoder: NSCoder) { + fatalError("unavailable on WASI due to missing plist support") + } + #else public func encode(with aCoder: NSCoder) { aCoder.encode(self.data) } From 1b018abfdeda8bffa44917beac889a22115f4747 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 17 Jun 2022 14:07:54 +0000 Subject: [PATCH 42/63] Update carton version in CI to 0.16.0 --- .github/workflows/wasmBuild.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wasmBuild.yml b/.github/workflows/wasmBuild.yml index d7ae09f4..ee2779b0 100644 --- a/.github/workflows/wasmBuild.yml +++ b/.github/workflows/wasmBuild.yml @@ -4,7 +4,7 @@ jobs: test: name: BezierKit WASM support runs-on: ubuntu-latest - container: ghcr.io/swiftwasm/carton:0.12.1 + container: ghcr.io/swiftwasm/carton:0.16.0 steps: - uses: actions/checkout@v2 From 3cd69dbad30767436eb1c6e2058843a5b03ea245 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 17 Jun 2022 14:20:43 +0000 Subject: [PATCH 43/63] Unlock CubicCurveTests.swift by fixing stack overflow WebAssembly's default stack size is very small and insufficient for testing CubicCurve, especially where Utils.pairiteration is used. This change extends the size to unlock the test, and also adds 32bit CGFloat support. --- BezierKit/BezierKitTests/CubicCurveTests.swift | 13 +++++++++++-- Package.swift | 11 ++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/BezierKit/BezierKitTests/CubicCurveTests.swift b/BezierKit/BezierKitTests/CubicCurveTests.swift index d7bd2e08..7bd0ed2b 100644 --- a/BezierKit/BezierKitTests/CubicCurveTests.swift +++ b/BezierKit/BezierKitTests/CubicCurveTests.swift @@ -8,7 +8,7 @@ import XCTest @testable import BezierKit -#if !os(WASI) + class CubicCurveTests: XCTestCase { override func setUp() { @@ -518,11 +518,21 @@ class CubicCurveTests: XCTestCase { p2: CGPoint(x: 0.4760155370276209, y: 0.24346330678827144), p3: CGPoint(x: 0.6941905032971079, y: 0.20928332065477662)) let intersections = c1.intersections(with: c2, accuracy: 1.0e-5) + + // On platforms where CGFloat is 32bit + #if arch(i386) || arch(arm) || arch(wasm32) + XCTAssertEqual(intersections.count, 3) + XCTAssertEqual(intersections[0].t1, 0.94645, accuracy: 1.0e-5) + XCTAssertEqual(intersections[0].t2, 0.07462, accuracy: 1.0e-5) + XCTAssertEqual(intersections[1].t1, 0.97130, accuracy: 1.0e-5) + XCTAssertEqual(intersections[1].t2, 0.03999, accuracy: 1.0e-5) + #else XCTAssertEqual(intersections.count, 2) XCTAssertEqual(intersections[0].t1, 0.73204, accuracy: 1.0e-5) XCTAssertEqual(intersections[0].t2, 0.37268, accuracy: 1.0e-5) XCTAssertEqual(intersections[1].t1, 1) XCTAssertEqual(intersections[1].t2, 0) + #endif } func testIntersectionsCubicButActuallyLinear() { @@ -655,4 +665,3 @@ class CubicCurveTests: XCTestCase { XCTAssertNotEqual(c1, c5) } } -#endif diff --git a/Package.swift b/Package.swift index 1ac483d3..2bd5ddc4 100644 --- a/Package.swift +++ b/Package.swift @@ -23,7 +23,16 @@ let package = Package( .testTarget( name: "BezierKitTests", dependencies: ["BezierKit"], - path: "BezierKit/BezierKitTests" + path: "BezierKit/BezierKitTests", + linkerSettings: [ + // Extend stack size on WebAssembly since the default stack size of wasm-ld (64kb) + // is not enough for testing BezierKit.Utils.pairiteration, which heavily calls itself + // recursively. + .unsafeFlags( + ["-Xlinker", "-z", "-Xlinker", "stack-size=\(248 * 1024)"], + .when(platforms: [.wasi]) + ), + ] ), ], swiftLanguageVersions: [.v5] From d12bfa127c46412cca9674984b70a5d540e356df Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 22 Jun 2022 03:32:29 +0000 Subject: [PATCH 44/63] Make NSSecureCoding conformance conditional --- BezierKit/Library/Path.swift | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/BezierKit/Library/Path.swift b/BezierKit/Library/Path.swift index 40d697f7..6ebf8d94 100644 --- a/BezierKit/Library/Path.swift +++ b/BezierKit/Library/Path.swift @@ -33,7 +33,7 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR } } -open class Path: NSObject, NSSecureCoding { +open class Path: NSObject { /// lock to make external accessing of lazy vars threadsafe private let lock = UnfairLock() @@ -245,17 +245,7 @@ open class Path: NSObject, NSSecureCoding { return true } - #if os(WASI) - @available(*, deprecated, message: "unavailable on WASI due to missing plist support") - public func encode(with aCoder: NSCoder) { - fatalError("unavailable on WASI due to missing plist support") - } - - @available(*, deprecated, message: "unavailable on WASI due to missing plist support") - required public convenience init?(coder aDecoder: NSCoder) { - fatalError("unavailable on WASI due to missing plist support") - } - #else + #if !os(WASI) public func encode(with aCoder: NSCoder) { aCoder.encode(self.data) } @@ -377,6 +367,10 @@ open class Path: NSObject, NSSecureCoding { } } +#if !os(WASI) +extension Path: NSSecureCoding {} +#endif + extension Path: Transformable { public func copy(using t: CGAffineTransform) -> Self { return type(of: self).init(components: self.components.map { $0.copy(using: t)}) From 1f9fe24483d422fd498108c586f3cee8afb1d191 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 22 Jun 2022 03:35:05 +0000 Subject: [PATCH 45/63] Skip precision error tests on 32bit platforms --- BezierKit/BezierKitTests/CubicCurveTests.swift | 13 +++---------- BezierKit/BezierKitTests/PolynomialTests.swift | 13 +++---------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/BezierKit/BezierKitTests/CubicCurveTests.swift b/BezierKit/BezierKitTests/CubicCurveTests.swift index 7bd0ed2b..3fb8b311 100644 --- a/BezierKit/BezierKitTests/CubicCurveTests.swift +++ b/BezierKit/BezierKitTests/CubicCurveTests.swift @@ -505,6 +505,8 @@ class CubicCurveTests: XCTestCase { XCTAssertEqual(c1.intersections(with: c2, accuracy: 1.0e-8), expectedIntersections) } + // Skip on platforms where CGFloat is 32bit + #if !(arch(i386) || arch(arm) || arch(wasm32)) func testRealWorldNearlyCoincidentCurvesIntersection() { // these curves are nearly coincident over from c1's t = 0.278 to 1.0 // staying roughly 0.0002 distance of eachother @@ -518,22 +520,13 @@ class CubicCurveTests: XCTestCase { p2: CGPoint(x: 0.4760155370276209, y: 0.24346330678827144), p3: CGPoint(x: 0.6941905032971079, y: 0.20928332065477662)) let intersections = c1.intersections(with: c2, accuracy: 1.0e-5) - - // On platforms where CGFloat is 32bit - #if arch(i386) || arch(arm) || arch(wasm32) - XCTAssertEqual(intersections.count, 3) - XCTAssertEqual(intersections[0].t1, 0.94645, accuracy: 1.0e-5) - XCTAssertEqual(intersections[0].t2, 0.07462, accuracy: 1.0e-5) - XCTAssertEqual(intersections[1].t1, 0.97130, accuracy: 1.0e-5) - XCTAssertEqual(intersections[1].t2, 0.03999, accuracy: 1.0e-5) - #else XCTAssertEqual(intersections.count, 2) XCTAssertEqual(intersections[0].t1, 0.73204, accuracy: 1.0e-5) XCTAssertEqual(intersections[0].t2, 0.37268, accuracy: 1.0e-5) XCTAssertEqual(intersections[1].t1, 1) XCTAssertEqual(intersections[1].t2, 0) - #endif } + #endif func testIntersectionsCubicButActuallyLinear() { // this test presents a challenge for an implicitization based approach diff --git a/BezierKit/BezierKitTests/PolynomialTests.swift b/BezierKit/BezierKitTests/PolynomialTests.swift index 2fbcdb76..cabd24d8 100644 --- a/BezierKit/BezierKitTests/PolynomialTests.swift +++ b/BezierKit/BezierKitTests/PolynomialTests.swift @@ -93,24 +93,17 @@ class PolynomialTests: XCTestCase { XCTAssertEqual(roots[2], 1, accuracy: accuracy) XCTAssertEqual(roots[3], 1.2, accuracy: accuracy) } - + // Skip on platforms where CGFloat is 32bit + #if !(arch(i386) || arch(arm) || arch(wasm32)) func testDegree4RepeatedRoots() { // x^4 - 2x^2 + 1 let polynomial = BernsteinPolynomial4(b0: 1, b1: 1, b2: 2.0 / 3.0, b3: 0, b4: 0) let roots = findDistinctRoots(of: polynomial, between: -2, and: 2) - // On platforms where CGFloat is 32bit - #if arch(i386) || arch(arm) || arch(wasm32) - XCTAssertEqual(roots.count, 3) - XCTAssertEqual(roots[0], -1.0000075, accuracy: accuracy) - XCTAssertEqual(roots[1], -0.99948174, accuracy: accuracy) - XCTAssertEqual(roots[2], 1, accuracy: accuracy) - #else XCTAssertEqual(roots.count, 2) XCTAssertEqual(roots[0], -1, accuracy: accuracy) XCTAssertEqual(roots[1], 1, accuracy: accuracy) - #endif } - + #endif func testDegree5() { // 0.2x^5 - 0.813333x^3 - 8.56x let polynomial = BernsteinPolynomial5(b0: 0, b1: -1.712, b2: -3.424, b3: -5.2173333, b4: -7.1733332, b5: -9.173333) From 4c0573ac528b969c653fde07b49147dc2e4e6dc2 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 23 Jun 2022 08:47:40 +0900 Subject: [PATCH 46/63] Minimal CGFloat to Double change to fix precision on 32bit arch This change includes minimal set of CGFloat -> Double migration. Without any single line of this change, precision tests will fail. CGFloat -> Double change doesn't affect source compatibility in most case, thanks to [SE-0307](https://github.com/apple/swift-evolution/blob/ main/proposals/0307-allow-interchangeable-use-of-double-cgfloat-types.md) However, CGFloat in generic parameter position is not interconvertible with Double, so it would break source compatibility. In this change, `[CGFloat]` in `coefficients` fields are that case. --- .../BezierKitTests/CubicCurveTests.swift | 3 -- .../BezierKitTests/PolynomialTests.swift | 7 ++-- BezierKit/Library/BernsteinPolynomialN.swift | 22 +++++------ .../Library/BezierCurve+Implicitization.swift | 12 +++--- .../Library/BezierCurve+Intersection.swift | 12 +++--- BezierKit/Library/BezierCurve.swift | 12 +++--- BezierKit/Library/CGPoint+Overloads.swift | 10 ++--- BezierKit/Library/CubicCurve.swift | 18 ++++----- BezierKit/Library/LineSegment.swift | 6 +-- BezierKit/Library/Path.swift | 16 ++++---- BezierKit/Library/PathComponent.swift | 4 +- BezierKit/Library/Polynomial.swift | 38 +++++++++---------- BezierKit/Library/QuadraticCurve.swift | 6 +-- BezierKit/Library/Utils.swift | 8 ++-- 14 files changed, 85 insertions(+), 89 deletions(-) diff --git a/BezierKit/BezierKitTests/CubicCurveTests.swift b/BezierKit/BezierKitTests/CubicCurveTests.swift index 3fb8b311..c2b72b53 100644 --- a/BezierKit/BezierKitTests/CubicCurveTests.swift +++ b/BezierKit/BezierKitTests/CubicCurveTests.swift @@ -505,8 +505,6 @@ class CubicCurveTests: XCTestCase { XCTAssertEqual(c1.intersections(with: c2, accuracy: 1.0e-8), expectedIntersections) } - // Skip on platforms where CGFloat is 32bit - #if !(arch(i386) || arch(arm) || arch(wasm32)) func testRealWorldNearlyCoincidentCurvesIntersection() { // these curves are nearly coincident over from c1's t = 0.278 to 1.0 // staying roughly 0.0002 distance of eachother @@ -526,7 +524,6 @@ class CubicCurveTests: XCTestCase { XCTAssertEqual(intersections[1].t1, 1) XCTAssertEqual(intersections[1].t2, 0) } - #endif func testIntersectionsCubicButActuallyLinear() { // this test presents a challenge for an implicitization based approach diff --git a/BezierKit/BezierKitTests/PolynomialTests.swift b/BezierKit/BezierKitTests/PolynomialTests.swift index cabd24d8..e0746327 100644 --- a/BezierKit/BezierKitTests/PolynomialTests.swift +++ b/BezierKit/BezierKitTests/PolynomialTests.swift @@ -57,7 +57,7 @@ class PolynomialTests: XCTestCase { func testDegree3() { // x^3 - 6x^2 + 11x - 6 let polynomial = BernsteinPolynomial3(b0: -6, b1: -7.0 / 3.0, b2: -2.0 / 3.0, b3: 0) - XCTAssertEqual(polynomial.coefficients, [-6, CGFloat(-7.0 / 3.0), CGFloat(-2.0 / 3.0), 0.0]) + XCTAssertEqual(polynomial.coefficients, [-6, Double(-7.0 / 3.0), Double(-2.0 / 3.0), 0.0]) let roots = findDistinctRoots(of: polynomial, between: 0, and: 4) XCTAssertEqual(roots[0], 1, accuracy: accuracy) XCTAssertEqual(roots[1], 2, accuracy: accuracy) @@ -93,8 +93,7 @@ class PolynomialTests: XCTestCase { XCTAssertEqual(roots[2], 1, accuracy: accuracy) XCTAssertEqual(roots[3], 1.2, accuracy: accuracy) } - // Skip on platforms where CGFloat is 32bit - #if !(arch(i386) || arch(arm) || arch(wasm32)) + func testDegree4RepeatedRoots() { // x^4 - 2x^2 + 1 let polynomial = BernsteinPolynomial4(b0: 1, b1: 1, b2: 2.0 / 3.0, b3: 0, b4: 0) @@ -103,7 +102,7 @@ class PolynomialTests: XCTestCase { XCTAssertEqual(roots[0], -1, accuracy: accuracy) XCTAssertEqual(roots[1], 1, accuracy: accuracy) } - #endif + func testDegree5() { // 0.2x^5 - 0.813333x^3 - 8.56x let polynomial = BernsteinPolynomial5(b0: 0, b1: -1.712, b2: -3.424, b3: -5.2173333, b4: -7.1733332, b5: -9.173333) diff --git a/BezierKit/Library/BernsteinPolynomialN.swift b/BezierKit/Library/BernsteinPolynomialN.swift index 59adaf22..584ec932 100644 --- a/BezierKit/Library/BernsteinPolynomialN.swift +++ b/BezierKit/Library/BernsteinPolynomialN.swift @@ -11,18 +11,18 @@ import CoreGraphics import Foundation struct BernsteinPolynomialN: BernsteinPolynomial { - let coefficients: [CGFloat] + let coefficients: [Double] func difference(a1: CGFloat, a2: CGFloat) -> BernsteinPolynomialN { fatalError("unimplemented.") } var order: Int { return coefficients.count - 1 } - init(coefficients: [CGFloat]) { + init(coefficients: [Double]) { precondition(coefficients.isEmpty == false, "Bezier curves require at least one point") self.coefficients = coefficients } - static func * (left: CGFloat, right: BernsteinPolynomialN) -> BernsteinPolynomialN { + static func * (left: Double, right: BernsteinPolynomialN) -> BernsteinPolynomialN { return BernsteinPolynomialN(coefficients: right.coefficients.map { left * $0 }) } static func == (left: BernsteinPolynomialN, right: BernsteinPolynomialN) -> Bool { @@ -35,7 +35,7 @@ struct BernsteinPolynomialN: BernsteinPolynomial { guard order > 0 else { return BernsteinPolynomialN(coefficients: [CGFloat.zero]) } return CGFloat(order) * hodograph } - func value(at t: CGFloat) -> CGFloat { + func value(at t: Double) -> Double { return self.split(at: t).left.coefficients.last! } private var hodograph: BernsteinPolynomialN { @@ -43,16 +43,16 @@ struct BernsteinPolynomialN: BernsteinPolynomial { let differences = (0.. (left: BernsteinPolynomialN, right: BernsteinPolynomialN) { + func split(at t: Double) -> (left: BernsteinPolynomialN, right: BernsteinPolynomialN) { guard order > 0 else { // splitting a point results in getting a point back return (left: self, right: self) } // apply de Casteljau Algorithm - var leftPoints = [CGFloat](repeating: .zero, count: coefficients.count) - var rightPoints = [CGFloat](repeating: .zero, count: coefficients.count) + var leftPoints = [Double](repeating: .zero, count: coefficients.count) + var rightPoints = [Double](repeating: .zero, count: coefficients.count) let n = order - var scratchPad: [CGFloat] = coefficients + var scratchPad: [Double] = coefficients leftPoints[0] = scratchPad[0] rightPoints[n] = scratchPad[n] for j in 1...n { @@ -65,7 +65,7 @@ struct BernsteinPolynomialN: BernsteinPolynomial { return (left: BernsteinPolynomialN(coefficients: leftPoints), right: BernsteinPolynomialN(coefficients: rightPoints)) } - func split(from t1: CGFloat, to t2: CGFloat) -> BernsteinPolynomialN { + func split(from t1: Double, to t2: Double) -> BernsteinPolynomialN { guard (t1 > t2) == false else { // simplifying to t1 <= t2 would infinite loop on NaN because NaN comparisons are always false return split(from: t2, to: t1).reversed() @@ -90,14 +90,14 @@ extension BernsteinPolynomialN { // 9.3 Multiplication of Polynomials in Bernstein Form let m = left.order let n = right.order - let points = (0...m + n).map { k -> CGFloat in + let points = (0...m + n).map { k -> Double in let start = max(k - n, 0) let end = min(m, k) let sum = (start...end).reduce(CGFloat.zero) { totalSoFar, i in let j = k - i return totalSoFar + CGFloat(Utils.binomialCoefficient(m, choose: i) * Utils.binomialCoefficient(n, choose: j)) * left.coefficients[i] * right.coefficients[j] } - let divisor = CGFloat(Utils.binomialCoefficient(m + n, choose: k)) + let divisor = Double(Utils.binomialCoefficient(m + n, choose: k)) return sum / divisor } return BernsteinPolynomialN(coefficients: points) diff --git a/BezierKit/Library/BezierCurve+Implicitization.swift b/BezierKit/Library/BezierCurve+Implicitization.swift index 2404559f..ccd9a0ef 100644 --- a/BezierKit/Library/BezierCurve+Implicitization.swift +++ b/BezierKit/Library/BezierCurve+Implicitization.swift @@ -62,7 +62,7 @@ internal struct ImplicitPolynomial { } let resultOrder = order * polynomialOrder - var sum: BernsteinPolynomialN = BernsteinPolynomialN(coefficients: [CGFloat](repeating: 0, count: resultOrder + 1)) + var sum: BernsteinPolynomialN = BernsteinPolynomialN(coefficients: [Double](repeating: 0, count: resultOrder + 1)) for i in 0...order { let xPower: BernsteinPolynomialN = xPowers[i] for j in 0...order { @@ -79,7 +79,7 @@ internal struct ImplicitPolynomial { // swiftlint:disable shorthand_operator if k > 0 { // bring the term up to degree k - term = term * BernsteinPolynomialN(coefficients: [CGFloat](repeating: 1, count: k + 1)) + term = term * BernsteinPolynomialN(coefficients: [Double](repeating: 1, count: k + 1)) } else { assert(k == 0, "for k < 0 we should have c == 0") } @@ -90,10 +90,10 @@ internal struct ImplicitPolynomial { return sum } - func value(at point: CGPoint) -> CGFloat { + func value(at point: CGPoint) -> Double { let x = point.x let y = point.y - var sum: CGFloat = 0 + var sum: Double = 0 for i in 0...order { for j in 0...order { sum += coefficient(i, j) * pow(x, CGFloat(i)) * pow(y, CGFloat(j)) @@ -114,7 +114,7 @@ internal struct ImplicitPolynomial { } private struct ImplicitLineProduct { - var a20, a11, a10, a02, a01, a00: CGFloat + var a20, a11, a10, a02, a01, a00: Double static func * (left: ImplicitLine, right: ImplicitLineProduct) -> ImplicitPolynomial { let a00 = left.a00 * right.a00 let a10 = left.a00 * right.a10 + left.a10 * right.a00 @@ -153,7 +153,7 @@ private struct ImplicitLineProduct { } private struct ImplicitLine { - var a10, a01, a00: CGFloat + var a10, a01, a00: Double static func * (left: ImplicitLine, right: ImplicitLine) -> ImplicitLineProduct { return ImplicitLineProduct(a20: left.a10 * right.a10, a11: left.a01 * right.a10 + left.a10 * right.a01, diff --git a/BezierKit/Library/BezierCurve+Intersection.swift b/BezierKit/Library/BezierCurve+Intersection.swift index a73fefc1..3049e71f 100644 --- a/BezierKit/Library/BezierCurve+Intersection.swift +++ b/BezierKit/Library/BezierCurve+Intersection.swift @@ -37,15 +37,15 @@ public extension BezierCurve { } private func coincidenceCheck(_ curve1: U, _ curve2: T, accuracy: CGFloat) -> [Intersection]? { - func pointIsCloseToCurve(_ point: CGPoint, _ curve: X) -> CGFloat? { + func pointIsCloseToCurve(_ point: CGPoint, _ curve: X) -> Double? { let (projection, t) = curve.project(point) guard distanceSquared(point, projection) < 4.0 * accuracy * accuracy else { return nil } return t } - var range1Start: CGFloat = .infinity - var range1End: CGFloat = -.infinity - var range2Start: CGFloat = .infinity - var range2End: CGFloat = -.infinity + var range1Start: Double = .infinity + var range1End: Double = -.infinity + var range2Start: Double = .infinity + var range2End: Double = -.infinity if range1Start > 0 || range2Start > 0 || range2End < 1 { if let t2 = pointIsCloseToCurve(curve1.startingPoint, curve2) { range1Start = 0 @@ -187,7 +187,7 @@ internal func helperIntersectsCurveLine(_ curve: U, _ line: LineSegment, reve let lineDirection = (line.p1 - line.p0) let lineLength = lineDirection.lengthSquared guard lineLength > 0 else { return [] } - func align(_ point: CGPoint) -> CGFloat { + func align(_ point: CGPoint) -> Double { return (point - line.p0).dot(lineDirection.perpendicular) } var intersections: [Intersection] = [] diff --git a/BezierKit/Library/BezierCurve.swift b/BezierKit/Library/BezierCurve.swift index a28848a9..63c94b59 100644 --- a/BezierKit/Library/BezierCurve.swift +++ b/BezierKit/Library/BezierCurve.swift @@ -12,8 +12,8 @@ import CoreGraphics import Foundation public struct Subcurve where CurveType: BezierCurve { - public let t1: CGFloat - public let t2: CGFloat + public let t1: Double + public let t2: Double public let curve: CurveType internal var canSplit: Bool { @@ -27,7 +27,7 @@ public struct Subcurve where CurveType: BezierCurve { self.curve = curve } - internal init(t1: CGFloat, t2: CGFloat, curve: CurveType) { + internal init(t1: Double, t2: Double, curve: CurveType) { self.t1 = t1 self.t2 = t2 self.curve = curve @@ -289,15 +289,15 @@ public protocol BezierCurve: BoundingBoxProtocol, Transformable, Reversible { var endingPoint: CGPoint { get set } var order: Int { get } init(points: [CGPoint]) - func point(at t: CGFloat) -> CGPoint + func point(at t: Double) -> CGPoint func derivative(at t: CGFloat) -> CGPoint func normal(at t: CGFloat) -> CGPoint - func split(from t1: CGFloat, to t2: CGFloat) -> Self + func split(from t1: Double, to t2: Double) -> Self func split(at t: CGFloat) -> (left: Self, right: Self) func length() -> CGFloat func extrema() -> (x: [CGFloat], y: [CGFloat], all: [CGFloat]) func lookupTable(steps: Int) -> [CGPoint] - func project(_ point: CGPoint) -> (point: CGPoint, t: CGFloat) + func project(_ point: CGPoint) -> (point: CGPoint, t: Double) // intersection routines var selfIntersects: Bool { get } var selfIntersections: [Intersection] { get } diff --git a/BezierKit/Library/CGPoint+Overloads.swift b/BezierKit/Library/CGPoint+Overloads.swift index 0d92c3f8..44614802 100644 --- a/BezierKit/Library/CGPoint+Overloads.swift +++ b/BezierKit/Library/CGPoint+Overloads.swift @@ -14,10 +14,10 @@ import Foundation // swiftlint:disable shorthand_operator public extension CGPoint { - var length: CGFloat { + var length: Double { return sqrt(self.lengthSquared) } - internal var lengthSquared: CGFloat { + internal var lengthSquared: Double { return self.dot(self) } func normalize() -> CGPoint { @@ -52,7 +52,7 @@ public extension CGPoint { static internal var dimensions: Int { return 2 } - func dot(_ other: CGPoint) -> CGFloat { + func dot(_ other: CGPoint) -> Double { return self.x * other.x + self.y * other.y } func cross(_ other: CGPoint) -> CGFloat { @@ -88,10 +88,10 @@ public extension CGPoint { static func -= (left: inout CGPoint, right: CGPoint) { left = left - right } - static func / (left: CGPoint, right: CGFloat) -> CGPoint { + static func / (left: CGPoint, right: Double) -> CGPoint { return CGPoint(x: left.x / right, y: left.y / right) } - static func * (left: CGFloat, right: CGPoint) -> CGPoint { + static func * (left: Double, right: CGPoint) -> CGPoint { return CGPoint(x: left * right.x, y: left * right.y) } static prefix func - (point: CGPoint) -> CGPoint { diff --git a/BezierKit/Library/CubicCurve.swift b/BezierKit/Library/CubicCurve.swift index efd88994..e8726c08 100644 --- a/BezierKit/Library/CubicCurve.swift +++ b/BezierKit/Library/CubicCurve.swift @@ -62,8 +62,8 @@ public struct CubicCurve: NonlinearBezierCurve, Equatable { } public init(lineSegment: LineSegment) { - let oneThird: CGFloat = 1.0 / 3.0 - let twoThirds: CGFloat = 2.0 / 3.0 + let oneThird: Double = 1.0 / 3.0 + let twoThirds: Double = 2.0 / 3.0 self.init(p0: lineSegment.p0, p1: twoThirds * lineSegment.p0 + oneThird * lineSegment.p1, p2: oneThird * lineSegment.p0 + twoThirds * lineSegment.p1, @@ -71,8 +71,8 @@ public struct CubicCurve: NonlinearBezierCurve, Equatable { } public init(quadratic: QuadraticCurve) { - let oneThird: CGFloat = 1.0 / 3.0 - let twoThirds: CGFloat = 2.0 / 3.0 + let oneThird: Double = 1.0 / 3.0 + let twoThirds: Double = 2.0 / 3.0 self.init(p0: quadratic.p0, p1: twoThirds * quadratic.p1 + oneThird * quadratic.p0, p2: oneThird * quadratic.p2 + twoThirds * quadratic.p1, @@ -187,7 +187,7 @@ public struct CubicCurve: NonlinearBezierCurve, Equatable { return temp1 + temp2 + temp3 } - public func split(from t1: CGFloat, to t2: CGFloat) -> CubicCurve { + public func split(from t1: Double, to t2: Double) -> CubicCurve { guard t1 != 0.0 || t2 != 1.0 else { return self } let k = (t2 - t1) / 3.0 let p0 = self.point(at: t1) @@ -217,7 +217,7 @@ public struct CubicCurve: NonlinearBezierCurve, Equatable { } - public func project(_ point: CGPoint) -> (point: CGPoint, t: CGFloat) { + public func project(_ point: CGPoint) -> (point: CGPoint, t: Double) { func mul(_ a: CGPoint, _ b: CGPoint) -> CGPoint { return CGPoint(x: a.x * b.x, y: a.y * b.y) } @@ -298,15 +298,15 @@ public struct CubicCurve: NonlinearBezierCurve, Equatable { return BoundingBox(min: mmin, max: mmax) } - public func point(at t: CGFloat) -> CGPoint { + public func point(at t: Double) -> CGPoint { if t == 0 { return self.p0 } else if t == 1 { return self.p3 } let mt = 1.0 - t - let mt2: CGFloat = mt*mt - let t2: CGFloat = t*t + let mt2: Double = mt*mt + let t2: Double = t*t let a = mt2 * mt let b = mt2 * t * 3.0 let c = mt * t2 * 3.0 diff --git a/BezierKit/Library/LineSegment.swift b/BezierKit/Library/LineSegment.swift index cae2f55a..a0badd12 100644 --- a/BezierKit/Library/LineSegment.swift +++ b/BezierKit/Library/LineSegment.swift @@ -64,7 +64,7 @@ public struct LineSegment: BezierCurve, Equatable { return (self.p1 - self.p0).perpendicular.normalize() } - public func split(from t1: CGFloat, to t2: CGFloat) -> LineSegment { + public func split(from t1: Double, to t2: Double) -> LineSegment { return LineSegment(p0: self.point(at: t1), p1: self.point(at: t2)) } @@ -83,7 +83,7 @@ public struct LineSegment: BezierCurve, Equatable { return BoundingBox(min: CGPoint.min(p0, p1), max: CGPoint.max(p0, p1)) } - public func point(at t: CGFloat) -> CGPoint { + public func point(at t: Double) -> CGPoint { if t == 0 { return self.p0 } else if t == 1 { @@ -103,7 +103,7 @@ public struct LineSegment: BezierCurve, Equatable { return (x: [], y: [], all: []) } - public func project(_ point: CGPoint) -> (point: CGPoint, t: CGFloat) { + public func project(_ point: CGPoint) -> (point: CGPoint, t: Double) { // optimized implementation for line segments can be directly computed // default project implementation is found in BezierCurve protocol extension let relativePoint = point - self.p0 diff --git a/BezierKit/Library/Path.swift b/BezierKit/Library/Path.swift index 6ebf8d94..311152c4 100644 --- a/BezierKit/Library/Path.swift +++ b/BezierKit/Library/Path.swift @@ -106,11 +106,11 @@ open class Path: NSObject { public let components: [PathComponent] - public func selfIntersects(accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + public func selfIntersects(accuracy: Double = BezierKit.defaultIntersectionAccuracy) -> Bool { return !self.selfIntersections(accuracy: accuracy).isEmpty } - public func selfIntersections(accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> [PathIntersection] { + public func selfIntersections(accuracy: Double = BezierKit.defaultIntersectionAccuracy) -> [PathIntersection] { var intersections: [PathIntersection] = [] for i in 0.. Bool { + public func intersects(_ other: Path, accuracy: Double = BezierKit.defaultIntersectionAccuracy) -> Bool { return !self.intersections(with: other, accuracy: accuracy).isEmpty } - public func intersections(with other: Path, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> [PathIntersection] { + public func intersections(with other: Path, accuracy: Double = BezierKit.defaultIntersectionAccuracy) -> [PathIntersection] { guard self.boundingBox.overlaps(other.boundingBox) else { return [] } @@ -301,7 +301,7 @@ open class Path: NSObject { return windingCountImpliesContainment(count, using: rule) } - public func contains(_ other: Path, using rule: PathFillRule = .winding, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { + public func contains(_ other: Path, using rule: PathFillRule = .winding, accuracy: Double = BezierKit.defaultIntersectionAccuracy) -> Bool { // first, check that each component of `other` starts inside self for component in other.components { let p = component.startingPoint @@ -315,7 +315,7 @@ open class Path: NSObject { return !self.intersects(other, accuracy: accuracy) } - public func offset(distance d: CGFloat) -> Path { + public func offset(distance d: Double) -> Path { return Path(components: self.components.compactMap { $0.offset(distance: d) }) @@ -386,8 +386,8 @@ extension Path: Reversible { public struct IndexedPathLocation: Equatable, Comparable { public let componentIndex: Int public let elementIndex: Int - public let t: CGFloat - public init(componentIndex: Int, elementIndex: Int, t: CGFloat) { + public let t: Double + public init(componentIndex: Int, elementIndex: Int, t: Double) { self.componentIndex = componentIndex self.elementIndex = elementIndex self.t = t diff --git a/BezierKit/Library/PathComponent.swift b/BezierKit/Library/PathComponent.swift index bf064419..0de3292d 100644 --- a/BezierKit/Library/PathComponent.swift +++ b/BezierKit/Library/PathComponent.swift @@ -234,7 +234,7 @@ open class PathComponent: NSObject, Reversible, Transformable { self.points = temp } - public var length: CGFloat { + public var length: Double { return self.curves.reduce(0.0) { $0 + $1.length() } } @@ -250,7 +250,7 @@ open class PathComponent: NSObject, Reversible, Transformable { return self.startingPoint == self.endingPoint } - public func offset(distance d: CGFloat) -> PathComponent? { + public func offset(distance d: Double) -> PathComponent? { var offsetCurves = self.curves.reduce([]) { $0 + $1.offset(distance: d) } diff --git a/BezierKit/Library/Polynomial.swift b/BezierKit/Library/Polynomial.swift index d1483183..4aa01c8b 100644 --- a/BezierKit/Library/Polynomial.swift +++ b/BezierKit/Library/Polynomial.swift @@ -14,7 +14,7 @@ import Foundation public protocol BernsteinPolynomial: Equatable { func value(at x: CGFloat) -> CGFloat var order: Int { get } - var coefficients: [CGFloat] { get } + var coefficients: [Double] { get } // var last: CGFloat { get } // var first: CGFloat { get } // func enumerated(block: (Int, CGFloat) -> Void) @@ -129,9 +129,9 @@ public struct BernsteinPolynomial0: BernsteinPolynomial { // func reversed() -> BernsteinPolynomial0 { return self } // func split(to x: CGFloat) -> Self { return self } // func split(from x: CGFloat) -> Self { return self } - public init(b0: CGFloat) { self.b0 = b0 } - public var b0: CGFloat - public var coefficients: [CGFloat] { return [b0] } + public init(b0: Double) { self.b0 = b0 } + public var b0: Double + public var coefficients: [Double] { return [b0] } public func value(at x: CGFloat) -> CGFloat { return b0 } @@ -161,13 +161,13 @@ public struct BernsteinPolynomial1: BernsteinPolynomial { // self.b1 = d.b0 // } // func reversed() -> BernsteinPolynomial1 { BernsteinPolynomial1(b0: b1, b1: b0) } - public init(b0: CGFloat, b1: CGFloat) { + public init(b0: Double, b1: Double) { self.b0 = b0 self.b1 = b1 } public typealias NextLowerOrderPolynomial = BernsteinPolynomial0 - public var b0, b1: CGFloat - public var coefficients: [CGFloat] { return [b0, b1] } + public var b0, b1: Double + public var coefficients: [Double] { return [b0, b1] } public func reduce(a1: CGFloat, a2: CGFloat) -> CGFloat { return a1 * b0 + a2 * b1 } @@ -195,14 +195,14 @@ public struct BernsteinPolynomial2: BernsteinPolynomial { // self.b1 = d.b0 // self.b2 = d.b1 // } - public init(b0: CGFloat, b1: CGFloat, b2: CGFloat) { + public init(b0: Double, b1: Double, b2: Double) { self.b0 = b0 self.b1 = b1 self.b2 = b2 } public typealias NextLowerOrderPolynomial = BernsteinPolynomial1 - public var b0, b1, b2: CGFloat - public var coefficients: [CGFloat] { return [b0, b1, b2] } + public var b0, b1, b2: Double + public var coefficients: [Double] { return [b0, b1, b2] } public func difference(a1: CGFloat, a2: CGFloat) -> BernsteinPolynomial1 { return BernsteinPolynomial1(b0: a1 * b0 + a2 * b1, b1: a1 * b1 + a2 * b2) @@ -231,15 +231,15 @@ public struct BernsteinPolynomial3: BernsteinPolynomial { // self.b2 = d.b1 // self.b3 = d.b2 // } - public init(b0: CGFloat, b1: CGFloat, b2: CGFloat, b3: CGFloat) { + public init(b0: Double, b1: Double, b2: Double, b3: Double) { self.b0 = b0 self.b1 = b1 self.b2 = b2 self.b3 = b3 } public typealias NextLowerOrderPolynomial = BernsteinPolynomial2 - public var b0, b1, b2, b3: CGFloat - public var coefficients: [CGFloat] { return [b0, b1, b2, b3] } + public var b0, b1, b2, b3: Double + public var coefficients: [Double] { return [b0, b1, b2, b3] } public func difference(a1: CGFloat, a2: CGFloat) -> BernsteinPolynomial2 { return BernsteinPolynomial2(b0: a1 * b0 + a2 * b1, b1: a1 * b1 + a2 * b2, @@ -272,7 +272,7 @@ public struct BernsteinPolynomial4: BernsteinPolynomial { // self.b3 = d.b2 // self.b4 = d.b3 // } - public init(b0: CGFloat, b1: CGFloat, b2: CGFloat, b3: CGFloat, b4: CGFloat) { + public init(b0: Double, b1: Double, b2: Double, b3: Double, b4: Double) { self.b0 = b0 self.b1 = b1 self.b2 = b2 @@ -280,8 +280,8 @@ public struct BernsteinPolynomial4: BernsteinPolynomial { self.b4 = b4 } public typealias NextLowerOrderPolynomial = BernsteinPolynomial3 - public var b0, b1, b2, b3, b4: CGFloat - public var coefficients: [CGFloat] { return [b0, b1, b2, b3, b4] } + public var b0, b1, b2, b3, b4: Double + public var coefficients: [Double] { return [b0, b1, b2, b3, b4] } public func difference(a1: CGFloat, a2: CGFloat) -> BernsteinPolynomial3 { return BernsteinPolynomial3(b0: a1 * b0 + a2 * b1, b1: a1 * b1 + a2 * b2, @@ -318,7 +318,7 @@ public struct BernsteinPolynomial5: BernsteinPolynomial { // self.b4 = d.b3 // self.b5 = d.b4 // } - public init(b0: CGFloat, b1: CGFloat, b2: CGFloat, b3: CGFloat, b4: CGFloat, b5: CGFloat) { + public init(b0: Double, b1: Double, b2: Double, b3: Double, b4: Double, b5: Double) { self.b0 = b0 self.b1 = b1 self.b2 = b2 @@ -327,8 +327,8 @@ public struct BernsteinPolynomial5: BernsteinPolynomial { self.b5 = b5 } public typealias NextLowerOrderPolynomial = BernsteinPolynomial4 - public var b0, b1, b2, b3, b4, b5: CGFloat - public var coefficients: [CGFloat] { return [b0, b1, b2, b3, b4, b5] } + public var b0, b1, b2, b3, b4, b5: Double + public var coefficients: [Double] { return [b0, b1, b2, b3, b4, b5] } public func difference(a1: CGFloat, a2: CGFloat) -> BernsteinPolynomial4 { return BernsteinPolynomial4(b0: a1 * b0 + a2 * b1, b1: a1 * b1 + a2 * b2, diff --git a/BezierKit/Library/QuadraticCurve.swift b/BezierKit/Library/QuadraticCurve.swift index 5d0c2647..dccdbf5c 100644 --- a/BezierKit/Library/QuadraticCurve.swift +++ b/BezierKit/Library/QuadraticCurve.swift @@ -108,7 +108,7 @@ public struct QuadraticCurve: NonlinearBezierCurve, Equatable { return a*p0 + b*p1 } - public func split(from t1: CGFloat, to t2: CGFloat) -> QuadraticCurve { + public func split(from t1: Double, to t2: Double) -> QuadraticCurve { guard t1 != 0.0 || t2 != 1.0 else { return self } let k = (t2 - t1) / 2 let p0 = self.point(at: t1) @@ -132,7 +132,7 @@ public struct QuadraticCurve: NonlinearBezierCurve, Equatable { return (left: leftCurve, right: rightCurve) } - public func project(_ point: CGPoint) -> (point: CGPoint, t: CGFloat) { + public func project(_ point: CGPoint) -> (point: CGPoint, t: Double) { func multiplyCoordinates(_ a: CGPoint, _ b: CGPoint) -> CGPoint { return CGPoint(x: a.x * b.x, y: a.y * b.y) } @@ -195,7 +195,7 @@ public struct QuadraticCurve: NonlinearBezierCurve, Equatable { return BoundingBox(min: mmin, max: mmax) } - public func point(at t: CGFloat) -> CGPoint { + public func point(at t: Double) -> CGPoint { if t == 0 { return self.p0 } else if t == 1 { diff --git a/BezierKit/Library/Utils.swift b/BezierKit/Library/Utils.swift index 093c412a..e76baa61 100644 --- a/BezierKit/Library/Utils.swift +++ b/BezierKit/Library/Utils.swift @@ -141,7 +141,7 @@ internal class Utils { return top/bottom } - static func map(_ v: CGFloat, _ ds: CGFloat, _ de: CGFloat, _ ts: CGFloat, _ te: CGFloat) -> CGFloat { + static func map(_ v: CGFloat, _ ds: CGFloat, _ de: CGFloat, _ ts: Double, _ te: Double) -> Double { let t = (v - ds) / (de - ds) return t * te + (1 - t) * ts } @@ -168,7 +168,7 @@ internal class Utils { return (v < 0) ? -pow(-v, 1.0/3.0) : pow(v, 1.0/3.0) } - static func clamp(_ x: CGFloat, _ a: CGFloat, _ b: CGFloat) -> CGFloat { + static func clamp(_ x: Double, _ a: Double, _ b: Double) -> Double { precondition(b >= a) if x < a { return a @@ -179,7 +179,7 @@ internal class Utils { } } - static func droots(_ p0: CGFloat, _ p1: CGFloat, _ p2: CGFloat, _ p3: CGFloat, callback: (CGFloat) -> Void) { + static func droots(_ p0: Double, _ p1: Double, _ p2: Double, _ p3: Double, callback: (CGFloat) -> Void) { // convert the points p0, p1, p2, p3 to a cubic polynomial at^3 + bt^2 + ct + 1 and solve // see http://www.trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm let p0 = Double(p0) @@ -278,7 +278,7 @@ internal class Utils { callback(p0 / (p0 - p1)) } - static func linearInterpolate(_ v1: CGPoint, _ v2: CGPoint, _ t: CGFloat) -> CGPoint { + static func linearInterpolate(_ v1: CGPoint, _ v2: CGPoint, _ t: Double) -> CGPoint { return v1 + t * (v2 - v1) } From 16535a82d3f42647ecafc7793ebe75ffa650acb8 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 23 Jun 2022 22:21:02 +0900 Subject: [PATCH 47/63] Make pairiteration stack-friendly This enables small stack size environments to use `intersections` method without extending stack space. This keeps fast recursive impl to avoid regression on other platforms Also avoid making a struct since generic struct is not free to build VWT from each generic arguments. --- .../Library/BezierCurve+Intersection.swift | 3 +- BezierKit/Library/Utils.swift | 156 ++++++++++++------ 2 files changed, 108 insertions(+), 51 deletions(-) diff --git a/BezierKit/Library/BezierCurve+Intersection.swift b/BezierKit/Library/BezierCurve+Intersection.swift index 3049e71f..9e7dcd71 100644 --- a/BezierKit/Library/BezierCurve+Intersection.swift +++ b/BezierKit/Library/BezierCurve+Intersection.swift @@ -121,8 +121,7 @@ internal func helperIntersectsCurveCurve(_ curve1: Subcurve, _ curve2: let lb = curve1.curve.boundingBox let rb = curve2.curve.boundingBox var pairIntersections: [Intersection] = [] - var subdivisionIterations = 0 - if Utils.pairiteration(curve1, curve2, lb, rb, &pairIntersections, accuracy, &subdivisionIterations) { + if Utils.pairiteration(curve1, curve2, lb, rb, &pairIntersections, accuracy) { return pairIntersections.sortedAndUniqued() } diff --git a/BezierKit/Library/Utils.swift b/BezierKit/Library/Utils.swift index e76baa61..da3034e8 100644 --- a/BezierKit/Library/Utils.swift +++ b/BezierKit/Library/Utils.swift @@ -308,64 +308,122 @@ internal class Utils { return atan2(d1.cross(d2), d1.dot(d2)) } + enum PairiterationStrategy { + /// DFS by recursive function call + case recurse + /// DFS by stack to avoid deep call stack. + /// This strategy is 2x slower than `recurse` strategy, + /// but consumes much less call stack. + case manualStack + + static var `default`: PairiterationStrategy { + // Use stack-friendly strategy on WebAssembly since + // wasm-ld, a defacto WebAssembly linker sets stack size + // very small (64KB) by default. + #if arch(wasm32) + return .manualStack + #else + return .recurse + #endif + } + } + // disable this SwiftLint warning about function having more than 5 parameters // swiftlint:disable function_parameter_count - static func pairiteration(_ c1: Subcurve, _ c2: Subcurve, _ c1b: BoundingBox, _ c2b: BoundingBox, _ results: inout [Intersection], _ accuracy: CGFloat, - _ totalIterations: inout Int) -> Bool { + _ strategy: PairiterationStrategy = .default) -> Bool { let maximumIterations = 900 - let maximumIntersections = c1.curve.order * c2.curve.order - - totalIterations += 1 - guard totalIterations <= maximumIterations else { return false } - guard results.count <= maximumIntersections else { return false } - guard c1b.overlaps(c2b) else { return true } - - let canSplit1 = c1.canSplit - let canSplit2 = c2.canSplit - let size1 = c1b.size - let size2 = c2b.size - let shouldRecurse1 = canSplit1 && ((size1.x + size1.y) >= accuracy) - let shouldRecurse2 = canSplit2 && ((size2.x + size2.y) >= accuracy) - - if shouldRecurse1 == false, shouldRecurse2 == false { - // subcurves are small enough or we simply cannot recurse any more - let l1 = LineSegment(p0: c1.curve.startingPoint, p1: c1.curve.endingPoint) - let l2 = LineSegment(p0: c2.curve.startingPoint, p1: c2.curve.endingPoint) - guard let intersection = l1.intersections(with: l2, checkCoincidence: false).first else { return true } - let t1 = intersection.t1 - let t2 = intersection.t2 - results.append(Intersection(t1: t1 * c1.t2 + (1.0 - t1) * c1.t1, - t2: t2 * c2.t2 + (1.0 - t2) * c2.t1)) - } else if shouldRecurse1, shouldRecurse2 { - let cc1 = c1.split(at: 0.5) - let cc2 = c2.split(at: 0.5) - let cc1lb = cc1.left.curve.boundingBox - let cc1rb = cc1.right.curve.boundingBox - let cc2lb = cc2.left.curve.boundingBox - let cc2rb = cc2.right.curve.boundingBox - guard Utils.pairiteration(cc1.left, cc2.left, cc1lb, cc2lb, &results, accuracy, &totalIterations) else { return false } - guard Utils.pairiteration(cc1.left, cc2.right, cc1lb, cc2rb, &results, accuracy, &totalIterations) else { return false } - guard Utils.pairiteration(cc1.right, cc2.left, cc1rb, cc2lb, &results, accuracy, &totalIterations) else { return false } - guard Utils.pairiteration(cc1.right, cc2.right, cc1rb, cc2rb, &results, accuracy, &totalIterations) else { return false } - } else if shouldRecurse1 { - let cc1 = c1.split(at: 0.5) - let cc1lb = cc1.left.curve.boundingBox - let cc1rb = cc1.right.curve.boundingBox - guard Utils.pairiteration(cc1.left, c2, cc1lb, c2b, &results, accuracy, &totalIterations) else { return false } - guard Utils.pairiteration(cc1.right, c2, cc1rb, c2b, &results, accuracy, &totalIterations) else { return false } - } else if shouldRecurse2 { - let cc2 = c2.split(at: 0.5) - let cc2lb = cc2.left.curve.boundingBox - let cc2rb = cc2.right.curve.boundingBox - guard Utils.pairiteration(c1, cc2.left, c1b, cc2lb, &results, accuracy, &totalIterations) else { return false } - guard Utils.pairiteration(c1, cc2.right, c1b, cc2rb, &results, accuracy, &totalIterations) else { return false } + var totalIterations = 0 + + /// Parameters + /// - enqueueWork: A closure to enqueue a child work. Returns `false` when search should be stopped + func process(_ c1: Subcurve, _ c2: Subcurve, + _ c1b: BoundingBox, _ c2b: BoundingBox, + _ results: inout [Intersection], + _ enqueueWork: (_ c1: Subcurve, _ c2: Subcurve, + _ c1b: BoundingBox, _ c2b: BoundingBox, + inout [Intersection]) -> Bool + ) -> Bool { + totalIterations += 1 + guard totalIterations <= maximumIterations else { return false } + + let maximumIntersections = c1.curve.order * c2.curve.order + guard results.count <= maximumIntersections else { return false } + + // fast-path: when no overlap in boxes + guard c1b.overlaps(c2b) else { return true } + + let canSplit1 = c1.canSplit + let canSplit2 = c2.canSplit + let size1 = c1b.size + let size2 = c2b.size + let shouldRecurse1 = canSplit1 && ((size1.x + size1.y) >= accuracy) + let shouldRecurse2 = canSplit2 && ((size2.x + size2.y) >= accuracy) + + if shouldRecurse1 == false, shouldRecurse2 == false { + // subcurves are small enough or we simply cannot recurse any more + let l1 = LineSegment(p0: c1.curve.startingPoint, p1: c1.curve.endingPoint) + let l2 = LineSegment(p0: c2.curve.startingPoint, p1: c2.curve.endingPoint) + guard let intersection = l1.intersections(with: l2, checkCoincidence: false).first else { return true } + let t1 = intersection.t1 + let t2 = intersection.t2 + results.append(Intersection(t1: t1 * c1.t2 + (1.0 - t1) * c1.t1, + t2: t2 * c2.t2 + (1.0 - t2) * c2.t1)) + } else if shouldRecurse1, shouldRecurse2 { + let cc1 = c1.split(at: 0.5) + let cc2 = c2.split(at: 0.5) + let cc1lb = cc1.left.curve.boundingBox + let cc1rb = cc1.right.curve.boundingBox + let cc2lb = cc2.left.curve.boundingBox + let cc2rb = cc2.right.curve.boundingBox + guard enqueueWork(cc1.right, cc2.right, cc1rb, cc2rb, &results) else { return false } + guard enqueueWork(cc1.right, cc2.left, cc1rb, cc2lb, &results) else { return false } + guard enqueueWork(cc1.left, cc2.right, cc1lb, cc2rb, &results) else { return false } + guard enqueueWork(cc1.left, cc2.left, cc1lb, cc2lb, &results) else { return false } + } else if shouldRecurse1 { + let cc1 = c1.split(at: 0.5) + let cc1lb = cc1.left.curve.boundingBox + let cc1rb = cc1.right.curve.boundingBox + guard enqueueWork(cc1.right, c2, cc1rb, c2b, &results) else { return false } + guard enqueueWork(cc1.left, c2, cc1lb, c2b, &results) else { return false } + } else if shouldRecurse2 { + let cc2 = c2.split(at: 0.5) + let cc2lb = cc2.left.curve.boundingBox + let cc2rb = cc2.right.curve.boundingBox + guard enqueueWork(c1, cc2.left, c1b, cc2lb, &results) else { return false } + guard enqueueWork(c1, cc2.right, c1b, cc2rb, &results) else { return false } + } + return true + } + + switch strategy { + case .recurse: + func runImmediately(_ c1: Subcurve, _ c2: Subcurve, + _ c1b: BoundingBox, _ c2b: BoundingBox, + _ results: inout [Intersection]) -> Bool { + return process(c1, c2, c1b, c2b, &results) { + runImmediately($0, $1, $2, $3, &$4) + } + } + return runImmediately(c1, c2, c1b, c2b, &results) + case .manualStack: + typealias Work = (c1: Subcurve, c2: Subcurve, c1b: BoundingBox, c2b: BoundingBox) + var worklist: [Work] = [(c1, c2, c1b, c2b)] + while let work = worklist.popLast() { + let shouldContinue = process(work.c1, work.c2, work.c1b, work.c2b, &results) { c1, c2, c1b, c2b, _ in + worklist.append((c1, c2, c1b, c2b)) + return true + } + guard shouldContinue else { + return false + } + } + return true } - return true } // swiftlint:enable function_parameter_count From 74b21087fe8cca2f2fd07110330b42565479f522 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 23 Jun 2022 22:56:11 +0900 Subject: [PATCH 48/63] Revert "Unlock CubicCurveTests.swift by fixing stack overflow" This reverts commit 3cd69dbad30767436eb1c6e2058843a5b03ea245. --- BezierKit/BezierKitTests/CubicCurveTests.swift | 3 ++- Package.swift | 11 +---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/BezierKit/BezierKitTests/CubicCurveTests.swift b/BezierKit/BezierKitTests/CubicCurveTests.swift index c2b72b53..d7bd2e08 100644 --- a/BezierKit/BezierKitTests/CubicCurveTests.swift +++ b/BezierKit/BezierKitTests/CubicCurveTests.swift @@ -8,7 +8,7 @@ import XCTest @testable import BezierKit - +#if !os(WASI) class CubicCurveTests: XCTestCase { override func setUp() { @@ -655,3 +655,4 @@ class CubicCurveTests: XCTestCase { XCTAssertNotEqual(c1, c5) } } +#endif diff --git a/Package.swift b/Package.swift index 2bd5ddc4..1ac483d3 100644 --- a/Package.swift +++ b/Package.swift @@ -23,16 +23,7 @@ let package = Package( .testTarget( name: "BezierKitTests", dependencies: ["BezierKit"], - path: "BezierKit/BezierKitTests", - linkerSettings: [ - // Extend stack size on WebAssembly since the default stack size of wasm-ld (64kb) - // is not enough for testing BezierKit.Utils.pairiteration, which heavily calls itself - // recursively. - .unsafeFlags( - ["-Xlinker", "-z", "-Xlinker", "stack-size=\(248 * 1024)"], - .when(platforms: [.wasi]) - ), - ] + path: "BezierKit/BezierKitTests" ), ], swiftLanguageVersions: [.v5] From 32e05c6e0d980b63d7b2aa59f83f2eabbeec9923 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 23 Jun 2022 22:57:17 +0900 Subject: [PATCH 49/63] Prefer private to fileprivate --- BezierKit/Library/Path+Data.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BezierKit/Library/Path+Data.swift b/BezierKit/Library/Path+Data.swift index 48f93678..bba25248 100644 --- a/BezierKit/Library/Path+Data.swift +++ b/BezierKit/Library/Path+Data.swift @@ -22,7 +22,7 @@ fileprivate extension Data { } } -fileprivate struct DataStream { +private struct DataStream { var dataCursor: Data.Index let data: Data From 6322e8fd7e16912ab48c58a9806f26fe2a3fedfb Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 24 Jun 2022 17:03:35 -0700 Subject: [PATCH 50/63] candidate fix for recursion beyond numerical limits in pair iteration. --- BezierKit/BezierKit.xcodeproj/project.pbxproj | 2 +- .../xcschemes/BezierKitMacDemos.xcscheme | 2 +- .../xcschemes/BezierKit_Mac.xcscheme | 2 +- .../xcschemes/BezierKit_MacTests.xcscheme | 2 +- .../xcschemes/BezierKit_iOS.xcscheme | 2 +- .../xcschemes/BezierKit_iOSTests.xcscheme | 2 +- BezierKit/Library/BezierCurve.swift | 12 +++--- BezierKit/Library/CubicCurve.swift | 4 ++ BezierKit/Library/LineSegment.swift | 4 ++ BezierKit/Library/QuadraticCurve.swift | 4 ++ BezierKit/Library/Utils.swift | 42 ++++++++++--------- 11 files changed, 47 insertions(+), 31 deletions(-) diff --git a/BezierKit/BezierKit.xcodeproj/project.pbxproj b/BezierKit/BezierKit.xcodeproj/project.pbxproj index 5436f873..4078d959 100644 --- a/BezierKit/BezierKit.xcodeproj/project.pbxproj +++ b/BezierKit/BezierKit.xcodeproj/project.pbxproj @@ -540,7 +540,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0830; - LastUpgradeCheck = 1320; + LastUpgradeCheck = 1340; ORGANIZATIONNAME = "Holmes Futrell"; TargetAttributes = { FD0F54F41DC43FFB0084CDCD = { diff --git a/BezierKit/BezierKit.xcodeproj/xcshareddata/xcschemes/BezierKitMacDemos.xcscheme b/BezierKit/BezierKit.xcodeproj/xcshareddata/xcschemes/BezierKitMacDemos.xcscheme index 04bc0b6a..b88fcc75 100644 --- a/BezierKit/BezierKit.xcodeproj/xcshareddata/xcschemes/BezierKitMacDemos.xcscheme +++ b/BezierKit/BezierKit.xcodeproj/xcshareddata/xcschemes/BezierKitMacDemos.xcscheme @@ -1,6 +1,6 @@ where CurveType: BezierCurve { public let t2: CGFloat public let curve: CurveType - internal var canSplit: Bool { - let mid = 0.5 * (self.t1 + self.t2) - return mid > self.t1 && mid < self.t2 - } - internal init(curve: CurveType) { self.t1 = 0.0 self.t2 = 1.0 @@ -307,7 +302,12 @@ public protocol BezierCurve: BoundingBoxProtocol, Transformable, Reversible { func intersections(with curve: BezierCurve, accuracy: CGFloat) -> [Intersection] } -internal protocol NonlinearBezierCurve: BezierCurve, ComponentPolynomials, Implicitizeable { +internal protocol PointClassifiable { + /// returns true if all control points are equal + var isPoint: Bool { get } +} + +internal protocol NonlinearBezierCurve: BezierCurve, ComponentPolynomials, Implicitizeable, PointClassifiable { // intentionally empty, just declare conformance if you're not a line } diff --git a/BezierKit/Library/CubicCurve.swift b/BezierKit/Library/CubicCurve.swift index efd88994..3877d627 100644 --- a/BezierKit/Library/CubicCurve.swift +++ b/BezierKit/Library/CubicCurve.swift @@ -341,3 +341,7 @@ extension CubicCurve: Flatness { return (1.0 / 16.0) * ( temp1 + temp2 ) } } + +extension CubicCurve: PointClassifiable { + var isPoint: Bool { return self.p0 == self.p1 && self.p1 == self.p2 && self.p2 == self.p3 } +} diff --git a/BezierKit/Library/LineSegment.swift b/BezierKit/Library/LineSegment.swift index cae2f55a..d83dfd01 100644 --- a/BezierKit/Library/LineSegment.swift +++ b/BezierKit/Library/LineSegment.swift @@ -129,3 +129,7 @@ extension LineSegment: Flatness { public var flatnessSquared: CGFloat { return 0.0 } public var flatness: CGFloat { return 0.0 } } + +extension LineSegment: PointClassifiable { + var isPoint: Bool { return self.p0 == self.p1 } +} diff --git a/BezierKit/Library/QuadraticCurve.swift b/BezierKit/Library/QuadraticCurve.swift index 5d0c2647..75dd73ad 100644 --- a/BezierKit/Library/QuadraticCurve.swift +++ b/BezierKit/Library/QuadraticCurve.swift @@ -233,3 +233,7 @@ extension QuadraticCurve: Flatness { return (1.0 / 16.0) * (a.x * a.x + a.y * a.y) } } + +extension QuadraticCurve: PointClassifiable { + var isPoint: Bool { return self.p0 == self.p1 && self.p1 == self.p2 } +} diff --git a/BezierKit/Library/Utils.swift b/BezierKit/Library/Utils.swift index 093c412a..f7e0cc19 100644 --- a/BezierKit/Library/Utils.swift +++ b/BezierKit/Library/Utils.swift @@ -311,11 +311,23 @@ internal class Utils { // disable this SwiftLint warning about function having more than 5 parameters // swiftlint:disable function_parameter_count - static func pairiteration(_ c1: Subcurve, _ c2: Subcurve, - _ c1b: BoundingBox, _ c2b: BoundingBox, - _ results: inout [Intersection], - _ accuracy: CGFloat, - _ totalIterations: inout Int) -> Bool { + static func pairiteration(_ c1: Subcurve, _ c2: Subcurve, + _ c1b: BoundingBox, _ c2b: BoundingBox, + _ results: inout [Intersection], + _ accuracy: CGFloat, + _ totalIterations: inout Int) -> Bool { + + func splitCurveIfWithinAccuracyAndNumericalLimits(_ curve: Subcurve, boundingBox: BoundingBox) -> (left: Subcurve, right: Subcurve)? { + // first check if the curve is small enough that it falls within + // accuracy threshold and therefore does not require further recursion + guard boundingBox.size.x + boundingBox.size.y >= accuracy else { return nil } + // next check if splitting the curve results in either half being a point + // if so, we've reached numerical precision limits of how far we can split things + let (left, right) = curve.split(at: 0.5) + guard left.curve.isPoint == false else { return nil } + guard right.curve.isPoint == false else { return nil } + return (left: left, right: right) + } let maximumIterations = 900 let maximumIntersections = c1.curve.order * c2.curve.order @@ -325,14 +337,10 @@ internal class Utils { guard results.count <= maximumIntersections else { return false } guard c1b.overlaps(c2b) else { return true } - let canSplit1 = c1.canSplit - let canSplit2 = c2.canSplit - let size1 = c1b.size - let size2 = c2b.size - let shouldRecurse1 = canSplit1 && ((size1.x + size1.y) >= accuracy) - let shouldRecurse2 = canSplit2 && ((size2.x + size2.y) >= accuracy) + let cc1 = splitCurveIfWithinAccuracyAndNumericalLimits(c1, boundingBox: c1b) + let cc2 = splitCurveIfWithinAccuracyAndNumericalLimits(c2, boundingBox: c2b) - if shouldRecurse1 == false, shouldRecurse2 == false { + if cc1 == nil, cc2 == nil { // subcurves are small enough or we simply cannot recurse any more let l1 = LineSegment(p0: c1.curve.startingPoint, p1: c1.curve.endingPoint) let l2 = LineSegment(p0: c2.curve.startingPoint, p1: c2.curve.endingPoint) @@ -341,9 +349,7 @@ internal class Utils { let t2 = intersection.t2 results.append(Intersection(t1: t1 * c1.t2 + (1.0 - t1) * c1.t1, t2: t2 * c2.t2 + (1.0 - t2) * c2.t1)) - } else if shouldRecurse1, shouldRecurse2 { - let cc1 = c1.split(at: 0.5) - let cc2 = c2.split(at: 0.5) + } else if let cc1 = cc1, let cc2 = cc2 { let cc1lb = cc1.left.curve.boundingBox let cc1rb = cc1.right.curve.boundingBox let cc2lb = cc2.left.curve.boundingBox @@ -352,14 +358,12 @@ internal class Utils { guard Utils.pairiteration(cc1.left, cc2.right, cc1lb, cc2rb, &results, accuracy, &totalIterations) else { return false } guard Utils.pairiteration(cc1.right, cc2.left, cc1rb, cc2lb, &results, accuracy, &totalIterations) else { return false } guard Utils.pairiteration(cc1.right, cc2.right, cc1rb, cc2rb, &results, accuracy, &totalIterations) else { return false } - } else if shouldRecurse1 { - let cc1 = c1.split(at: 0.5) + } else if let cc1 = cc1 { let cc1lb = cc1.left.curve.boundingBox let cc1rb = cc1.right.curve.boundingBox guard Utils.pairiteration(cc1.left, c2, cc1lb, c2b, &results, accuracy, &totalIterations) else { return false } guard Utils.pairiteration(cc1.right, c2, cc1rb, c2b, &results, accuracy, &totalIterations) else { return false } - } else if shouldRecurse2 { - let cc2 = c2.split(at: 0.5) + } else if let cc2 = cc2 { let cc2lb = cc2.left.curve.boundingBox let cc2rb = cc2.right.curve.boundingBox guard Utils.pairiteration(c1, cc2.left, c1b, cc2lb, &results, accuracy, &totalIterations) else { return false } From e852785d45e4faace2aa5ecd4448961e182bb36d Mon Sep 17 00:00:00 2001 From: hfutrell Date: Fri, 24 Jun 2022 17:39:37 -0700 Subject: [PATCH 51/63] slightly different approach from an optimization perspective --- BezierKit/Library/Utils.swift | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/BezierKit/Library/Utils.swift b/BezierKit/Library/Utils.swift index f7e0cc19..e2faa242 100644 --- a/BezierKit/Library/Utils.swift +++ b/BezierKit/Library/Utils.swift @@ -317,16 +317,22 @@ internal class Utils { _ accuracy: CGFloat, _ totalIterations: inout Int) -> Bool { - func splitCurveIfWithinAccuracyAndNumericalLimits(_ curve: Subcurve, boundingBox: BoundingBox) -> (left: Subcurve, right: Subcurve)? { + func splitCurveAndCheckIfShouldRecurse(_ curve: Subcurve, boundingBox: BoundingBox) -> (left: Subcurve, right: Subcurve, shouldRecurse: Bool) { + let (left, right) = curve.split(at: 0.5) // first check if the curve is small enough that it falls within // accuracy threshold and therefore does not require further recursion - guard boundingBox.size.x + boundingBox.size.y >= accuracy else { return nil } + guard boundingBox.size.x + boundingBox.size.y >= accuracy else { + return (left: left, right: right, shouldRecurse: false) + } // next check if splitting the curve results in either half being a point // if so, we've reached numerical precision limits of how far we can split things - let (left, right) = curve.split(at: 0.5) - guard left.curve.isPoint == false else { return nil } - guard right.curve.isPoint == false else { return nil } - return (left: left, right: right) + guard left.curve.isPoint == false else { + return (left: left, right: right, shouldRecurse: false) + } + guard right.curve.isPoint == false else { + return (left: left, right: right, shouldRecurse: false) + } + return (left: left, right: right, shouldRecurse: true) } let maximumIterations = 900 @@ -337,10 +343,10 @@ internal class Utils { guard results.count <= maximumIntersections else { return false } guard c1b.overlaps(c2b) else { return true } - let cc1 = splitCurveIfWithinAccuracyAndNumericalLimits(c1, boundingBox: c1b) - let cc2 = splitCurveIfWithinAccuracyAndNumericalLimits(c2, boundingBox: c2b) + let cc1 = splitCurveAndCheckIfShouldRecurse(c1, boundingBox: c1b) + let cc2 = splitCurveAndCheckIfShouldRecurse(c2, boundingBox: c2b) - if cc1 == nil, cc2 == nil { + if cc1.shouldRecurse == false, cc2.shouldRecurse == false { // subcurves are small enough or we simply cannot recurse any more let l1 = LineSegment(p0: c1.curve.startingPoint, p1: c1.curve.endingPoint) let l2 = LineSegment(p0: c2.curve.startingPoint, p1: c2.curve.endingPoint) @@ -349,7 +355,7 @@ internal class Utils { let t2 = intersection.t2 results.append(Intersection(t1: t1 * c1.t2 + (1.0 - t1) * c1.t1, t2: t2 * c2.t2 + (1.0 - t2) * c2.t1)) - } else if let cc1 = cc1, let cc2 = cc2 { + } else if cc1.shouldRecurse, cc2.shouldRecurse { let cc1lb = cc1.left.curve.boundingBox let cc1rb = cc1.right.curve.boundingBox let cc2lb = cc2.left.curve.boundingBox @@ -358,12 +364,12 @@ internal class Utils { guard Utils.pairiteration(cc1.left, cc2.right, cc1lb, cc2rb, &results, accuracy, &totalIterations) else { return false } guard Utils.pairiteration(cc1.right, cc2.left, cc1rb, cc2lb, &results, accuracy, &totalIterations) else { return false } guard Utils.pairiteration(cc1.right, cc2.right, cc1rb, cc2rb, &results, accuracy, &totalIterations) else { return false } - } else if let cc1 = cc1 { + } else if cc1.shouldRecurse { let cc1lb = cc1.left.curve.boundingBox let cc1rb = cc1.right.curve.boundingBox guard Utils.pairiteration(cc1.left, c2, cc1lb, c2b, &results, accuracy, &totalIterations) else { return false } guard Utils.pairiteration(cc1.right, c2, cc1rb, c2b, &results, accuracy, &totalIterations) else { return false } - } else if let cc2 = cc2 { + } else if cc2.shouldRecurse { let cc2lb = cc2.left.curve.boundingBox let cc2rb = cc2.right.curve.boundingBox guard Utils.pairiteration(c1, cc2.left, c1b, cc2lb, &results, accuracy, &totalIterations) else { return false } From c122462374e00942c03d3b12e6d4deb7a5bcf3cf Mon Sep 17 00:00:00 2001 From: Holmes Futrell Date: Mon, 27 Jun 2022 16:47:47 -0700 Subject: [PATCH 52/63] a different way of fixing the issue, specifically targeting 32-bit architectures. --- BezierKit/Library/BezierCurve.swift | 5 +++ BezierKit/Library/Utils.swift | 58 ++++++++++++++--------------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/BezierKit/Library/BezierCurve.swift b/BezierKit/Library/BezierCurve.swift index ffc2144e..6dca58e5 100644 --- a/BezierKit/Library/BezierCurve.swift +++ b/BezierKit/Library/BezierCurve.swift @@ -16,6 +16,11 @@ public struct Subcurve where CurveType: BezierCurve { public let t2: CGFloat public let curve: CurveType + internal var canSplit: Bool { + let mid = 0.5 * (self.t1 + self.t2) + return mid > self.t1 && mid < self.t2 + } + internal init(curve: CurveType) { self.t1 = 0.0 self.t2 = 1.0 diff --git a/BezierKit/Library/Utils.swift b/BezierKit/Library/Utils.swift index e2faa242..1ed0fcb6 100644 --- a/BezierKit/Library/Utils.swift +++ b/BezierKit/Library/Utils.swift @@ -311,29 +311,11 @@ internal class Utils { // disable this SwiftLint warning about function having more than 5 parameters // swiftlint:disable function_parameter_count - static func pairiteration(_ c1: Subcurve, _ c2: Subcurve, - _ c1b: BoundingBox, _ c2b: BoundingBox, - _ results: inout [Intersection], - _ accuracy: CGFloat, - _ totalIterations: inout Int) -> Bool { - - func splitCurveAndCheckIfShouldRecurse(_ curve: Subcurve, boundingBox: BoundingBox) -> (left: Subcurve, right: Subcurve, shouldRecurse: Bool) { - let (left, right) = curve.split(at: 0.5) - // first check if the curve is small enough that it falls within - // accuracy threshold and therefore does not require further recursion - guard boundingBox.size.x + boundingBox.size.y >= accuracy else { - return (left: left, right: right, shouldRecurse: false) - } - // next check if splitting the curve results in either half being a point - // if so, we've reached numerical precision limits of how far we can split things - guard left.curve.isPoint == false else { - return (left: left, right: right, shouldRecurse: false) - } - guard right.curve.isPoint == false else { - return (left: left, right: right, shouldRecurse: false) - } - return (left: left, right: right, shouldRecurse: true) - } + static func pairiteration(_ c1: Subcurve, _ c2: Subcurve, + _ c1b: BoundingBox, _ c2b: BoundingBox, + _ results: inout [Intersection], + _ accuracy: CGFloat, + _ totalIterations: inout Int) -> Bool { let maximumIterations = 900 let maximumIntersections = c1.curve.order * c2.curve.order @@ -343,10 +325,24 @@ internal class Utils { guard results.count <= maximumIntersections else { return false } guard c1b.overlaps(c2b) else { return true } - let cc1 = splitCurveAndCheckIfShouldRecurse(c1, boundingBox: c1b) - let cc2 = splitCurveAndCheckIfShouldRecurse(c2, boundingBox: c2b) + func shouldRecurse(for subcurve: Subcurve, boundingBox: BoundingBox) -> Bool { + guard subcurve.canSplit else { return false } + guard boundingBox.size.x + boundingBox.size.y >= accuracy else { return false } + if MemoryLayout.size == 4 { + // limit recursion when we exceed Float32 precision + let midPoint = subcurve.curve.point(at: 0.5) + if midPoint == subcurve.curve.startingPoint || + midPoint == subcurve.curve.endingPoint { + guard c1.curve.selfIntersects else { return false } + } + } + return true + } + + let shouldRecurse1 = shouldRecurse(for: c1, boundingBox: c1b) + let shouldRecurse2 = shouldRecurse(for: c2, boundingBox: c2b) - if cc1.shouldRecurse == false, cc2.shouldRecurse == false { + if shouldRecurse1 == false, shouldRecurse2 == false { // subcurves are small enough or we simply cannot recurse any more let l1 = LineSegment(p0: c1.curve.startingPoint, p1: c1.curve.endingPoint) let l2 = LineSegment(p0: c2.curve.startingPoint, p1: c2.curve.endingPoint) @@ -355,7 +351,9 @@ internal class Utils { let t2 = intersection.t2 results.append(Intersection(t1: t1 * c1.t2 + (1.0 - t1) * c1.t1, t2: t2 * c2.t2 + (1.0 - t2) * c2.t1)) - } else if cc1.shouldRecurse, cc2.shouldRecurse { + } else if shouldRecurse1, shouldRecurse2 { + let cc1 = c1.split(at: 0.5) + let cc2 = c2.split(at: 0.5) let cc1lb = cc1.left.curve.boundingBox let cc1rb = cc1.right.curve.boundingBox let cc2lb = cc2.left.curve.boundingBox @@ -364,12 +362,14 @@ internal class Utils { guard Utils.pairiteration(cc1.left, cc2.right, cc1lb, cc2rb, &results, accuracy, &totalIterations) else { return false } guard Utils.pairiteration(cc1.right, cc2.left, cc1rb, cc2lb, &results, accuracy, &totalIterations) else { return false } guard Utils.pairiteration(cc1.right, cc2.right, cc1rb, cc2rb, &results, accuracy, &totalIterations) else { return false } - } else if cc1.shouldRecurse { + } else if shouldRecurse1 { + let cc1 = c1.split(at: 0.5) let cc1lb = cc1.left.curve.boundingBox let cc1rb = cc1.right.curve.boundingBox guard Utils.pairiteration(cc1.left, c2, cc1lb, c2b, &results, accuracy, &totalIterations) else { return false } guard Utils.pairiteration(cc1.right, c2, cc1rb, c2b, &results, accuracy, &totalIterations) else { return false } - } else if cc2.shouldRecurse { + } else if shouldRecurse2 { + let cc2 = c2.split(at: 0.5) let cc2lb = cc2.left.curve.boundingBox let cc2rb = cc2.right.curve.boundingBox guard Utils.pairiteration(c1, cc2.left, c1b, cc2lb, &results, accuracy, &totalIterations) else { return false } From 88f16ff0a7be4e21fb9faffda427a020aa487db5 Mon Sep 17 00:00:00 2001 From: Holmes Futrell Date: Mon, 27 Jun 2022 16:59:49 -0700 Subject: [PATCH 53/63] optimization --- BezierKit/Library/Utils.swift | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/BezierKit/Library/Utils.swift b/BezierKit/Library/Utils.swift index 1ed0fcb6..e680c6dd 100644 --- a/BezierKit/Library/Utils.swift +++ b/BezierKit/Library/Utils.swift @@ -308,6 +308,21 @@ internal class Utils { return atan2(d1.cross(d2), d1.dot(d2)) } + @inline(__always) private static func shouldRecurse(for subcurve: Subcurve, boundingBoxSize: CGPoint, accuracy: CGFloat) -> Bool { + guard subcurve.canSplit else { return false } + guard boundingBoxSize.x + boundingBoxSize.y >= accuracy else { return false } + if MemoryLayout.size == 4 { + let curve = subcurve.curve + // limit recursion when we exceed Float32 precision + let midPoint = curve.point(at: 0.5) + if midPoint == curve.startingPoint || + midPoint == curve.endingPoint { + guard curve.selfIntersects else { return false } + } + } + return true + } + // disable this SwiftLint warning about function having more than 5 parameters // swiftlint:disable function_parameter_count @@ -325,22 +340,8 @@ internal class Utils { guard results.count <= maximumIntersections else { return false } guard c1b.overlaps(c2b) else { return true } - func shouldRecurse(for subcurve: Subcurve, boundingBox: BoundingBox) -> Bool { - guard subcurve.canSplit else { return false } - guard boundingBox.size.x + boundingBox.size.y >= accuracy else { return false } - if MemoryLayout.size == 4 { - // limit recursion when we exceed Float32 precision - let midPoint = subcurve.curve.point(at: 0.5) - if midPoint == subcurve.curve.startingPoint || - midPoint == subcurve.curve.endingPoint { - guard c1.curve.selfIntersects else { return false } - } - } - return true - } - - let shouldRecurse1 = shouldRecurse(for: c1, boundingBox: c1b) - let shouldRecurse2 = shouldRecurse(for: c2, boundingBox: c2b) + let shouldRecurse1 = shouldRecurse(for: c1, boundingBoxSize: c1b.size, accuracy: accuracy) + let shouldRecurse2 = shouldRecurse(for: c2, boundingBoxSize: c2b.size, accuracy: accuracy) if shouldRecurse1 == false, shouldRecurse2 == false { // subcurves are small enough or we simply cannot recurse any more From 4b7c4b89269ddee808437d39de404f1ed13e0af2 Mon Sep 17 00:00:00 2001 From: Holmes Futrell Date: Mon, 27 Jun 2022 17:02:59 -0700 Subject: [PATCH 54/63] remove point classifiable. --- BezierKit/Library/BezierCurve.swift | 5 ----- BezierKit/Library/CubicCurve.swift | 4 ---- BezierKit/Library/LineSegment.swift | 4 ---- BezierKit/Library/QuadraticCurve.swift | 4 ---- 4 files changed, 17 deletions(-) diff --git a/BezierKit/Library/BezierCurve.swift b/BezierKit/Library/BezierCurve.swift index 6dca58e5..937014b2 100644 --- a/BezierKit/Library/BezierCurve.swift +++ b/BezierKit/Library/BezierCurve.swift @@ -307,11 +307,6 @@ public protocol BezierCurve: BoundingBoxProtocol, Transformable, Reversible { func intersections(with curve: BezierCurve, accuracy: CGFloat) -> [Intersection] } -internal protocol PointClassifiable { - /// returns true if all control points are equal - var isPoint: Bool { get } -} - internal protocol NonlinearBezierCurve: BezierCurve, ComponentPolynomials, Implicitizeable, PointClassifiable { // intentionally empty, just declare conformance if you're not a line } diff --git a/BezierKit/Library/CubicCurve.swift b/BezierKit/Library/CubicCurve.swift index 3877d627..efd88994 100644 --- a/BezierKit/Library/CubicCurve.swift +++ b/BezierKit/Library/CubicCurve.swift @@ -341,7 +341,3 @@ extension CubicCurve: Flatness { return (1.0 / 16.0) * ( temp1 + temp2 ) } } - -extension CubicCurve: PointClassifiable { - var isPoint: Bool { return self.p0 == self.p1 && self.p1 == self.p2 && self.p2 == self.p3 } -} diff --git a/BezierKit/Library/LineSegment.swift b/BezierKit/Library/LineSegment.swift index d83dfd01..cae2f55a 100644 --- a/BezierKit/Library/LineSegment.swift +++ b/BezierKit/Library/LineSegment.swift @@ -129,7 +129,3 @@ extension LineSegment: Flatness { public var flatnessSquared: CGFloat { return 0.0 } public var flatness: CGFloat { return 0.0 } } - -extension LineSegment: PointClassifiable { - var isPoint: Bool { return self.p0 == self.p1 } -} diff --git a/BezierKit/Library/QuadraticCurve.swift b/BezierKit/Library/QuadraticCurve.swift index 75dd73ad..5d0c2647 100644 --- a/BezierKit/Library/QuadraticCurve.swift +++ b/BezierKit/Library/QuadraticCurve.swift @@ -233,7 +233,3 @@ extension QuadraticCurve: Flatness { return (1.0 / 16.0) * (a.x * a.x + a.y * a.y) } } - -extension QuadraticCurve: PointClassifiable { - var isPoint: Bool { return self.p0 == self.p1 && self.p1 == self.p2 } -} From 0d4649e76954c0ce379f309aa08ca8ebfec19dba Mon Sep 17 00:00:00 2001 From: Holmes Futrell Date: Mon, 27 Jun 2022 17:03:27 -0700 Subject: [PATCH 55/63] remove point classifiable. --- BezierKit/Library/BezierCurve.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BezierKit/Library/BezierCurve.swift b/BezierKit/Library/BezierCurve.swift index 937014b2..a28848a9 100644 --- a/BezierKit/Library/BezierCurve.swift +++ b/BezierKit/Library/BezierCurve.swift @@ -307,7 +307,7 @@ public protocol BezierCurve: BoundingBoxProtocol, Transformable, Reversible { func intersections(with curve: BezierCurve, accuracy: CGFloat) -> [Intersection] } -internal protocol NonlinearBezierCurve: BezierCurve, ComponentPolynomials, Implicitizeable, PointClassifiable { +internal protocol NonlinearBezierCurve: BezierCurve, ComponentPolynomials, Implicitizeable { // intentionally empty, just declare conformance if you're not a line } From f05e04e32c80acde9df09fba35dcae9880737da6 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Tue, 28 Jun 2022 12:14:04 -0700 Subject: [PATCH 56/63] Revert "WIP" This reverts commit c1f2a7ea185fdfa6b25a15267a03f1c350ad9721. --- .../Library/BezierCurve+Implicitization.swift | 10 +- .../Library/BezierCurve+Intersection.swift | 91 +++++++------------ 2 files changed, 33 insertions(+), 68 deletions(-) diff --git a/BezierKit/Library/BezierCurve+Implicitization.swift b/BezierKit/Library/BezierCurve+Implicitization.swift index 517ebf07..2404559f 100644 --- a/BezierKit/Library/BezierCurve+Implicitization.swift +++ b/BezierKit/Library/BezierCurve+Implicitization.swift @@ -113,7 +113,7 @@ internal struct ImplicitPolynomial { } } -struct ImplicitLineProduct { +private struct ImplicitLineProduct { var a20, a11, a10, a02, a01, a00: CGFloat static func * (left: ImplicitLine, right: ImplicitLineProduct) -> ImplicitPolynomial { let a00 = left.a00 * right.a00 @@ -152,7 +152,7 @@ struct ImplicitLineProduct { } } -struct ImplicitLine { +private struct ImplicitLine { var a10, a01, a00: CGFloat static func * (left: ImplicitLine, right: ImplicitLine) -> ImplicitLineProduct { return ImplicitLineProduct(a20: left.a10 * right.a10, @@ -170,12 +170,6 @@ struct ImplicitLine { a01: left.a01 + right.a01, a00: left.a00 + right.a00) } - func value(at point: CGPoint) -> CGFloat { - return a10 * point.x + a01 * point.y + a00 - } - var d: CGPoint { - return CGPoint(x: a10, y: a01) - } } private extension BezierCurve { diff --git a/BezierKit/Library/BezierCurve+Intersection.swift b/BezierKit/Library/BezierCurve+Intersection.swift index fc5b4811..a73fefc1 100644 --- a/BezierKit/Library/BezierCurve+Intersection.swift +++ b/BezierKit/Library/BezierCurve+Intersection.swift @@ -115,32 +115,6 @@ fileprivate extension BezierCurve { } } -private func implicitLine(_ pi: CGPoint, _ pj: CGPoint, _ k: Int) -> ImplicitLine { - return CGFloat(k) * ImplicitLine(a10: pi.y - pj.y, a01: pj.x - pi.x, a00: pi.x * pj.y - pj.x * pi.y) -} - -func f(_ k: ImplicitLine, _ l: ImplicitLine, _ m: ImplicitLine, _ curve: U, _ t: CGFloat) -> CGFloat { - let p = curve.point(at: t) - return k.value(at: p) * k.value(at: p) - l.value(at: p) * m.value(at: p) -} - -func df(_ k: ImplicitLine, _ l: ImplicitLine, _ m: ImplicitLine, _ curve: U, _ t: CGFloat) -> CGFloat { - let p = curve.point(at: t) - let pp = curve.derivative(at: t) - let temp = 2.0 * k.value(at: p) * k.d - l.value(at: p) * m.d - m.value(at: p) * l.d - return temp.dot(pp) -} - -func ddf(_ k: ImplicitLine, _ l: ImplicitLine, _ m: ImplicitLine, _ curve: U, _ t: CGFloat) -> CGFloat { - let p = curve.point(at: t) - let pp = curve.derivative(at: t) - let ppp = 2.0 * (curve.points[2] - 2.0 * curve.points[1] + curve.points[0]) - let temp1 = (2.0 * k.value(at: p) * k.d - l.value(at: p) * m.d - m.value(at: p) * l.d).dot(ppp) - let temp2 = (2.0 * k.d.dot(pp) * k.d - l.d.dot(pp) * m.d - m.d.dot(pp) * l.d).dot(pp) - print("temp1 = \(temp1), temp2 = \(temp2), sum = \(temp1 + temp2)") - return temp1 + temp2 -} - internal func helperIntersectsCurveCurve(_ curve1: Subcurve, _ curve2: Subcurve, accuracy: CGFloat) -> [Intersection] where U: NonlinearBezierCurve, T: NonlinearBezierCurve { // try intersecting using subdivision @@ -148,48 +122,45 @@ internal func helperIntersectsCurveCurve(_ curve1: Subcurve, _ curve2: let rb = curve2.curve.boundingBox var pairIntersections: [Intersection] = [] var subdivisionIterations = 0 -// if Utils.pairiteration(curve1, curve2, lb, rb, &pairIntersections, accuracy, &subdivisionIterations) { -// return pairIntersections.sortedAndUniqued() -// } -// -// // subdivision failed, check if the curves are coincident -// let insignificantDistance: CGFloat = 0.5 * accuracy -// if let coincidence = coincidenceCheck(curve1.curve, curve2.curve, accuracy: 0.1 * accuracy) { -// return coincidence -// } - - let k = implicitLine(curve2.curve.points[2], curve2.curve.points[0], 1) - let l = implicitLine(curve2.curve.points[2], curve2.curve.points[1], 2) - let m = implicitLine(curve2.curve.points[1], curve2.curve.points[0], 2) - //let lineProduct = k * k - l * m - - let pStart = curve1.curve.startingPoint - let pEnd = curve1.curve.endingPoint - - let n = 4 - let p0 = f(k, l, m, curve1.curve, 0) - let p1 = df(k, l, m, curve1.curve, 0) / CGFloat(n) + p0 - let p2 = ddf(k, l, m, curve1.curve, 0) / CGFloat(n) / CGFloat(n-1) + 2 * p1 - p0 - let p4 = f(k, l, m, curve1.curve, 1) - let p3 = -df(k, l, m, curve1.curve, 1) / CGFloat(n) + p4 - - let c1 = curve1.curve - let c2 = curve2.curve - - let equation = BernsteinPolynomialN(coefficients: [p0, p1, p2, p3, p4]) - let equation2: BernsteinPolynomialN = -1 * c2.implicitPolynomial.value(c1.xPolynomial, c1.yPolynomial) - - print("expected \(equation2.derivative.derivative.value(at: 0))") + if Utils.pairiteration(curve1, curve2, lb, rb, &pairIntersections, accuracy, &subdivisionIterations) { + return pairIntersections.sortedAndUniqued() + } + + // subdivision failed, check if the curves are coincident + let insignificantDistance: CGFloat = 0.5 * accuracy + if let coincidence = coincidenceCheck(curve1.curve, curve2.curve, accuracy: 0.1 * accuracy) { + return coincidence + } + + // find any intersections using curve implicitization + let transform = CGAffineTransform(translationX: -curve2.curve.startingPoint.x, y: -curve2.curve.startingPoint.y) + let c2 = curve2.curve.downgradedIfPossible(maximumError: insignificantDistance).copy(using: transform) + let c1 = curve1.curve.copy(using: transform) + let equation: BernsteinPolynomialN = c2.implicitPolynomial.value(c1.xPolynomial, c1.yPolynomial) let roots = equation.distinctRealRootsInUnitInterval(configuration: RootFindingConfiguration(errorThreshold: RootFindingConfiguration.minimumErrorThreshold)) + let t1Tolerance = insignificantDistance / c1.derivativeBounds + let t2Tolerance = insignificantDistance / c2.derivativeBounds + func intersectionIfCloseEnough(at t1: CGFloat) -> Intersection? { let point = c1.point(at: t1) - let t2 = c2.project(point).t + guard c2.boundingBox.contains(point) else { return nil } + var t2 = c2.project(point).t + if t2 < t2Tolerance { + t2 = 0 + } else if t2 > 1 - t2Tolerance { + t2 = 1 + } + guard distance(point, c2.point(at: t2)) < accuracy else { return nil } return Intersection(t1: t1, t2: t2) } - var intersections = roots.compactMap { t1 -> Intersection? in + if t1 < t1Tolerance { + return nil // (t1 near 0 handled explicitly) + } else if t1 > 1 - t1Tolerance { + return nil // (t1 near 1 handled explicitly) + } return intersectionIfCloseEnough(at: t1) } if intersections.contains(where: { $0.t1 == 0 }) == false { From b0af3102f15cf10b3cbc620527223502fe29c1ff Mon Sep 17 00:00:00 2001 From: hfutrell Date: Tue, 28 Jun 2022 12:19:31 -0700 Subject: [PATCH 57/63] Revert "Make pairiteration stack-friendly" This reverts commit 16535a82d3f42647ecafc7793ebe75ffa650acb8. --- .../Library/BezierCurve+Intersection.swift | 3 +- BezierKit/Library/Utils.swift | 156 ++++++------------ 2 files changed, 51 insertions(+), 108 deletions(-) diff --git a/BezierKit/Library/BezierCurve+Intersection.swift b/BezierKit/Library/BezierCurve+Intersection.swift index 9e7dcd71..3049e71f 100644 --- a/BezierKit/Library/BezierCurve+Intersection.swift +++ b/BezierKit/Library/BezierCurve+Intersection.swift @@ -121,7 +121,8 @@ internal func helperIntersectsCurveCurve(_ curve1: Subcurve, _ curve2: let lb = curve1.curve.boundingBox let rb = curve2.curve.boundingBox var pairIntersections: [Intersection] = [] - if Utils.pairiteration(curve1, curve2, lb, rb, &pairIntersections, accuracy) { + var subdivisionIterations = 0 + if Utils.pairiteration(curve1, curve2, lb, rb, &pairIntersections, accuracy, &subdivisionIterations) { return pairIntersections.sortedAndUniqued() } diff --git a/BezierKit/Library/Utils.swift b/BezierKit/Library/Utils.swift index da3034e8..e76baa61 100644 --- a/BezierKit/Library/Utils.swift +++ b/BezierKit/Library/Utils.swift @@ -308,122 +308,64 @@ internal class Utils { return atan2(d1.cross(d2), d1.dot(d2)) } - enum PairiterationStrategy { - /// DFS by recursive function call - case recurse - /// DFS by stack to avoid deep call stack. - /// This strategy is 2x slower than `recurse` strategy, - /// but consumes much less call stack. - case manualStack - - static var `default`: PairiterationStrategy { - // Use stack-friendly strategy on WebAssembly since - // wasm-ld, a defacto WebAssembly linker sets stack size - // very small (64KB) by default. - #if arch(wasm32) - return .manualStack - #else - return .recurse - #endif - } - } - // disable this SwiftLint warning about function having more than 5 parameters // swiftlint:disable function_parameter_count + static func pairiteration(_ c1: Subcurve, _ c2: Subcurve, _ c1b: BoundingBox, _ c2b: BoundingBox, _ results: inout [Intersection], _ accuracy: CGFloat, - _ strategy: PairiterationStrategy = .default) -> Bool { + _ totalIterations: inout Int) -> Bool { let maximumIterations = 900 - var totalIterations = 0 - - /// Parameters - /// - enqueueWork: A closure to enqueue a child work. Returns `false` when search should be stopped - func process(_ c1: Subcurve, _ c2: Subcurve, - _ c1b: BoundingBox, _ c2b: BoundingBox, - _ results: inout [Intersection], - _ enqueueWork: (_ c1: Subcurve, _ c2: Subcurve, - _ c1b: BoundingBox, _ c2b: BoundingBox, - inout [Intersection]) -> Bool - ) -> Bool { - totalIterations += 1 - guard totalIterations <= maximumIterations else { return false } - - let maximumIntersections = c1.curve.order * c2.curve.order - guard results.count <= maximumIntersections else { return false } - - // fast-path: when no overlap in boxes - guard c1b.overlaps(c2b) else { return true } - - let canSplit1 = c1.canSplit - let canSplit2 = c2.canSplit - let size1 = c1b.size - let size2 = c2b.size - let shouldRecurse1 = canSplit1 && ((size1.x + size1.y) >= accuracy) - let shouldRecurse2 = canSplit2 && ((size2.x + size2.y) >= accuracy) - - if shouldRecurse1 == false, shouldRecurse2 == false { - // subcurves are small enough or we simply cannot recurse any more - let l1 = LineSegment(p0: c1.curve.startingPoint, p1: c1.curve.endingPoint) - let l2 = LineSegment(p0: c2.curve.startingPoint, p1: c2.curve.endingPoint) - guard let intersection = l1.intersections(with: l2, checkCoincidence: false).first else { return true } - let t1 = intersection.t1 - let t2 = intersection.t2 - results.append(Intersection(t1: t1 * c1.t2 + (1.0 - t1) * c1.t1, - t2: t2 * c2.t2 + (1.0 - t2) * c2.t1)) - } else if shouldRecurse1, shouldRecurse2 { - let cc1 = c1.split(at: 0.5) - let cc2 = c2.split(at: 0.5) - let cc1lb = cc1.left.curve.boundingBox - let cc1rb = cc1.right.curve.boundingBox - let cc2lb = cc2.left.curve.boundingBox - let cc2rb = cc2.right.curve.boundingBox - guard enqueueWork(cc1.right, cc2.right, cc1rb, cc2rb, &results) else { return false } - guard enqueueWork(cc1.right, cc2.left, cc1rb, cc2lb, &results) else { return false } - guard enqueueWork(cc1.left, cc2.right, cc1lb, cc2rb, &results) else { return false } - guard enqueueWork(cc1.left, cc2.left, cc1lb, cc2lb, &results) else { return false } - } else if shouldRecurse1 { - let cc1 = c1.split(at: 0.5) - let cc1lb = cc1.left.curve.boundingBox - let cc1rb = cc1.right.curve.boundingBox - guard enqueueWork(cc1.right, c2, cc1rb, c2b, &results) else { return false } - guard enqueueWork(cc1.left, c2, cc1lb, c2b, &results) else { return false } - } else if shouldRecurse2 { - let cc2 = c2.split(at: 0.5) - let cc2lb = cc2.left.curve.boundingBox - let cc2rb = cc2.right.curve.boundingBox - guard enqueueWork(c1, cc2.left, c1b, cc2lb, &results) else { return false } - guard enqueueWork(c1, cc2.right, c1b, cc2rb, &results) else { return false } - } - return true - } - - switch strategy { - case .recurse: - func runImmediately(_ c1: Subcurve, _ c2: Subcurve, - _ c1b: BoundingBox, _ c2b: BoundingBox, - _ results: inout [Intersection]) -> Bool { - return process(c1, c2, c1b, c2b, &results) { - runImmediately($0, $1, $2, $3, &$4) - } - } - return runImmediately(c1, c2, c1b, c2b, &results) - case .manualStack: - typealias Work = (c1: Subcurve, c2: Subcurve, c1b: BoundingBox, c2b: BoundingBox) - var worklist: [Work] = [(c1, c2, c1b, c2b)] - while let work = worklist.popLast() { - let shouldContinue = process(work.c1, work.c2, work.c1b, work.c2b, &results) { c1, c2, c1b, c2b, _ in - worklist.append((c1, c2, c1b, c2b)) - return true - } - guard shouldContinue else { - return false - } - } - return true + let maximumIntersections = c1.curve.order * c2.curve.order + + totalIterations += 1 + guard totalIterations <= maximumIterations else { return false } + guard results.count <= maximumIntersections else { return false } + guard c1b.overlaps(c2b) else { return true } + + let canSplit1 = c1.canSplit + let canSplit2 = c2.canSplit + let size1 = c1b.size + let size2 = c2b.size + let shouldRecurse1 = canSplit1 && ((size1.x + size1.y) >= accuracy) + let shouldRecurse2 = canSplit2 && ((size2.x + size2.y) >= accuracy) + + if shouldRecurse1 == false, shouldRecurse2 == false { + // subcurves are small enough or we simply cannot recurse any more + let l1 = LineSegment(p0: c1.curve.startingPoint, p1: c1.curve.endingPoint) + let l2 = LineSegment(p0: c2.curve.startingPoint, p1: c2.curve.endingPoint) + guard let intersection = l1.intersections(with: l2, checkCoincidence: false).first else { return true } + let t1 = intersection.t1 + let t2 = intersection.t2 + results.append(Intersection(t1: t1 * c1.t2 + (1.0 - t1) * c1.t1, + t2: t2 * c2.t2 + (1.0 - t2) * c2.t1)) + } else if shouldRecurse1, shouldRecurse2 { + let cc1 = c1.split(at: 0.5) + let cc2 = c2.split(at: 0.5) + let cc1lb = cc1.left.curve.boundingBox + let cc1rb = cc1.right.curve.boundingBox + let cc2lb = cc2.left.curve.boundingBox + let cc2rb = cc2.right.curve.boundingBox + guard Utils.pairiteration(cc1.left, cc2.left, cc1lb, cc2lb, &results, accuracy, &totalIterations) else { return false } + guard Utils.pairiteration(cc1.left, cc2.right, cc1lb, cc2rb, &results, accuracy, &totalIterations) else { return false } + guard Utils.pairiteration(cc1.right, cc2.left, cc1rb, cc2lb, &results, accuracy, &totalIterations) else { return false } + guard Utils.pairiteration(cc1.right, cc2.right, cc1rb, cc2rb, &results, accuracy, &totalIterations) else { return false } + } else if shouldRecurse1 { + let cc1 = c1.split(at: 0.5) + let cc1lb = cc1.left.curve.boundingBox + let cc1rb = cc1.right.curve.boundingBox + guard Utils.pairiteration(cc1.left, c2, cc1lb, c2b, &results, accuracy, &totalIterations) else { return false } + guard Utils.pairiteration(cc1.right, c2, cc1rb, c2b, &results, accuracy, &totalIterations) else { return false } + } else if shouldRecurse2 { + let cc2 = c2.split(at: 0.5) + let cc2lb = cc2.left.curve.boundingBox + let cc2rb = cc2.right.curve.boundingBox + guard Utils.pairiteration(c1, cc2.left, c1b, cc2lb, &results, accuracy, &totalIterations) else { return false } + guard Utils.pairiteration(c1, cc2.right, c1b, cc2rb, &results, accuracy, &totalIterations) else { return false } } + return true } // swiftlint:enable function_parameter_count From 610aa71f5d6260e68d826ac691702fdef72b5f95 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Tue, 28 Jun 2022 12:21:05 -0700 Subject: [PATCH 58/63] Revert "Minimal CGFloat to Double change to fix precision on 32bit arch" This reverts commit 4c0573ac528b969c653fde07b49147dc2e4e6dc2. --- .../BezierKitTests/CubicCurveTests.swift | 3 ++ .../BezierKitTests/PolynomialTests.swift | 7 ++-- BezierKit/Library/BernsteinPolynomialN.swift | 22 +++++------ .../Library/BezierCurve+Implicitization.swift | 12 +++--- .../Library/BezierCurve+Intersection.swift | 12 +++--- BezierKit/Library/BezierCurve.swift | 12 +++--- BezierKit/Library/CGPoint+Overloads.swift | 10 ++--- BezierKit/Library/CubicCurve.swift | 18 ++++----- BezierKit/Library/LineSegment.swift | 6 +-- BezierKit/Library/Path.swift | 16 ++++---- BezierKit/Library/PathComponent.swift | 4 +- BezierKit/Library/Polynomial.swift | 38 +++++++++---------- BezierKit/Library/QuadraticCurve.swift | 6 +-- BezierKit/Library/Utils.swift | 8 ++-- 14 files changed, 89 insertions(+), 85 deletions(-) diff --git a/BezierKit/BezierKitTests/CubicCurveTests.swift b/BezierKit/BezierKitTests/CubicCurveTests.swift index d7bd2e08..65a59181 100644 --- a/BezierKit/BezierKitTests/CubicCurveTests.swift +++ b/BezierKit/BezierKitTests/CubicCurveTests.swift @@ -505,6 +505,8 @@ class CubicCurveTests: XCTestCase { XCTAssertEqual(c1.intersections(with: c2, accuracy: 1.0e-8), expectedIntersections) } + // Skip on platforms where CGFloat is 32bit + #if !(arch(i386) || arch(arm) || arch(wasm32)) func testRealWorldNearlyCoincidentCurvesIntersection() { // these curves are nearly coincident over from c1's t = 0.278 to 1.0 // staying roughly 0.0002 distance of eachother @@ -524,6 +526,7 @@ class CubicCurveTests: XCTestCase { XCTAssertEqual(intersections[1].t1, 1) XCTAssertEqual(intersections[1].t2, 0) } + #endif func testIntersectionsCubicButActuallyLinear() { // this test presents a challenge for an implicitization based approach diff --git a/BezierKit/BezierKitTests/PolynomialTests.swift b/BezierKit/BezierKitTests/PolynomialTests.swift index e0746327..cabd24d8 100644 --- a/BezierKit/BezierKitTests/PolynomialTests.swift +++ b/BezierKit/BezierKitTests/PolynomialTests.swift @@ -57,7 +57,7 @@ class PolynomialTests: XCTestCase { func testDegree3() { // x^3 - 6x^2 + 11x - 6 let polynomial = BernsteinPolynomial3(b0: -6, b1: -7.0 / 3.0, b2: -2.0 / 3.0, b3: 0) - XCTAssertEqual(polynomial.coefficients, [-6, Double(-7.0 / 3.0), Double(-2.0 / 3.0), 0.0]) + XCTAssertEqual(polynomial.coefficients, [-6, CGFloat(-7.0 / 3.0), CGFloat(-2.0 / 3.0), 0.0]) let roots = findDistinctRoots(of: polynomial, between: 0, and: 4) XCTAssertEqual(roots[0], 1, accuracy: accuracy) XCTAssertEqual(roots[1], 2, accuracy: accuracy) @@ -93,7 +93,8 @@ class PolynomialTests: XCTestCase { XCTAssertEqual(roots[2], 1, accuracy: accuracy) XCTAssertEqual(roots[3], 1.2, accuracy: accuracy) } - + // Skip on platforms where CGFloat is 32bit + #if !(arch(i386) || arch(arm) || arch(wasm32)) func testDegree4RepeatedRoots() { // x^4 - 2x^2 + 1 let polynomial = BernsteinPolynomial4(b0: 1, b1: 1, b2: 2.0 / 3.0, b3: 0, b4: 0) @@ -102,7 +103,7 @@ class PolynomialTests: XCTestCase { XCTAssertEqual(roots[0], -1, accuracy: accuracy) XCTAssertEqual(roots[1], 1, accuracy: accuracy) } - + #endif func testDegree5() { // 0.2x^5 - 0.813333x^3 - 8.56x let polynomial = BernsteinPolynomial5(b0: 0, b1: -1.712, b2: -3.424, b3: -5.2173333, b4: -7.1733332, b5: -9.173333) diff --git a/BezierKit/Library/BernsteinPolynomialN.swift b/BezierKit/Library/BernsteinPolynomialN.swift index 584ec932..59adaf22 100644 --- a/BezierKit/Library/BernsteinPolynomialN.swift +++ b/BezierKit/Library/BernsteinPolynomialN.swift @@ -11,18 +11,18 @@ import CoreGraphics import Foundation struct BernsteinPolynomialN: BernsteinPolynomial { - let coefficients: [Double] + let coefficients: [CGFloat] func difference(a1: CGFloat, a2: CGFloat) -> BernsteinPolynomialN { fatalError("unimplemented.") } var order: Int { return coefficients.count - 1 } - init(coefficients: [Double]) { + init(coefficients: [CGFloat]) { precondition(coefficients.isEmpty == false, "Bezier curves require at least one point") self.coefficients = coefficients } - static func * (left: Double, right: BernsteinPolynomialN) -> BernsteinPolynomialN { + static func * (left: CGFloat, right: BernsteinPolynomialN) -> BernsteinPolynomialN { return BernsteinPolynomialN(coefficients: right.coefficients.map { left * $0 }) } static func == (left: BernsteinPolynomialN, right: BernsteinPolynomialN) -> Bool { @@ -35,7 +35,7 @@ struct BernsteinPolynomialN: BernsteinPolynomial { guard order > 0 else { return BernsteinPolynomialN(coefficients: [CGFloat.zero]) } return CGFloat(order) * hodograph } - func value(at t: Double) -> Double { + func value(at t: CGFloat) -> CGFloat { return self.split(at: t).left.coefficients.last! } private var hodograph: BernsteinPolynomialN { @@ -43,16 +43,16 @@ struct BernsteinPolynomialN: BernsteinPolynomial { let differences = (0.. (left: BernsteinPolynomialN, right: BernsteinPolynomialN) { + func split(at t: CGFloat) -> (left: BernsteinPolynomialN, right: BernsteinPolynomialN) { guard order > 0 else { // splitting a point results in getting a point back return (left: self, right: self) } // apply de Casteljau Algorithm - var leftPoints = [Double](repeating: .zero, count: coefficients.count) - var rightPoints = [Double](repeating: .zero, count: coefficients.count) + var leftPoints = [CGFloat](repeating: .zero, count: coefficients.count) + var rightPoints = [CGFloat](repeating: .zero, count: coefficients.count) let n = order - var scratchPad: [Double] = coefficients + var scratchPad: [CGFloat] = coefficients leftPoints[0] = scratchPad[0] rightPoints[n] = scratchPad[n] for j in 1...n { @@ -65,7 +65,7 @@ struct BernsteinPolynomialN: BernsteinPolynomial { return (left: BernsteinPolynomialN(coefficients: leftPoints), right: BernsteinPolynomialN(coefficients: rightPoints)) } - func split(from t1: Double, to t2: Double) -> BernsteinPolynomialN { + func split(from t1: CGFloat, to t2: CGFloat) -> BernsteinPolynomialN { guard (t1 > t2) == false else { // simplifying to t1 <= t2 would infinite loop on NaN because NaN comparisons are always false return split(from: t2, to: t1).reversed() @@ -90,14 +90,14 @@ extension BernsteinPolynomialN { // 9.3 Multiplication of Polynomials in Bernstein Form let m = left.order let n = right.order - let points = (0...m + n).map { k -> Double in + let points = (0...m + n).map { k -> CGFloat in let start = max(k - n, 0) let end = min(m, k) let sum = (start...end).reduce(CGFloat.zero) { totalSoFar, i in let j = k - i return totalSoFar + CGFloat(Utils.binomialCoefficient(m, choose: i) * Utils.binomialCoefficient(n, choose: j)) * left.coefficients[i] * right.coefficients[j] } - let divisor = Double(Utils.binomialCoefficient(m + n, choose: k)) + let divisor = CGFloat(Utils.binomialCoefficient(m + n, choose: k)) return sum / divisor } return BernsteinPolynomialN(coefficients: points) diff --git a/BezierKit/Library/BezierCurve+Implicitization.swift b/BezierKit/Library/BezierCurve+Implicitization.swift index ccd9a0ef..2404559f 100644 --- a/BezierKit/Library/BezierCurve+Implicitization.swift +++ b/BezierKit/Library/BezierCurve+Implicitization.swift @@ -62,7 +62,7 @@ internal struct ImplicitPolynomial { } let resultOrder = order * polynomialOrder - var sum: BernsteinPolynomialN = BernsteinPolynomialN(coefficients: [Double](repeating: 0, count: resultOrder + 1)) + var sum: BernsteinPolynomialN = BernsteinPolynomialN(coefficients: [CGFloat](repeating: 0, count: resultOrder + 1)) for i in 0...order { let xPower: BernsteinPolynomialN = xPowers[i] for j in 0...order { @@ -79,7 +79,7 @@ internal struct ImplicitPolynomial { // swiftlint:disable shorthand_operator if k > 0 { // bring the term up to degree k - term = term * BernsteinPolynomialN(coefficients: [Double](repeating: 1, count: k + 1)) + term = term * BernsteinPolynomialN(coefficients: [CGFloat](repeating: 1, count: k + 1)) } else { assert(k == 0, "for k < 0 we should have c == 0") } @@ -90,10 +90,10 @@ internal struct ImplicitPolynomial { return sum } - func value(at point: CGPoint) -> Double { + func value(at point: CGPoint) -> CGFloat { let x = point.x let y = point.y - var sum: Double = 0 + var sum: CGFloat = 0 for i in 0...order { for j in 0...order { sum += coefficient(i, j) * pow(x, CGFloat(i)) * pow(y, CGFloat(j)) @@ -114,7 +114,7 @@ internal struct ImplicitPolynomial { } private struct ImplicitLineProduct { - var a20, a11, a10, a02, a01, a00: Double + var a20, a11, a10, a02, a01, a00: CGFloat static func * (left: ImplicitLine, right: ImplicitLineProduct) -> ImplicitPolynomial { let a00 = left.a00 * right.a00 let a10 = left.a00 * right.a10 + left.a10 * right.a00 @@ -153,7 +153,7 @@ private struct ImplicitLineProduct { } private struct ImplicitLine { - var a10, a01, a00: Double + var a10, a01, a00: CGFloat static func * (left: ImplicitLine, right: ImplicitLine) -> ImplicitLineProduct { return ImplicitLineProduct(a20: left.a10 * right.a10, a11: left.a01 * right.a10 + left.a10 * right.a01, diff --git a/BezierKit/Library/BezierCurve+Intersection.swift b/BezierKit/Library/BezierCurve+Intersection.swift index 3049e71f..a73fefc1 100644 --- a/BezierKit/Library/BezierCurve+Intersection.swift +++ b/BezierKit/Library/BezierCurve+Intersection.swift @@ -37,15 +37,15 @@ public extension BezierCurve { } private func coincidenceCheck(_ curve1: U, _ curve2: T, accuracy: CGFloat) -> [Intersection]? { - func pointIsCloseToCurve(_ point: CGPoint, _ curve: X) -> Double? { + func pointIsCloseToCurve(_ point: CGPoint, _ curve: X) -> CGFloat? { let (projection, t) = curve.project(point) guard distanceSquared(point, projection) < 4.0 * accuracy * accuracy else { return nil } return t } - var range1Start: Double = .infinity - var range1End: Double = -.infinity - var range2Start: Double = .infinity - var range2End: Double = -.infinity + var range1Start: CGFloat = .infinity + var range1End: CGFloat = -.infinity + var range2Start: CGFloat = .infinity + var range2End: CGFloat = -.infinity if range1Start > 0 || range2Start > 0 || range2End < 1 { if let t2 = pointIsCloseToCurve(curve1.startingPoint, curve2) { range1Start = 0 @@ -187,7 +187,7 @@ internal func helperIntersectsCurveLine(_ curve: U, _ line: LineSegment, reve let lineDirection = (line.p1 - line.p0) let lineLength = lineDirection.lengthSquared guard lineLength > 0 else { return [] } - func align(_ point: CGPoint) -> Double { + func align(_ point: CGPoint) -> CGFloat { return (point - line.p0).dot(lineDirection.perpendicular) } var intersections: [Intersection] = [] diff --git a/BezierKit/Library/BezierCurve.swift b/BezierKit/Library/BezierCurve.swift index 63c94b59..a28848a9 100644 --- a/BezierKit/Library/BezierCurve.swift +++ b/BezierKit/Library/BezierCurve.swift @@ -12,8 +12,8 @@ import CoreGraphics import Foundation public struct Subcurve where CurveType: BezierCurve { - public let t1: Double - public let t2: Double + public let t1: CGFloat + public let t2: CGFloat public let curve: CurveType internal var canSplit: Bool { @@ -27,7 +27,7 @@ public struct Subcurve where CurveType: BezierCurve { self.curve = curve } - internal init(t1: Double, t2: Double, curve: CurveType) { + internal init(t1: CGFloat, t2: CGFloat, curve: CurveType) { self.t1 = t1 self.t2 = t2 self.curve = curve @@ -289,15 +289,15 @@ public protocol BezierCurve: BoundingBoxProtocol, Transformable, Reversible { var endingPoint: CGPoint { get set } var order: Int { get } init(points: [CGPoint]) - func point(at t: Double) -> CGPoint + func point(at t: CGFloat) -> CGPoint func derivative(at t: CGFloat) -> CGPoint func normal(at t: CGFloat) -> CGPoint - func split(from t1: Double, to t2: Double) -> Self + func split(from t1: CGFloat, to t2: CGFloat) -> Self func split(at t: CGFloat) -> (left: Self, right: Self) func length() -> CGFloat func extrema() -> (x: [CGFloat], y: [CGFloat], all: [CGFloat]) func lookupTable(steps: Int) -> [CGPoint] - func project(_ point: CGPoint) -> (point: CGPoint, t: Double) + func project(_ point: CGPoint) -> (point: CGPoint, t: CGFloat) // intersection routines var selfIntersects: Bool { get } var selfIntersections: [Intersection] { get } diff --git a/BezierKit/Library/CGPoint+Overloads.swift b/BezierKit/Library/CGPoint+Overloads.swift index 44614802..0d92c3f8 100644 --- a/BezierKit/Library/CGPoint+Overloads.swift +++ b/BezierKit/Library/CGPoint+Overloads.swift @@ -14,10 +14,10 @@ import Foundation // swiftlint:disable shorthand_operator public extension CGPoint { - var length: Double { + var length: CGFloat { return sqrt(self.lengthSquared) } - internal var lengthSquared: Double { + internal var lengthSquared: CGFloat { return self.dot(self) } func normalize() -> CGPoint { @@ -52,7 +52,7 @@ public extension CGPoint { static internal var dimensions: Int { return 2 } - func dot(_ other: CGPoint) -> Double { + func dot(_ other: CGPoint) -> CGFloat { return self.x * other.x + self.y * other.y } func cross(_ other: CGPoint) -> CGFloat { @@ -88,10 +88,10 @@ public extension CGPoint { static func -= (left: inout CGPoint, right: CGPoint) { left = left - right } - static func / (left: CGPoint, right: Double) -> CGPoint { + static func / (left: CGPoint, right: CGFloat) -> CGPoint { return CGPoint(x: left.x / right, y: left.y / right) } - static func * (left: Double, right: CGPoint) -> CGPoint { + static func * (left: CGFloat, right: CGPoint) -> CGPoint { return CGPoint(x: left * right.x, y: left * right.y) } static prefix func - (point: CGPoint) -> CGPoint { diff --git a/BezierKit/Library/CubicCurve.swift b/BezierKit/Library/CubicCurve.swift index e8726c08..efd88994 100644 --- a/BezierKit/Library/CubicCurve.swift +++ b/BezierKit/Library/CubicCurve.swift @@ -62,8 +62,8 @@ public struct CubicCurve: NonlinearBezierCurve, Equatable { } public init(lineSegment: LineSegment) { - let oneThird: Double = 1.0 / 3.0 - let twoThirds: Double = 2.0 / 3.0 + let oneThird: CGFloat = 1.0 / 3.0 + let twoThirds: CGFloat = 2.0 / 3.0 self.init(p0: lineSegment.p0, p1: twoThirds * lineSegment.p0 + oneThird * lineSegment.p1, p2: oneThird * lineSegment.p0 + twoThirds * lineSegment.p1, @@ -71,8 +71,8 @@ public struct CubicCurve: NonlinearBezierCurve, Equatable { } public init(quadratic: QuadraticCurve) { - let oneThird: Double = 1.0 / 3.0 - let twoThirds: Double = 2.0 / 3.0 + let oneThird: CGFloat = 1.0 / 3.0 + let twoThirds: CGFloat = 2.0 / 3.0 self.init(p0: quadratic.p0, p1: twoThirds * quadratic.p1 + oneThird * quadratic.p0, p2: oneThird * quadratic.p2 + twoThirds * quadratic.p1, @@ -187,7 +187,7 @@ public struct CubicCurve: NonlinearBezierCurve, Equatable { return temp1 + temp2 + temp3 } - public func split(from t1: Double, to t2: Double) -> CubicCurve { + public func split(from t1: CGFloat, to t2: CGFloat) -> CubicCurve { guard t1 != 0.0 || t2 != 1.0 else { return self } let k = (t2 - t1) / 3.0 let p0 = self.point(at: t1) @@ -217,7 +217,7 @@ public struct CubicCurve: NonlinearBezierCurve, Equatable { } - public func project(_ point: CGPoint) -> (point: CGPoint, t: Double) { + public func project(_ point: CGPoint) -> (point: CGPoint, t: CGFloat) { func mul(_ a: CGPoint, _ b: CGPoint) -> CGPoint { return CGPoint(x: a.x * b.x, y: a.y * b.y) } @@ -298,15 +298,15 @@ public struct CubicCurve: NonlinearBezierCurve, Equatable { return BoundingBox(min: mmin, max: mmax) } - public func point(at t: Double) -> CGPoint { + public func point(at t: CGFloat) -> CGPoint { if t == 0 { return self.p0 } else if t == 1 { return self.p3 } let mt = 1.0 - t - let mt2: Double = mt*mt - let t2: Double = t*t + let mt2: CGFloat = mt*mt + let t2: CGFloat = t*t let a = mt2 * mt let b = mt2 * t * 3.0 let c = mt * t2 * 3.0 diff --git a/BezierKit/Library/LineSegment.swift b/BezierKit/Library/LineSegment.swift index a0badd12..cae2f55a 100644 --- a/BezierKit/Library/LineSegment.swift +++ b/BezierKit/Library/LineSegment.swift @@ -64,7 +64,7 @@ public struct LineSegment: BezierCurve, Equatable { return (self.p1 - self.p0).perpendicular.normalize() } - public func split(from t1: Double, to t2: Double) -> LineSegment { + public func split(from t1: CGFloat, to t2: CGFloat) -> LineSegment { return LineSegment(p0: self.point(at: t1), p1: self.point(at: t2)) } @@ -83,7 +83,7 @@ public struct LineSegment: BezierCurve, Equatable { return BoundingBox(min: CGPoint.min(p0, p1), max: CGPoint.max(p0, p1)) } - public func point(at t: Double) -> CGPoint { + public func point(at t: CGFloat) -> CGPoint { if t == 0 { return self.p0 } else if t == 1 { @@ -103,7 +103,7 @@ public struct LineSegment: BezierCurve, Equatable { return (x: [], y: [], all: []) } - public func project(_ point: CGPoint) -> (point: CGPoint, t: Double) { + public func project(_ point: CGPoint) -> (point: CGPoint, t: CGFloat) { // optimized implementation for line segments can be directly computed // default project implementation is found in BezierCurve protocol extension let relativePoint = point - self.p0 diff --git a/BezierKit/Library/Path.swift b/BezierKit/Library/Path.swift index 311152c4..6ebf8d94 100644 --- a/BezierKit/Library/Path.swift +++ b/BezierKit/Library/Path.swift @@ -106,11 +106,11 @@ open class Path: NSObject { public let components: [PathComponent] - public func selfIntersects(accuracy: Double = BezierKit.defaultIntersectionAccuracy) -> Bool { + public func selfIntersects(accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { return !self.selfIntersections(accuracy: accuracy).isEmpty } - public func selfIntersections(accuracy: Double = BezierKit.defaultIntersectionAccuracy) -> [PathIntersection] { + public func selfIntersections(accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> [PathIntersection] { var intersections: [PathIntersection] = [] for i in 0.. Bool { + public func intersects(_ other: Path, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { return !self.intersections(with: other, accuracy: accuracy).isEmpty } - public func intersections(with other: Path, accuracy: Double = BezierKit.defaultIntersectionAccuracy) -> [PathIntersection] { + public func intersections(with other: Path, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> [PathIntersection] { guard self.boundingBox.overlaps(other.boundingBox) else { return [] } @@ -301,7 +301,7 @@ open class Path: NSObject { return windingCountImpliesContainment(count, using: rule) } - public func contains(_ other: Path, using rule: PathFillRule = .winding, accuracy: Double = BezierKit.defaultIntersectionAccuracy) -> Bool { + public func contains(_ other: Path, using rule: PathFillRule = .winding, accuracy: CGFloat = BezierKit.defaultIntersectionAccuracy) -> Bool { // first, check that each component of `other` starts inside self for component in other.components { let p = component.startingPoint @@ -315,7 +315,7 @@ open class Path: NSObject { return !self.intersects(other, accuracy: accuracy) } - public func offset(distance d: Double) -> Path { + public func offset(distance d: CGFloat) -> Path { return Path(components: self.components.compactMap { $0.offset(distance: d) }) @@ -386,8 +386,8 @@ extension Path: Reversible { public struct IndexedPathLocation: Equatable, Comparable { public let componentIndex: Int public let elementIndex: Int - public let t: Double - public init(componentIndex: Int, elementIndex: Int, t: Double) { + public let t: CGFloat + public init(componentIndex: Int, elementIndex: Int, t: CGFloat) { self.componentIndex = componentIndex self.elementIndex = elementIndex self.t = t diff --git a/BezierKit/Library/PathComponent.swift b/BezierKit/Library/PathComponent.swift index 0de3292d..bf064419 100644 --- a/BezierKit/Library/PathComponent.swift +++ b/BezierKit/Library/PathComponent.swift @@ -234,7 +234,7 @@ open class PathComponent: NSObject, Reversible, Transformable { self.points = temp } - public var length: Double { + public var length: CGFloat { return self.curves.reduce(0.0) { $0 + $1.length() } } @@ -250,7 +250,7 @@ open class PathComponent: NSObject, Reversible, Transformable { return self.startingPoint == self.endingPoint } - public func offset(distance d: Double) -> PathComponent? { + public func offset(distance d: CGFloat) -> PathComponent? { var offsetCurves = self.curves.reduce([]) { $0 + $1.offset(distance: d) } diff --git a/BezierKit/Library/Polynomial.swift b/BezierKit/Library/Polynomial.swift index 4aa01c8b..d1483183 100644 --- a/BezierKit/Library/Polynomial.swift +++ b/BezierKit/Library/Polynomial.swift @@ -14,7 +14,7 @@ import Foundation public protocol BernsteinPolynomial: Equatable { func value(at x: CGFloat) -> CGFloat var order: Int { get } - var coefficients: [Double] { get } + var coefficients: [CGFloat] { get } // var last: CGFloat { get } // var first: CGFloat { get } // func enumerated(block: (Int, CGFloat) -> Void) @@ -129,9 +129,9 @@ public struct BernsteinPolynomial0: BernsteinPolynomial { // func reversed() -> BernsteinPolynomial0 { return self } // func split(to x: CGFloat) -> Self { return self } // func split(from x: CGFloat) -> Self { return self } - public init(b0: Double) { self.b0 = b0 } - public var b0: Double - public var coefficients: [Double] { return [b0] } + public init(b0: CGFloat) { self.b0 = b0 } + public var b0: CGFloat + public var coefficients: [CGFloat] { return [b0] } public func value(at x: CGFloat) -> CGFloat { return b0 } @@ -161,13 +161,13 @@ public struct BernsteinPolynomial1: BernsteinPolynomial { // self.b1 = d.b0 // } // func reversed() -> BernsteinPolynomial1 { BernsteinPolynomial1(b0: b1, b1: b0) } - public init(b0: Double, b1: Double) { + public init(b0: CGFloat, b1: CGFloat) { self.b0 = b0 self.b1 = b1 } public typealias NextLowerOrderPolynomial = BernsteinPolynomial0 - public var b0, b1: Double - public var coefficients: [Double] { return [b0, b1] } + public var b0, b1: CGFloat + public var coefficients: [CGFloat] { return [b0, b1] } public func reduce(a1: CGFloat, a2: CGFloat) -> CGFloat { return a1 * b0 + a2 * b1 } @@ -195,14 +195,14 @@ public struct BernsteinPolynomial2: BernsteinPolynomial { // self.b1 = d.b0 // self.b2 = d.b1 // } - public init(b0: Double, b1: Double, b2: Double) { + public init(b0: CGFloat, b1: CGFloat, b2: CGFloat) { self.b0 = b0 self.b1 = b1 self.b2 = b2 } public typealias NextLowerOrderPolynomial = BernsteinPolynomial1 - public var b0, b1, b2: Double - public var coefficients: [Double] { return [b0, b1, b2] } + public var b0, b1, b2: CGFloat + public var coefficients: [CGFloat] { return [b0, b1, b2] } public func difference(a1: CGFloat, a2: CGFloat) -> BernsteinPolynomial1 { return BernsteinPolynomial1(b0: a1 * b0 + a2 * b1, b1: a1 * b1 + a2 * b2) @@ -231,15 +231,15 @@ public struct BernsteinPolynomial3: BernsteinPolynomial { // self.b2 = d.b1 // self.b3 = d.b2 // } - public init(b0: Double, b1: Double, b2: Double, b3: Double) { + public init(b0: CGFloat, b1: CGFloat, b2: CGFloat, b3: CGFloat) { self.b0 = b0 self.b1 = b1 self.b2 = b2 self.b3 = b3 } public typealias NextLowerOrderPolynomial = BernsteinPolynomial2 - public var b0, b1, b2, b3: Double - public var coefficients: [Double] { return [b0, b1, b2, b3] } + public var b0, b1, b2, b3: CGFloat + public var coefficients: [CGFloat] { return [b0, b1, b2, b3] } public func difference(a1: CGFloat, a2: CGFloat) -> BernsteinPolynomial2 { return BernsteinPolynomial2(b0: a1 * b0 + a2 * b1, b1: a1 * b1 + a2 * b2, @@ -272,7 +272,7 @@ public struct BernsteinPolynomial4: BernsteinPolynomial { // self.b3 = d.b2 // self.b4 = d.b3 // } - public init(b0: Double, b1: Double, b2: Double, b3: Double, b4: Double) { + public init(b0: CGFloat, b1: CGFloat, b2: CGFloat, b3: CGFloat, b4: CGFloat) { self.b0 = b0 self.b1 = b1 self.b2 = b2 @@ -280,8 +280,8 @@ public struct BernsteinPolynomial4: BernsteinPolynomial { self.b4 = b4 } public typealias NextLowerOrderPolynomial = BernsteinPolynomial3 - public var b0, b1, b2, b3, b4: Double - public var coefficients: [Double] { return [b0, b1, b2, b3, b4] } + public var b0, b1, b2, b3, b4: CGFloat + public var coefficients: [CGFloat] { return [b0, b1, b2, b3, b4] } public func difference(a1: CGFloat, a2: CGFloat) -> BernsteinPolynomial3 { return BernsteinPolynomial3(b0: a1 * b0 + a2 * b1, b1: a1 * b1 + a2 * b2, @@ -318,7 +318,7 @@ public struct BernsteinPolynomial5: BernsteinPolynomial { // self.b4 = d.b3 // self.b5 = d.b4 // } - public init(b0: Double, b1: Double, b2: Double, b3: Double, b4: Double, b5: Double) { + public init(b0: CGFloat, b1: CGFloat, b2: CGFloat, b3: CGFloat, b4: CGFloat, b5: CGFloat) { self.b0 = b0 self.b1 = b1 self.b2 = b2 @@ -327,8 +327,8 @@ public struct BernsteinPolynomial5: BernsteinPolynomial { self.b5 = b5 } public typealias NextLowerOrderPolynomial = BernsteinPolynomial4 - public var b0, b1, b2, b3, b4, b5: Double - public var coefficients: [Double] { return [b0, b1, b2, b3, b4, b5] } + public var b0, b1, b2, b3, b4, b5: CGFloat + public var coefficients: [CGFloat] { return [b0, b1, b2, b3, b4, b5] } public func difference(a1: CGFloat, a2: CGFloat) -> BernsteinPolynomial4 { return BernsteinPolynomial4(b0: a1 * b0 + a2 * b1, b1: a1 * b1 + a2 * b2, diff --git a/BezierKit/Library/QuadraticCurve.swift b/BezierKit/Library/QuadraticCurve.swift index dccdbf5c..5d0c2647 100644 --- a/BezierKit/Library/QuadraticCurve.swift +++ b/BezierKit/Library/QuadraticCurve.swift @@ -108,7 +108,7 @@ public struct QuadraticCurve: NonlinearBezierCurve, Equatable { return a*p0 + b*p1 } - public func split(from t1: Double, to t2: Double) -> QuadraticCurve { + public func split(from t1: CGFloat, to t2: CGFloat) -> QuadraticCurve { guard t1 != 0.0 || t2 != 1.0 else { return self } let k = (t2 - t1) / 2 let p0 = self.point(at: t1) @@ -132,7 +132,7 @@ public struct QuadraticCurve: NonlinearBezierCurve, Equatable { return (left: leftCurve, right: rightCurve) } - public func project(_ point: CGPoint) -> (point: CGPoint, t: Double) { + public func project(_ point: CGPoint) -> (point: CGPoint, t: CGFloat) { func multiplyCoordinates(_ a: CGPoint, _ b: CGPoint) -> CGPoint { return CGPoint(x: a.x * b.x, y: a.y * b.y) } @@ -195,7 +195,7 @@ public struct QuadraticCurve: NonlinearBezierCurve, Equatable { return BoundingBox(min: mmin, max: mmax) } - public func point(at t: Double) -> CGPoint { + public func point(at t: CGFloat) -> CGPoint { if t == 0 { return self.p0 } else if t == 1 { diff --git a/BezierKit/Library/Utils.swift b/BezierKit/Library/Utils.swift index e76baa61..093c412a 100644 --- a/BezierKit/Library/Utils.swift +++ b/BezierKit/Library/Utils.swift @@ -141,7 +141,7 @@ internal class Utils { return top/bottom } - static func map(_ v: CGFloat, _ ds: CGFloat, _ de: CGFloat, _ ts: Double, _ te: Double) -> Double { + static func map(_ v: CGFloat, _ ds: CGFloat, _ de: CGFloat, _ ts: CGFloat, _ te: CGFloat) -> CGFloat { let t = (v - ds) / (de - ds) return t * te + (1 - t) * ts } @@ -168,7 +168,7 @@ internal class Utils { return (v < 0) ? -pow(-v, 1.0/3.0) : pow(v, 1.0/3.0) } - static func clamp(_ x: Double, _ a: Double, _ b: Double) -> Double { + static func clamp(_ x: CGFloat, _ a: CGFloat, _ b: CGFloat) -> CGFloat { precondition(b >= a) if x < a { return a @@ -179,7 +179,7 @@ internal class Utils { } } - static func droots(_ p0: Double, _ p1: Double, _ p2: Double, _ p3: Double, callback: (CGFloat) -> Void) { + static func droots(_ p0: CGFloat, _ p1: CGFloat, _ p2: CGFloat, _ p3: CGFloat, callback: (CGFloat) -> Void) { // convert the points p0, p1, p2, p3 to a cubic polynomial at^3 + bt^2 + ct + 1 and solve // see http://www.trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm let p0 = Double(p0) @@ -278,7 +278,7 @@ internal class Utils { callback(p0 / (p0 - p1)) } - static func linearInterpolate(_ v1: CGPoint, _ v2: CGPoint, _ t: Double) -> CGPoint { + static func linearInterpolate(_ v1: CGPoint, _ v2: CGPoint, _ t: CGFloat) -> CGPoint { return v1 + t * (v2 - v1) } From 6a5339b376edc6d17314dbb28574fe9d905f5b48 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Tue, 28 Jun 2022 12:27:59 -0700 Subject: [PATCH 59/63] rid XCode of warning about build settings. --- BezierKit/BezierKit.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/BezierKitMacDemos.xcscheme | 2 +- .../xcshareddata/xcschemes/BezierKit_Mac.xcscheme | 2 +- .../xcshareddata/xcschemes/BezierKit_MacTests.xcscheme | 2 +- .../xcshareddata/xcschemes/BezierKit_iOS.xcscheme | 2 +- .../xcshareddata/xcschemes/BezierKit_iOSTests.xcscheme | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/BezierKit/BezierKit.xcodeproj/project.pbxproj b/BezierKit/BezierKit.xcodeproj/project.pbxproj index 5436f873..4078d959 100644 --- a/BezierKit/BezierKit.xcodeproj/project.pbxproj +++ b/BezierKit/BezierKit.xcodeproj/project.pbxproj @@ -540,7 +540,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0830; - LastUpgradeCheck = 1320; + LastUpgradeCheck = 1340; ORGANIZATIONNAME = "Holmes Futrell"; TargetAttributes = { FD0F54F41DC43FFB0084CDCD = { diff --git a/BezierKit/BezierKit.xcodeproj/xcshareddata/xcschemes/BezierKitMacDemos.xcscheme b/BezierKit/BezierKit.xcodeproj/xcshareddata/xcschemes/BezierKitMacDemos.xcscheme index 04bc0b6a..b88fcc75 100644 --- a/BezierKit/BezierKit.xcodeproj/xcshareddata/xcschemes/BezierKitMacDemos.xcscheme +++ b/BezierKit/BezierKit.xcodeproj/xcshareddata/xcschemes/BezierKitMacDemos.xcscheme @@ -1,6 +1,6 @@ Date: Tue, 28 Jun 2022 12:34:38 -0700 Subject: [PATCH 60/63] re-enable test on 32-bit --- BezierKit/BezierKitTests/CubicCurveTests.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/BezierKit/BezierKitTests/CubicCurveTests.swift b/BezierKit/BezierKitTests/CubicCurveTests.swift index 65a59181..d7bd2e08 100644 --- a/BezierKit/BezierKitTests/CubicCurveTests.swift +++ b/BezierKit/BezierKitTests/CubicCurveTests.swift @@ -505,8 +505,6 @@ class CubicCurveTests: XCTestCase { XCTAssertEqual(c1.intersections(with: c2, accuracy: 1.0e-8), expectedIntersections) } - // Skip on platforms where CGFloat is 32bit - #if !(arch(i386) || arch(arm) || arch(wasm32)) func testRealWorldNearlyCoincidentCurvesIntersection() { // these curves are nearly coincident over from c1's t = 0.278 to 1.0 // staying roughly 0.0002 distance of eachother @@ -526,7 +524,6 @@ class CubicCurveTests: XCTestCase { XCTAssertEqual(intersections[1].t1, 1) XCTAssertEqual(intersections[1].t2, 0) } - #endif func testIntersectionsCubicButActuallyLinear() { // this test presents a challenge for an implicitization based approach From cb464f72c942036725cf35bacc1a06fa17beabfb Mon Sep 17 00:00:00 2001 From: hfutrell Date: Tue, 28 Jun 2022 12:41:48 -0700 Subject: [PATCH 61/63] re-enable cubic curve tests (they fail here, but will be fixed by "recursion-reduction" merge) --- BezierKit/BezierKitTests/CubicCurveTests.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/BezierKit/BezierKitTests/CubicCurveTests.swift b/BezierKit/BezierKitTests/CubicCurveTests.swift index d7bd2e08..c2b72b53 100644 --- a/BezierKit/BezierKitTests/CubicCurveTests.swift +++ b/BezierKit/BezierKitTests/CubicCurveTests.swift @@ -8,7 +8,7 @@ import XCTest @testable import BezierKit -#if !os(WASI) + class CubicCurveTests: XCTestCase { override func setUp() { @@ -655,4 +655,3 @@ class CubicCurveTests: XCTestCase { XCTAssertNotEqual(c1, c5) } } -#endif From 37572e941931640cc6c77a38e2000d962d6f50e5 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Tue, 28 Jun 2022 12:43:13 -0700 Subject: [PATCH 62/63] Revert "re-enable test on 32-bit" This reverts commit c0f07d731db8af8671ebb4ac34d815167233de36. --- BezierKit/BezierKitTests/CubicCurveTests.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/BezierKit/BezierKitTests/CubicCurveTests.swift b/BezierKit/BezierKitTests/CubicCurveTests.swift index c2b72b53..3fb8b311 100644 --- a/BezierKit/BezierKitTests/CubicCurveTests.swift +++ b/BezierKit/BezierKitTests/CubicCurveTests.swift @@ -505,6 +505,8 @@ class CubicCurveTests: XCTestCase { XCTAssertEqual(c1.intersections(with: c2, accuracy: 1.0e-8), expectedIntersections) } + // Skip on platforms where CGFloat is 32bit + #if !(arch(i386) || arch(arm) || arch(wasm32)) func testRealWorldNearlyCoincidentCurvesIntersection() { // these curves are nearly coincident over from c1's t = 0.278 to 1.0 // staying roughly 0.0002 distance of eachother @@ -524,6 +526,7 @@ class CubicCurveTests: XCTestCase { XCTAssertEqual(intersections[1].t1, 1) XCTAssertEqual(intersections[1].t2, 0) } + #endif func testIntersectionsCubicButActuallyLinear() { // this test presents a challenge for an implicitization based approach From 1f4805beb430a15a9b740643e945fa07a367e491 Mon Sep 17 00:00:00 2001 From: hfutrell Date: Tue, 28 Jun 2022 12:49:38 -0700 Subject: [PATCH 63/63] increment version # --- BezierKit.podspec | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BezierKit.podspec b/BezierKit.podspec index 77d7011b..6b4c0c83 100644 --- a/BezierKit.podspec +++ b/BezierKit.podspec @@ -5,7 +5,7 @@ Pod::Spec.new do |s| s.name = "BezierKit" - s.version = "0.14.0" + s.version = "0.15.0" s.summary = "comprehensive Bezier Path library written in Swift" s.homepage = "https://github.com/hfutrell/BezierKit" s.license = "MIT" diff --git a/README.md b/README.md index 1aaa0184..698401d4 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ To integrate BezierKit into your Xcode project using CocoaPods, add it to your t ```ruby target '' do - pod 'BezierKit', '>= 0.14.0' + pod 'BezierKit', '>= 0.15.0' end ``` @@ -66,7 +66,7 @@ import PackageDescription let package = Package( name: "", dependencies: [ - .package(url: "https://github.com/hfutrell/BezierKit.git", from: "0.14.0"), + .package(url: "https://github.com/hfutrell/BezierKit.git", from: "0.15.0"), ] ) ```