Skip to content

Commit c264cb4

Browse files
committed
iterative traversal command generator
1 parent 9d59a6b commit c264cb4

File tree

3 files changed

+68
-40
lines changed

3 files changed

+68
-40
lines changed

SwiftDraw/LayerTree.CommandGenerator.swift

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -52,56 +52,76 @@ extension LayerTree {
5252
self.options = options
5353
}
5454

55-
func renderCommands(for layer: Layer, colorConverter: any ColorConverter) -> [RendererCommand<P.Types>] {
55+
func renderCommands(for l: Layer, colorConverter c: any ColorConverter) -> [RendererCommand<P.Types>] {
5656
var commands = [RendererCommand<P.Types>]()
5757

58-
let state = makeCommandState(for: layer, colorConverter: colorConverter)
59-
60-
guard state.hasContents else { return commands }
61-
62-
if state.hasFilters {
63-
logUnsupportedFilters(layer.filters)
64-
}
65-
66-
if state.hasOpacity || state.hasTransform || state.hasClip || state.hasMask {
67-
commands.append(.pushState)
68-
}
69-
70-
commands.append(contentsOf: renderCommands(forTransforms: layer.transform))
71-
commands.append(contentsOf: renderCommands(forOpacity: layer.opacity))
72-
commands.append(contentsOf: renderCommands(forClip: layer.clip, using: layer.clipRule))
73-
74-
if state.hasMask {
75-
commands.append(.pushTransparencyLayer)
76-
}
77-
78-
//render all of the layer contents
79-
for contents in layer.contents {
80-
switch makeRenderContents(for: contents, colorConverter: state.colorConverter) {
81-
case let .simple(cmd):
58+
var stack: [RenderStep] = [
59+
.beginLayer(l, c)
60+
]
61+
62+
while let step = stack.popLast() {
63+
switch step {
64+
case let .beginLayer(layer, colorConverter):
65+
let state = makeCommandState(for: layer, colorConverter: colorConverter)
66+
67+
//guard state.hasContents else { continue }
68+
stack.append(.endLayer(layer, state))
69+
70+
if state.hasFilters {
71+
logUnsupportedFilters(layer.filters)
72+
}
73+
74+
if state.hasOpacity || state.hasTransform || state.hasClip || state.hasMask {
75+
commands.append(.pushState)
76+
}
77+
78+
commands.append(contentsOf: renderCommands(forTransforms: layer.transform))
79+
commands.append(contentsOf: renderCommands(forOpacity: layer.opacity))
80+
commands.append(contentsOf: renderCommands(forClip: layer.clip, using: layer.clipRule))
81+
82+
if state.hasMask {
83+
commands.append(.pushTransparencyLayer)
84+
}
85+
86+
//push render of all of the layer contents in reverse order
87+
for contents in layer.contents.reversed() {
88+
switch makeRenderContents(for: contents, colorConverter: colorConverter) {
89+
case let .simple(cmd):
90+
stack.append(.contents(cmd))
91+
case let .layer(layer):
92+
stack.append(.beginLayer(layer, colorConverter))
93+
}
94+
}
95+
96+
case let .contents(cmd):
8297
commands.append(contentsOf: cmd)
83-
case let .layer(layer):
84-
commands.append(contentsOf: renderCommands(for: layer, colorConverter: colorConverter))
85-
}
86-
}
8798

88-
//render apply mask
89-
if state.hasMask {
90-
commands.append(contentsOf: renderCommands(forMask: layer.mask))
91-
commands.append(.popTransparencyLayer)
92-
}
99+
case let .endLayer(layer, state):
100+
//render apply mask
101+
if state.hasMask {
102+
commands.append(contentsOf: renderCommands(forMask: layer.mask))
103+
commands.append(.popTransparencyLayer)
104+
}
93105

94-
if state.hasOpacity {
95-
commands.append(.popTransparencyLayer)
96-
}
106+
if state.hasOpacity {
107+
commands.append(.popTransparencyLayer)
108+
}
97109

98-
if state.hasOpacity || state.hasTransform || state.hasClip || state.hasMask {
99-
commands.append(.popState)
110+
if state.hasOpacity || state.hasTransform || state.hasClip || state.hasMask {
111+
commands.append(.popState)
112+
}
113+
}
100114
}
101115

102116
return commands
103117
}
104118

119+
enum RenderStep {
120+
case beginLayer(LayerTree.Layer, any ColorConverter)
121+
case contents([RendererCommand<P.Types>])
122+
case endLayer(LayerTree.Layer, CommandState)
123+
}
124+
105125
struct CommandState {
106126
var hasOpacity: Bool
107127
var hasTransform: Bool

SwiftDrawTests/LayerTree.BuilderTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ extension LayerTree.Builder {
175175
}
176176
}
177177

178-
private extension DOM.Group {
178+
extension DOM.Group {
179179

180180
static func make(child: DOM.GraphicsElement, nestedLevels: Int) -> DOM.Group {
181181
var group = DOM.Group()

SwiftDrawTests/SVGTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ final class SVGTests: XCTestCase {
148148
images.remove(lines)
149149
XCTAssertFalse(images.contains(SVG.makeLines()))
150150
}
151+
152+
func testDeepNestedSVG() async {
153+
let circle = DOM.Circle(cx: 50, cy: 50, r: 10)
154+
let dom = DOM.SVG(width: 50, height: 50)
155+
dom.childElements.append(DOM.Group.make(child: circle, nestedLevels: 500))
156+
157+
_ = SVG(dom: dom, options: .default)
158+
}
151159
}
152160

153161
private extension SVG {

0 commit comments

Comments
 (0)