Skip to content

Commit 262c433

Browse files
authored
Add Silica's CGPath implementation (#222)
1 parent 04f816c commit 262c433

File tree

1 file changed

+230
-0
lines changed

1 file changed

+230
-0
lines changed
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
//
2+
// CGPath.swift
3+
// CoreGraphicsShims
4+
//
5+
// License: MIT
6+
// Modified from https://github.com/PureSwift/Silica/blob/22c72ff508c40ae5e673c16ad39f39235f6ddd01/Sources/Silica/CGPath.swift
7+
8+
#if !canImport(CoreGraphics)
9+
10+
public import Foundation
11+
12+
/// A graphics path is a mathematical description of a series of shapes or lines.
13+
public struct CGPath {
14+
15+
public typealias Element = PathElement
16+
17+
public var elements: [Element]
18+
19+
public init(elements: [Element] = []) {
20+
21+
self.elements = elements
22+
}
23+
}
24+
25+
// MARK: - Supporting Types
26+
27+
/// A path element.
28+
public enum PathElement {
29+
30+
/// The path element that starts a new subpath. The element holds a single point for the destination.
31+
case moveToPoint(CGPoint)
32+
33+
/// The path element that adds a line from the current point to a new point.
34+
/// The element holds a single point for the destination.
35+
case addLineToPoint(CGPoint)
36+
37+
/// The path element that adds a quadratic curve from the current point to the specified point.
38+
/// The element holds a control point and a destination point.
39+
case addQuadCurveToPoint(CGPoint, CGPoint)
40+
41+
/// The path element that adds a cubic curve from the current point to the specified point.
42+
/// The element holds two control points and a destination point.
43+
case addCurveToPoint(CGPoint, CGPoint, CGPoint)
44+
45+
/// The path element that closes and completes a subpath. The element does not contain any points.
46+
case closeSubpath
47+
}
48+
49+
// MARK: - Constructing a Path
50+
51+
public extension CGPath {
52+
53+
mutating func addRect(_ rect: CGRect) {
54+
55+
let newElements: [Element] = [.moveToPoint(CGPoint(x: rect.minX, y: rect.minY)),
56+
.addLineToPoint(CGPoint(x: rect.maxX, y: rect.minY)),
57+
.addLineToPoint(CGPoint(x: rect.maxX, y: rect.maxY)),
58+
.addLineToPoint(CGPoint(x: rect.minX, y: rect.maxY)),
59+
.closeSubpath]
60+
61+
elements.append(contentsOf: newElements)
62+
}
63+
64+
mutating func addEllipse(in rect: CGRect) {
65+
66+
var p = CGPoint()
67+
var p1 = CGPoint()
68+
var p2 = CGPoint()
69+
70+
let hdiff = rect.width / 2 * KAPPA
71+
let vdiff = rect.height / 2 * KAPPA
72+
73+
p = CGPoint(x: rect.origin.x + rect.width / 2, y: rect.origin.y + rect.height)
74+
elements.append(.moveToPoint(p))
75+
76+
p = CGPoint(x: rect.origin.x, y: rect.origin.y + rect.height / 2)
77+
p1 = CGPoint(x: rect.origin.x + rect.width / 2 - hdiff, y: rect.origin.y + rect.height)
78+
p2 = CGPoint(x: rect.origin.x, y: rect.origin.y + rect.height / 2 + vdiff)
79+
elements.append(.addCurveToPoint(p1, p2, p))
80+
81+
p = CGPoint(x: rect.origin.x + rect.size.width / 2, y: rect.origin.y)
82+
p1 = CGPoint(x: rect.origin.x, y: rect.origin.y + rect.size.height / 2 - vdiff)
83+
p2 = CGPoint(x: rect.origin.x + rect.size.width / 2 - hdiff, y: rect.origin.y)
84+
elements.append(.addCurveToPoint(p1, p2, p))
85+
86+
p = CGPoint(x: rect.origin.x + rect.size.width, y: rect.origin.y + rect.size.height / 2)
87+
p1 = CGPoint(x: rect.origin.x + rect.size.width / 2 + hdiff, y: rect.origin.y)
88+
p2 = CGPoint(x: rect.origin.x + rect.size.width, y: rect.origin.y + rect.size.height / 2 - vdiff)
89+
elements.append(.addCurveToPoint(p1, p2, p))
90+
91+
p = CGPoint(x: rect.origin.x + rect.size.width / 2, y: rect.origin.y + rect.size.height)
92+
p1 = CGPoint(x: rect.origin.x + rect.size.width, y: rect.origin.y + rect.size.height / 2 + vdiff)
93+
p2 = CGPoint(x: rect.origin.x + rect.size.width / 2 + hdiff, y: rect.origin.y + rect.size.height)
94+
elements.append(.addCurveToPoint(p1, p2, p))
95+
}
96+
97+
mutating func move(to point: CGPoint) {
98+
99+
elements.append(.moveToPoint(point))
100+
}
101+
102+
mutating func addLine(to point: CGPoint) {
103+
104+
elements.append(.addLineToPoint(point))
105+
}
106+
107+
mutating func addCurve(to endPoint: CGPoint, control1: CGPoint, control2: CGPoint) {
108+
109+
elements.append(.addCurveToPoint(control1, control2, endPoint))
110+
}
111+
112+
mutating func addQuadCurve(to endPoint: CGPoint, control: CGPoint) {
113+
114+
elements.append(.addQuadCurveToPoint(control, endPoint))
115+
}
116+
117+
mutating func closeSubpath() {
118+
119+
elements.append(.closeSubpath)
120+
}
121+
}
122+
123+
// This magic number is 4 *(sqrt(2) -1)/3
124+
private let KAPPA: CGFloat = 0.5522847498
125+
126+
// MARK: - CoreGraphics API
127+
128+
public struct CGPathElement {
129+
130+
public var type: CGPathElementType
131+
132+
public var points: (CGPoint, CGPoint, CGPoint)
133+
134+
public init(type: CGPathElementType, points: (CGPoint, CGPoint, CGPoint)) {
135+
136+
self.type = type
137+
self.points = points
138+
}
139+
}
140+
141+
/// Rules for determining which regions are interior to a path.
142+
///
143+
/// When filling a path, regions that a fill rule defines as interior to the path are painted.
144+
/// When clipping with a path, regions interior to the path remain visible after clipping.
145+
public enum CGPathFillRule: Int {
146+
147+
/// A rule that considers a region to be interior to a path based on the number of times it is enclosed by path elements.
148+
case evenOdd
149+
150+
/// A rule that considers a region to be interior to a path if the winding number for that region is nonzero.
151+
case winding
152+
}
153+
154+
/// The type of element found in a path.
155+
public enum CGPathElementType {
156+
157+
/// The path element that starts a new subpath. The element holds a single point for the destination.
158+
case moveToPoint
159+
160+
/// The path element that adds a line from the current point to a new point.
161+
/// The element holds a single point for the destination.
162+
case addLineToPoint
163+
164+
/// The path element that adds a quadratic curve from the current point to the specified point.
165+
/// The element holds a control point and a destination point.
166+
case addQuadCurveToPoint
167+
168+
/// The path element that adds a cubic curve from the current point to the specified point.
169+
/// The element holds two control points and a destination point.
170+
case addCurveToPoint
171+
172+
/// The path element that closes and completes a subpath. The element does not contain any points.
173+
case closeSubpath
174+
}
175+
176+
// MARK: - Silica Conversion
177+
178+
public extension CGPathElement {
179+
180+
init(_ element: PathElement) {
181+
182+
switch element {
183+
184+
case let .moveToPoint(point):
185+
186+
self.type = .moveToPoint
187+
self.points = (point, CGPoint(), CGPoint())
188+
189+
case let .addLineToPoint(point):
190+
191+
self.type = .addLineToPoint
192+
self.points = (point, CGPoint(), CGPoint())
193+
194+
case let .addQuadCurveToPoint(control, destination):
195+
196+
self.type = .addQuadCurveToPoint
197+
self.points = (control, destination, CGPoint())
198+
199+
case let .addCurveToPoint(control1, control2, destination):
200+
201+
self.type = .addCurveToPoint
202+
self.points = (control1, control2, destination)
203+
204+
case .closeSubpath:
205+
206+
self.type = .closeSubpath
207+
self.points = (CGPoint(), CGPoint(), CGPoint())
208+
}
209+
}
210+
}
211+
212+
public extension PathElement {
213+
214+
init(_ element: CGPathElement) {
215+
216+
switch element.type {
217+
218+
case .moveToPoint: self = .moveToPoint(element.points.0)
219+
220+
case .addLineToPoint: self = .addLineToPoint(element.points.0)
221+
222+
case .addQuadCurveToPoint: self = .addQuadCurveToPoint(element.points.0, element.points.1)
223+
224+
case .addCurveToPoint: self = .addCurveToPoint(element.points.0, element.points.1, element.points.2)
225+
226+
case .closeSubpath: self = .closeSubpath
227+
}
228+
}
229+
}
230+
#endif

0 commit comments

Comments
 (0)