Skip to content

Commit

Permalink
instead of throwing, just return nil since the error was not useful a…
Browse files Browse the repository at this point in the history
…nyway.
  • Loading branch information
hfutrell committed Dec 8, 2018
1 parent ee9e89a commit c05402a
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 32 deletions.
24 changes: 12 additions & 12 deletions BezierKit/BezierKitTests/PathTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ class PathTests: XCTestCase {
)])
let square1 = createSquare1()
let square2 = createSquare2()
let subtracted = square1.subtracting(square2)
let subtracted = square1.subtracting(square2)!
XCTAssertEqual(subtracted.subpaths.count, 1)
XCTAssert(
componentsEqualAsideFromElementOrdering(subtracted.subpaths[0], expectedResult.subpaths[0])
Expand All @@ -364,7 +364,7 @@ class PathTests: XCTestCase {
)])
let square1 = createSquare1()
let square2 = createSquare2()
let unioned = square1.union(square2)
let unioned = square1.union(square2)!
XCTAssertEqual(unioned.subpaths.count, 1)
XCTAssert(
componentsEqualAsideFromElementOrdering(unioned.subpaths[0], expectedResult.subpaths[0])
Expand All @@ -382,7 +382,7 @@ class PathTests: XCTestCase {
)])
let square1 = createSquare1()
let square2 = createSquare2()
let intersected = square1.intersecting(square2)
let intersected = square1.intersecting(square2)!
XCTAssertEqual(intersected.subpaths.count, 1)
XCTAssert(
componentsEqualAsideFromElementOrdering(intersected.subpaths[0], expectedResult.subpaths[0])
Expand All @@ -394,7 +394,7 @@ class PathTests: XCTestCase {
// the order of the hole is reversed so that it is not contained in the shape when using .winding fill rule
let circle = Path(cgPath: CGPath(ellipseIn: CGRect(x: 0, y: 0, width: 3, height: 3), transform: nil))
let hole = Path(cgPath: CGPath(ellipseIn: CGRect(x: 1, y: 1, width: 1, height: 1), transform: nil))
let donut = circle.subtracting(hole)
let donut = circle.subtracting(hole)!
XCTAssertTrue(donut.contains(CGPoint(x: 0.5, y: 0.5), using: .winding)) // inside the donut (but not the hole)
XCTAssertFalse(donut.contains(CGPoint(x: 1.5, y: 1.5), using: .winding)) // center of donut hole
}
Expand All @@ -403,7 +403,7 @@ class PathTests: XCTestCase {
// this is a specific test of `subtracting` to ensure that if a path component is entirely contained in the subtracting path that it gets removed
let circle = Path(cgPath: CGPath(ellipseIn: CGRect(x: -1, y: -1, width: 2, height: 2), transform: nil))
let biggerCircle = Path(cgPath: CGPath(ellipseIn: CGRect(x: -2, y: -2, width: 4, height: 4), transform: nil))
XCTAssertEqual(circle.subtracting(biggerCircle).subpaths.count, 0)
XCTAssertEqual(circle.subtracting(biggerCircle)!.subpaths.count, 0)
}

func testSubtractingEdgeCase1() {
Expand All @@ -415,7 +415,7 @@ class PathTests: XCTestCase {
let circle = Path(cgPath: CGPath(ellipseIn: CGRect(x: 0, y: 0, width: 4, height: 4), transform: nil))

// the circle intersects the rect at (0,2) and (3, 0.26792) ... the last number being exactly 2 - sqrt(3)
let difference = rectangle.subtracting(circle)
let difference = rectangle.subtracting(circle)!
XCTAssertEqual(difference.subpaths.count, 1)
XCTAssertFalse(difference.contains(CGPoint(x: 2.0, y: 2.0)))
}
Expand All @@ -434,7 +434,7 @@ class PathTests: XCTestCase {
square2CGPath.closeSubpath()

let square2 = Path(cgPath: square2CGPath)
let result = square1.subtracting(square2)
let result = square1.subtracting(square2)!

let expectedResultCGPath = CGMutablePath()
expectedResultCGPath.move(to: CGPoint.zero)
Expand Down Expand Up @@ -474,7 +474,7 @@ class PathTests: XCTestCase {
XCTAssertTrue(path.contains(CGPoint(x: 1.5, y: 1.25), using: .winding))
XCTAssertFalse(path.contains(CGPoint(x: 1.5, y: 1.25), using: .evenOdd))

let result = path.crossingsRemoved()
let result = path.crossingsRemoved()!
XCTAssertEqual(result.subpaths.count, 1)
XCTAssertTrue(componentsEqualAsideFromElementOrdering(result.subpaths[0], expectedResult.subpaths[0]))

Expand All @@ -483,15 +483,15 @@ class PathTests: XCTestCase {
cgPathAlt.addLines(between: Array(points[3..<points.count]) + Array(points[1...3]))
let pathAlt = Path(cgPath: cgPathAlt)

let resultAlt = pathAlt.crossingsRemoved()
let resultAlt = pathAlt.crossingsRemoved()!
XCTAssertEqual(resultAlt.subpaths.count, 1)
XCTAssertTrue(componentsEqualAsideFromElementOrdering(resultAlt.subpaths[0], expectedResult.subpaths[0]))
}

func testCrossingsRemovedNoCrossings() {
// a test which ensures that if a path has no crossings then crossingsRemoved does not modify it
let square = Path(cgPath: CGPath(ellipseIn: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0), transform: nil))
let result = square.crossingsRemoved()
let result = square.crossingsRemoved()!
XCTAssertEqual(result.subpaths.count, 1)
XCTAssertTrue(componentsEqualAsideFromElementOrdering(result.subpaths[0], square.subpaths[0]))
}
Expand All @@ -518,7 +518,7 @@ class PathTests: XCTestCase {
XCTAssertEqual(contour.windingCount(CGPoint(x: 0.5, y: 0.5)), -1) // winding count at center of one square region
XCTAssertEqual( contour.windingCount(CGPoint(x: 1.5, y: 1.5)), 1) // winding count at center of other square region

let crossingsRemoved = contour.crossingsRemoved()
let crossingsRemoved = contour.crossingsRemoved()!

XCTAssertEqual(crossingsRemoved.subpaths.count, 1)
XCTAssertTrue(componentsEqualAsideFromElementOrdering(crossingsRemoved.subpaths[0], contour.subpaths[0]))
Expand Down Expand Up @@ -559,7 +559,7 @@ class PathTests: XCTestCase {
}.filter { $0.length() > 0.0 }
let cleanPath = Path(subpaths: [PathComponent(curves: curves2)])

let result = cleanPath.crossingsRemoved(threshold: 1.0e-4)
let result = cleanPath.crossingsRemoved(threshold: 1.0e-4)!

// check that the inner loop was eliminated by checking the winding count in the middle
XCTAssertEqual(result.windingCount(CGPoint(x: 0.5, y: 1)), 1)
Expand Down
6 changes: 2 additions & 4 deletions BezierKit/Library/AugmentedGraph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
//

import CoreGraphics
import Foundation

public func signedAngle(_ a: CGPoint, _ b: CGPoint) -> CGFloat {
return atan2(CGPoint.cross(a, b), a.dot(b))
Expand Down Expand Up @@ -340,7 +339,7 @@ internal class AugmentedGraph {
}
}

internal func booleanOperation(_ operation: BooleanPathOperation) throws -> Path {
internal func booleanOperation(_ operation: BooleanPathOperation) -> Path? {

// special cases for components which do not cross
let nonCrossingComponents1: [PathComponent] = self.list1.nonCrossingComponents()
Expand Down Expand Up @@ -393,8 +392,7 @@ internal class AugmentedGraph {
isOnFirstCurve = !isOnFirstCurve

if isOnFirstCurve && unvisitedCrossings.contains(v) == false && v !== start {
let userInfo = [NSLocalizedDescriptionKey: "Boolean operation failed, try more accurate threshold?"]
throw NSError(domain: BezierKit.errorDomain, code: -1, userInfo: userInfo)
return nil
}
} while v !== start
pathComponents.append(PathComponent(curves: curves))
Expand Down
1 change: 0 additions & 1 deletion BezierKit/Library/BezierCurve.swift
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,6 @@ extension BezierCurve {
}

public let defaultIntersectionThreshold = CGFloat(0.5)
public let errorDomain = "BezierKit"

// MARK: factory

Expand Down
30 changes: 16 additions & 14 deletions BezierKit/Library/Path.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR
}
}

@objc(BezierKitPathError) public class PathError: NSError {
}

@objc(BezierKitPath) public class Path: NSObject, NSCoding {

private class PathApplierFunctionContext {
Expand Down Expand Up @@ -204,34 +201,37 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR
return windingCountImpliesContainment(count, using: rule)
}

private func performBooleanOperation(_ operation: BooleanPathOperation, withPath other: Path, threshold: CGFloat) throws -> Path {
private func performBooleanOperation(_ operation: BooleanPathOperation, withPath other: Path, threshold: CGFloat) -> Path? {
let intersections = self.intersects(path: other, threshold: threshold)
let augmentedGraph = AugmentedGraph(path1: self, path2: other, intersections: intersections)
return try augmentedGraph.booleanOperation(operation)
return augmentedGraph.booleanOperation(operation)
}

@objc(subtractingPath:threshold:error:) public func subtracting(_ other: Path, threshold: CGFloat=BezierKit.defaultIntersectionThreshold) throws -> Path {
return try self.performBooleanOperation(.difference, withPath: other.reversed(), threshold: threshold)
@objc(subtractingPath:threshold:) public func subtracting(_ other: Path, threshold: CGFloat=BezierKit.defaultIntersectionThreshold) -> Path? {
return self.performBooleanOperation(.difference, withPath: other.reversed(), threshold: threshold)
}

@objc(unionedWithPath:threshold:error:) public func `union`(_ other: Path, threshold: CGFloat=BezierKit.defaultIntersectionThreshold) throws -> Path {
@objc(unionedWithPath:threshold:) public func `union`(_ other: Path, threshold: CGFloat=BezierKit.defaultIntersectionThreshold) -> Path? {
guard self.isEmpty == false else {
return other
}
guard other.isEmpty == false else {
return self
}
return try self.performBooleanOperation(.union, withPath: other, threshold: threshold)
return self.performBooleanOperation(.union, withPath: other, threshold: threshold)
}

@objc(intersectedWithPath:threshold:error:) public func intersecting(_ other: Path, threshold: CGFloat=BezierKit.defaultIntersectionThreshold) throws -> Path {
return try self.performBooleanOperation(.intersection, withPath: other, threshold: threshold)
@objc(intersectedWithPath:threshold:) public func intersecting(_ other: Path, threshold: CGFloat=BezierKit.defaultIntersectionThreshold) -> Path? {
return self.performBooleanOperation(.intersection, withPath: other, threshold: threshold)
}

@objc(crossingsRemovedWithThreshold:error:) public func crossingsRemoved(threshold: CGFloat=BezierKit.defaultIntersectionThreshold) throws -> Path {
@objc(crossingsRemovedWithThreshold:) public func crossingsRemoved(threshold: CGFloat=BezierKit.defaultIntersectionThreshold) -> Path? {
// assert(self.subpaths.count <= 1, "todo: support multi-component paths")
return try self.subpaths.reduce(Path(), { result, component in
return self.subpaths.reduce(Path(), { (result: Path?, component: PathComponent) -> Path? in
// TODO: this won't work properly if components intersect
guard let result = result else {
return nil
}
let intersections = component.intersects(threshold: threshold).map {(i: PathComponentIntersection) -> PathIntersection in
return PathIntersection(indexedPathLocation1: IndexedPathLocation(componentIndex: 0, elementIndex: i.indexedComponentLocation1.elementIndex, t: i.indexedComponentLocation1.t),
indexedPathLocation2: IndexedPathLocation(componentIndex: 0, elementIndex: i.indexedComponentLocation2.elementIndex, t: i.indexedComponentLocation2.t))
Expand All @@ -241,7 +241,9 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR
}
let singleComponentPath = Path(subpaths: [component])
let augmentedGraph = AugmentedGraph(path1: singleComponentPath, path2: singleComponentPath, intersections: intersections)
let path = try augmentedGraph.booleanOperation(.removeCrossings)
guard let path = augmentedGraph.booleanOperation(.removeCrossings) else {
return nil
}
return Path(subpaths: result.subpaths + path.subpaths)
})
}
Expand Down
2 changes: 1 addition & 1 deletion BezierKit/MacDemos/Demos.swift
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ class Demos {
// v = v.next
// } while v !== first

let subtracted = (try? path1.intersecting(path2)) ?? path1
let subtracted = path1.intersecting(path2) ?? path1
Draw.drawPath(context, subtracted)
}
})
Expand Down

0 comments on commit c05402a

Please sign in to comment.