Skip to content

Commit 9d59a6b

Browse files
committed
iterative traversal builder
1 parent 84e7452 commit 9d59a6b

File tree

3 files changed

+96
-32
lines changed

3 files changed

+96
-32
lines changed

SwiftDraw/LayerTree.Builder.swift

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -91,47 +91,60 @@ extension LayerTree {
9191
return transform
9292
}
9393

94-
func makeLayer(from element: DOM.GraphicsElement, inheriting previousState: State) -> Layer {
94+
func makeLayer(from root: DOM.GraphicsElement, inheriting previousState: State) -> Layer {
95+
var stack: [(DOM.GraphicsElement, State, Layer?)] = [(root, previousState, nil)]
96+
var resultLayer: Layer? = nil
97+
98+
while let (currentElement, currentState, parentLayer) = stack.popLast() {
99+
let (layer, newState) = makeBaseLayer(from: currentElement, inheriting: currentState)
100+
101+
if let contents = makeContents(from: currentElement, with: newState) {
102+
layer.appendContents(contents)
103+
} else if let container = currentElement as? ContainerElement {
104+
// Push children in reverse so they are processed in the original order
105+
for child in container.childElements.reversed() {
106+
stack.append((child, newState, layer))
107+
}
108+
}
109+
110+
if let parent = parentLayer {
111+
parent.appendContents(.layer(layer))
112+
113+
if let svg = currentElement as? DOM.SVG {
114+
let viewBox = svg.viewBox ?? DOM.SVG.ViewBox(x: 0, y: 0, width: .init(svg.width), height: .init(svg.height))
115+
let bounds = LayerTree.Rect(x: viewBox.x, y: viewBox.y, width: viewBox.width, height: viewBox.height)
116+
layer.clip = [ClipShape(shape: .rect(within: bounds, radii: .zero), transform: .identity)]
117+
layer.transform = Builder.makeTransform(
118+
x: svg.x,
119+
y: svg.y,
120+
viewBox: svg.viewBox,
121+
width: svg.width,
122+
height: svg.height
123+
)
124+
}
125+
} else {
126+
// This must be the top-level root layer
127+
resultLayer = layer
128+
}
129+
}
130+
131+
return resultLayer!
132+
}
133+
134+
func makeBaseLayer(from element: DOM.GraphicsElement, inheriting previousState: State) -> (Layer, State) {
95135
let state = createState(for: element, inheriting: previousState)
96136
let attributes = element.attributes
97137
let l = Layer()
98138
l.class = element.class
99-
guard state.display != .none else { return l }
139+
guard state.display != .none else { return (l, state) }
100140

101141
l.transform = Builder.createTransforms(from: attributes.transform ?? [])
102142
l.clip = makeClipShapes(for: element)
103143
l.clipRule = attributes.clipRule
104144
l.mask = createMaskLayer(for: element)
105145
l.opacity = state.opacity
106-
l.contents = makeAllContents(from: element, with: state)
107146
l.filters = makeFilters(for: state)
108-
return l
109-
}
110-
111-
func makeChildLayer(from element: DOM.GraphicsElement, inheriting previousState: State) -> Layer {
112-
if let svg = element as? DOM.SVG {
113-
let layer = makeLayer(svg: svg, inheriting: previousState)
114-
let viewBox = svg.viewBox ?? DOM.SVG.ViewBox(x: 0, y: 0, width: .init(svg.width), height: .init(svg.height))
115-
let bounds = LayerTree.Rect(x: viewBox.x, y: viewBox.y, width: viewBox.width, height: viewBox.height)
116-
layer.clip = [ClipShape(shape: .rect(within: bounds, radii: .zero), transform: .identity)]
117-
return layer
118-
} else {
119-
return makeLayer(from: element, inheriting: previousState)
120-
}
121-
}
122-
123-
func makeAllContents(from element: DOM.GraphicsElement, with state: State) -> [Layer.Contents] {
124-
var all = [Layer.Contents]()
125-
if let contents = makeContents(from: element, with: state) {
126-
all.append(contents)
127-
}
128-
else if let container = element as? ContainerElement {
129-
container.childElements.forEach{
130-
let contents = Layer.Contents.layer(makeChildLayer(from: $0, inheriting: state))
131-
all.append(contents)
132-
}
133-
}
134-
return all
147+
return (l, state)
135148
}
136149

137150
func makeContents(from element: DOM.GraphicsElement, with state: State) -> Layer.Contents? {

SwiftDraw/LayerTree.Layer.swift

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ extension LayerTree {
4747
func appendContents(_ contents: Contents) {
4848
switch contents {
4949
case .layer(let l):
50-
guard l.contents.isEmpty == false else { return }
51-
5250
//if layer is simple, we can ignore all other properties
5351
if let simple = l.simpleContents {
5452
self.contents.append(simple)
@@ -63,6 +61,7 @@ extension LayerTree {
6361
var simpleContents: Contents? {
6462
guard self.contents.count == 1,
6563
let first = self.contents.first,
64+
`class` == nil,
6665
opacity == 1.0,
6766
transform == [],
6867
clip == [],
@@ -154,3 +153,32 @@ extension LayerTree {
154153
var anchor: DOM.TextAnchor
155154
}
156155
}
156+
157+
extension LayerTree.Layer.Contents: CustomDebugStringConvertible {
158+
159+
var debugDescription: String {
160+
switch self {
161+
case .image:
162+
return "image"
163+
case .layer(let l):
164+
return "layer-\(l.contents.map(\.debugDescription).joined(separator: ", "))"
165+
case .shape(let s, _, _):
166+
return "shape-\(s.debugDescription)"
167+
case .text:
168+
return "text"
169+
}
170+
}
171+
}
172+
173+
extension LayerTree.Shape: CustomDebugStringConvertible {
174+
175+
var debugDescription: String {
176+
switch self {
177+
case .ellipse: return "ellipse"
178+
case .rect: return "rect"
179+
case .line: return "line"
180+
case .path: return "path"
181+
case .polygon: return "polygon"
182+
}
183+
}
184+
}

SwiftDrawTests/LayerTree.BuilderTests.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,14 @@ final class LayerTreeBuilderTests: XCTestCase {
124124
let att2 = LayerTree.Builder.makeStrokeAttributes(with: state)
125125
XCTAssertEqual(att2.color, .none)
126126
}
127+
128+
func testDeepNestedSVGBuildLayers() async {
129+
let circle = DOM.Circle(cx: 50, cy: 50, r: 10)
130+
let svg = DOM.SVG(width: 50, height: 50)
131+
svg.childElements.append(DOM.Group.make(child: circle, nestedLevels: 500))
132+
133+
let _ = LayerTree.Builder(svg: svg).makeLayer()
134+
}
127135
}
128136

129137
private extension LayerTree.StrokeAttributes {
@@ -167,3 +175,18 @@ extension LayerTree.Builder {
167175
}
168176
}
169177

178+
private extension DOM.Group {
179+
180+
static func make(child: DOM.GraphicsElement, nestedLevels: Int) -> DOM.Group {
181+
var group = DOM.Group()
182+
group.childElements.append(child)
183+
184+
for _ in 0..<nestedLevels {
185+
let outerGroup = DOM.Group()
186+
outerGroup.childElements.append(group)
187+
group = outerGroup
188+
}
189+
190+
return group
191+
}
192+
}

0 commit comments

Comments
 (0)