Skip to content

Commit 931739f

Browse files
committed
Wind EvenOdd to NonZero
1 parent 68d9d50 commit 931739f

File tree

7 files changed

+122
-206
lines changed

7 files changed

+122
-206
lines changed

SwiftDraw/LayerTree.CommandGenerator.swift

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -547,15 +547,6 @@ private func apply(colorConverter: ColorConverter, to stop: LayerTree.Gradient.S
547547
return stop
548548
}
549549

550-
private extension LayerTree.Rect {
551-
var center: LayerTree.Point {
552-
LayerTree.Point(
553-
origin.x + (size.width / 2),
554-
origin.y + (size.height / 2)
555-
)
556-
}
557-
}
558-
559550
private extension LayerTree.Shape {
560551

561552
var endpoints: (start: LayerTree.Point, end: LayerTree.Point)? {

SwiftDraw/LayerTree.Path+Bounds.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,13 @@ extension LayerTree.Rect {
188188

189189
var midX: LayerTree.Float { origin.x + (size.width / 2) }
190190
var midY: LayerTree.Float { origin.y + (size.height / 2) }
191+
192+
var center: LayerTree.Point {
193+
.init(midX, midY)
194+
}
195+
196+
func contains(point: LayerTree.Point) -> Bool {
197+
(minX...maxX).contains(point.x) &&
198+
(minY...maxY).contains(point.y)
199+
}
191200
}

SwiftDraw/LayerTree.Path+Reversed.swift

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131

3232
extension LayerTree.Path {
3333

34+
func makeNonZero() -> LayerTree.Path {
35+
let paths = makeNodes().flatMap { $0.windPaths() }
36+
return LayerTree.Path(paths.flatMap(\.segments))
37+
}
38+
3439
var reversed: LayerTree.Path {
3540
var reversed = segments
3641
.reversed()
@@ -55,6 +60,61 @@ extension LayerTree.Path {
5560
}
5661
}
5762

63+
private extension LayerTree.Path {
64+
65+
func makeNodes() -> [SubPathNode] {
66+
var nodes = [SubPathNode]()
67+
68+
for p in subpaths {
69+
let node = SubPathNode(p)
70+
if let idx = nodes.firstIndex(where: { $0.bounds.contains(point: node.bounds.center) }) {
71+
nodes[idx].append(node)
72+
} else {
73+
nodes.append(node)
74+
}
75+
}
76+
return nodes
77+
}
78+
}
79+
80+
private struct SubPathNode {
81+
let path: LayerTree.Path
82+
let bounds: LayerTree.Rect
83+
let direction: LayerTree.Path.Direction
84+
var children: [SubPathNode] = []
85+
86+
init(_ path: LayerTree.Path) {
87+
self.path = path
88+
self.bounds = path.bounds
89+
self.direction = path.segments.direction
90+
}
91+
92+
mutating func append(_ node: SubPathNode) {
93+
if let idx = children.firstIndex(where: { $0.bounds.contains(point: node.bounds.center) }) {
94+
children[idx].append(node)
95+
} else {
96+
children.append(node)
97+
}
98+
}
99+
100+
func windPaths() -> [LayerTree.Path] {
101+
windPaths(direction)
102+
}
103+
104+
func windPaths(_ direction: LayerTree.Path.Direction) -> [LayerTree.Path] {
105+
var paths = [LayerTree.Path]()
106+
107+
if self.direction == direction {
108+
paths.append(path)
109+
} else {
110+
paths.append(path.reversed)
111+
}
112+
113+
paths += children.flatMap { $0.windPaths(direction.opposite) }
114+
return paths
115+
}
116+
}
117+
58118
private extension Array where Element == LayerTree.Path.Segment {
59119

60120
var lastLocation: LayerTree.Point? {

SwiftDraw/LayerTree.Path.swift

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ extension LayerTree {
4747
enum Direction {
4848
case clockwise
4949
case anticlockwise
50+
51+
var opposite: Self {
52+
switch self {
53+
case .clockwise:
54+
return .anticlockwise
55+
case .anticlockwise:
56+
return .clockwise
57+
}
58+
}
5059
}
5160

5261
func hash(into hasher: inout Hasher) {
@@ -161,10 +170,5 @@ extension Sequence where Element == LayerTree.Path.Segment {
161170
}
162171

163172
prefix func !(direction: LayerTree.Path.Direction) -> LayerTree.Path.Direction {
164-
switch direction {
165-
case .clockwise:
166-
return .anticlockwise
167-
case .anticlockwise:
168-
return .clockwise
169-
}
173+
direction.opposite
170174
}

SwiftDraw/Renderer.SFSymbol.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,11 @@ extension SFSymbolRenderer {
5757
switch c {
5858
case let .shape(shape, stroke, fill):
5959
if let path = makePath(for: shape, stoke: stroke, fill: fill) {
60-
paths.append(path)
60+
if fill.rule == .evenodd {
61+
paths.append(path.makeNonZero())
62+
} else {
63+
paths.append(path)
64+
}
6165
}
6266
case .layer(let l):
6367
paths.append(contentsOf: getPaths(for: l))

SwiftDrawTests/LayerTree.Path+ReversedTests.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,42 @@ final class LayerTreePathReversedTests: XCTestCase {
7070
.close]
7171
)
7272
}
73+
74+
func testEvenOddPathDirection() throws {
75+
let pathData = """
76+
M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22Z
77+
M12 13C11.4477 13 11 12.5523 11 12V8C11 7.44772 11.4477 7 12 7C12.5523 7 13 7.44772 13 8V12C13 12.5523 12.5523 13 12 13
78+
ZM13 17H11V15H13V17Z
79+
"""
80+
let domPath = try XMLParser().parsePath(from: pathData)
81+
let layerPath = try LayerTree.Builder.createPath(from: domPath)
82+
83+
XCTAssertEqual(
84+
layerPath.subpaths.map(\.segments.direction),
85+
[.clockwise, .clockwise, .clockwise]
86+
)
87+
88+
XCTAssertEqual(
89+
layerPath.makeNonZero().subpaths.map(\.segments.direction),
90+
[.clockwise, .anticlockwise, .anticlockwise]
91+
)
92+
}
93+
94+
func testNonZeroDirection() throws {
95+
let pathData = """
96+
M12,22C17.523,22 22,17.523 22,12C22,6.477 17.523,2 12,2C6.477,2 2,6.477 2,12C2,17.523 6.477,22 12,22ZM13,17L11,17L11,15L13,15L13,17ZM12,13C11.448,13 11,12.552 11,12L11,8C11,7.448 11.448,7 12,7C12.552,7 13,7.448 13,8L13,12C13,12.552 12.552,13 12,13Z
97+
"""
98+
let domPath = try XMLParser().parsePath(from: pathData)
99+
let layerPath = try LayerTree.Builder.createPath(from: domPath)
100+
101+
XCTAssertEqual(
102+
layerPath.subpaths.map(\.segments.direction),
103+
[.anticlockwise, .clockwise, .clockwise]
104+
)
105+
106+
XCTAssertEqual(
107+
layerPath.makeNonZero().subpaths.map(\.segments.direction),
108+
[.anticlockwise, .clockwise, .clockwise]
109+
)
110+
}
73111
}

SwiftDrawTests/XML.FormatterTests.swift

Lines changed: 0 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -120,93 +120,6 @@ final class XMLFormatterTests: XCTestCase {
120120
"""
121121
)
122122
}
123-
124-
func testEvenOddPathDirection() throws {
125-
let pathData = """
126-
M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22Z
127-
M12 13C11.4477 13 11 12.5523 11 12V8C11 7.44772 11.4477 7 12 7C12.5523 7 13 7.44772 13 8V12C13 12.5523 12.5523 13 12 13
128-
ZM13 17H11V15H13V17Z
129-
"""
130-
let domPath = try XMLParser().parsePath(from: pathData)
131-
let layerPath = try LayerTree.Builder.createPath(from: domPath)
132-
133-
XCTAssertEqual(
134-
layerPath.subPaths().map(\.direction),
135-
[.clockwise, .clockwise, .clockwise]
136-
)
137-
}
138-
139-
func testNonZeroDirection() throws {
140-
let pathData = """
141-
M12,22C17.523,22 22,17.523 22,12C22,6.477 17.523,2 12,2C6.477,2 2,6.477 2,12C2,17.523 6.477,22 12,22ZM13,17L11,17L11,15L13,15L13,17ZM12,13C11.448,13 11,12.552 11,12L11,8C11,7.448 11.448,7 12,7C12.552,7 13,7.448 13,8L13,12C13,12.552 12.552,13 12,13Z
142-
"""
143-
let domPath = try XMLParser().parsePath(from: pathData)
144-
let layerPath = try LayerTree.Builder.createPath(from: domPath)
145-
146-
XCTAssertEqual(
147-
layerPath.subPaths().map(\.direction),
148-
[.anticlockwise, .clockwise, .clockwise]
149-
)
150-
}
151-
152-
func testEvenOdd() throws {
153-
let pathData = """
154-
M 75 100
155-
l 50 -50 l 50 50 l -50 50 Z
156-
m 25 0
157-
l 25 -25 l 25 25 l -25 25 Z
158-
m 10 0
159-
l 15 -15 l 15 15 l -15 15 Z
160-
m 10 0
161-
l 5 -5 l 5 5 l -5 5 Z
162-
M 225 100
163-
l 50 -50 l 50 50 l -50 50 Z
164-
m 25 0
165-
l 25 -25 l 25 25 l -25 25 Z
166-
m 10 0
167-
l 15 -15 l 15 15 l -15 15 Z
168-
m 10 0
169-
l 5 -5 l 5 5 l -5 5 Z
170-
"""
171-
let domPath = try XMLParser().parsePath(from: pathData)
172-
let layerPath = try LayerTree.Builder.createPath(from: domPath)
173-
174-
let paths = MyPath.make(from: layerPath)
175-
print(paths.count)
176-
print(paths[0].inside.count)
177-
print(paths[1].inside.count)
178-
}
179-
180-
func testNonZero() throws {
181-
let pathData = """
182-
M 75 100
183-
l 50 -50 l 50 50 l -50 50 Z
184-
m 25 0
185-
l 25 25 l 25 -25 l -25 -25 Z
186-
m 10 0
187-
l 15 15 l 15 -15 l -15 -15 Z
188-
m 10 0
189-
l 5 -5 l 5 5 l -5 5 Z
190-
M 225 100
191-
l 50 -50 l 50 50 l -50 50 Z
192-
m 25 0
193-
l 25 25 l 25 -25 l -25 -25 Z
194-
m 10 0
195-
l 15 15 l 15 -15 l -15 -15 Z
196-
m 10 0
197-
l 5 -5 l 5 5 l -5 5 Z
198-
"""
199-
let domPath = try XMLParser().parsePath(from: pathData)
200-
let layerPath = try LayerTree.Builder.createPath(from: domPath)
201-
202-
let paths = MyPath.make(from: layerPath)
203-
print(paths.count)
204-
print(paths[0].inside.count)
205-
print(paths[1].inside.count)
206-
207-
print(paths[0].inside[0].inside.count)
208-
print(paths[0].inside[0].inside[0].inside.count)
209-
}
210123
}
211124

212125
private extension XML.Formatter.SVG {
@@ -216,109 +129,6 @@ private extension XML.Formatter.SVG {
216129
}
217130
}
218131

219-
extension LayerTree.Path {
220-
221-
func subPaths() -> [ArraySlice<Segment>] {
222-
segments.split(separator: .close)
223-
}
224-
}
225-
226-
extension Array where Element == LayerTree.Point {
227-
func isPointInside(_ test: LayerTree.Point) -> Bool {
228-
var j: Int = count - 1
229-
var contains = false
230-
231-
for i in indices {
232-
if (((self[i].y < test.y && self[j].y >= test.y) || (self[j].y < test.y && self[i].y >= test.y))
233-
&& (self[i].x <= test.x || self[j].x <= test.x)) {
234-
contains = contains != (self[i].x + (test.y - self[i].y) / (self[j].y - self[i].y) * (self[j].x - self[i].x) < test.x)
235-
}
236-
237-
j = i
238-
}
239-
240-
if !contains {
241-
print("****")
242-
print("polygon", self.map { $0.stringValue })
243-
print("point", test.stringValue, contains)
244-
print("****")
245-
}
246-
247-
return contains
248-
}
249-
}
250-
251-
struct MyPath {
252-
var segments: ArraySlice<LayerTree.Path.Segment>
253-
var inside: [MyPath] = []
254-
255-
func bounds(segments: ArraySlice<LayerTree.Path.Segment>) -> Bool {
256-
let outer = self.segments.compactMap(\.location)
257-
let inner = segments.compactMap(\.location)
258-
guard !inner.isEmpty else { return false }
259-
260-
for p in inner {
261-
if !outer.isPointInside(p) {
262-
return false
263-
}
264-
}
265-
266-
return true
267-
}
268-
269-
mutating func appendPath(_ p: MyPath) {
270-
for (idx, path) in inside.enumerated() {
271-
if path.bounds(segments: p.segments) {
272-
inside[idx].appendPath(p)
273-
return
274-
}
275-
}
276-
inside.append(p)
277-
}
278-
279-
static func make(from path: LayerTree.Path) -> [MyPath] {
280-
var paths = [MyPath]()
281-
for segments in path.segments.split(separator: .close) {
282-
paths.append(segments: segments)
283-
}
284-
return paths
285-
}
286-
}
287-
288-
private extension Array where Element == MyPath {
289-
290-
mutating func append(segments: ArraySlice<LayerTree.Path.Segment>) {
291-
for (idx, path) in enumerated() {
292-
if path.bounds(segments: segments) {
293-
self[idx].appendPath(MyPath(segments: segments))
294-
return
295-
}
296-
}
297-
298-
append(MyPath(segments: segments))
299-
}
300-
301-
}
302-
303-
private extension LayerTree.Path.Segment {
304-
305-
var isMove: Bool {
306-
switch self {
307-
case .move: return true
308-
default: return false
309-
}
310-
}
311-
312-
var location: LayerTree.Point? {
313-
switch self {
314-
case .move(to: let p): return p
315-
case .line(let p): return p
316-
case .cubic(let p, _, _): return p
317-
case .close: return nil
318-
}
319-
}
320-
}
321-
322132
extension LayerTree.Point {
323133
var stringValue: String {
324134
"\(x), \(y)"

0 commit comments

Comments
 (0)