Skip to content

Commit 7bf84f7

Browse files
authored
Merge pull request #76 from swhitty/iterative-traversal
Convert recursive functions to iterative traversal
2 parents 63a6201 + a69e747 commit 7bf84f7

File tree

10 files changed

+4063
-106
lines changed

10 files changed

+4063
-106
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.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

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+
}

SwiftDraw/Parser.XML.Element.swift

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -111,33 +111,30 @@ extension XMLParser {
111111
return ge
112112
}
113113

114-
func parseContainerChildren(_ e: XML.Element) throws -> [DOM.GraphicsElement] {
115-
guard e.name == "svg" ||
116-
e.name == "clipPath" ||
117-
e.name == "pattern" ||
118-
e.name == "mask" ||
119-
e.name == "defs" ||
120-
e.name == "switch" ||
121-
e.name == "g" ||
122-
e.name == "a" else {
123-
throw Error.invalid
124-
}
114+
func parseGraphicsElements(_ elements: [XML.Element]) throws -> [DOM.GraphicsElement] {
115+
var result = [DOM.GraphicsElement]()
116+
var stack: [(XML.Element, parent: ContainerElement?)] = elements
117+
.reversed()
118+
.map { ($0, parent: nil) }
119+
120+
while let (element, parent) = stack.popLast() {
121+
guard let ge = try parseGraphicsElement(element) else {
122+
continue
123+
}
125124

126-
var children = [DOM.GraphicsElement]()
127-
128-
for n in e.children {
129-
do {
130-
if let ge = try parseGraphicsElement(n) {
131-
children.append(ge)
132-
}
133-
} catch let error {
134-
if let parseError = parseError(for: error, parsing: n, with: options) {
135-
throw parseError
136-
}
125+
if var parent {
126+
parent.childElements.append(ge)
127+
} else {
128+
result.append(ge)
137129
}
130+
131+
if let container = ge as? ContainerElement {
132+
stack.append(contentsOf: element.children.reversed().map { ($0, container) })
133+
}
134+
138135
}
139136

140-
return children
137+
return result
141138
}
142139

143140
func parseError(for error: Swift.Error, parsing element: XML.Element, with options: Options) -> XMLParser.Error? {
@@ -165,19 +162,15 @@ extension XMLParser {
165162
throw Error.invalid
166163
}
167164

168-
let group = DOM.Group()
169-
group.childElements = try parseContainerChildren(e)
170-
return group
165+
return DOM.Group()
171166
}
172167

173168
func parseSwitch(_ e: XML.Element) throws -> DOM.Switch {
174169
guard e.name == "switch" else {
175170
throw Error.invalid
176171
}
177172

178-
let node = DOM.Switch()
179-
node.childElements = try parseContainerChildren(e)
180-
return node
173+
return DOM.Switch()
181174
}
182175

183176
func parseAttributes(_ e: XML.Element) throws -> Attributes {

SwiftDraw/Parser.XML.SVG.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ extension XMLParser {
5858
let svg = DOM.SVG(width: DOM.Length(w), height: DOM.Length(h))
5959
svg.x = try att.parseCoordinate("x")
6060
svg.y = try att.parseCoordinate("y")
61-
svg.childElements = try parseContainerChildren(e)
61+
svg.childElements = try parseGraphicsElements(e.children)
6262
svg.viewBox = try parseViewBox(try att.parseString("viewBox"))
6363

6464
svg.defs = try parseSVGDefs(e)
@@ -121,7 +121,7 @@ extension XMLParser {
121121
}
122122

123123
var defs = Dictionary<String, DOM.GraphicsElement>()
124-
let elements = try parseContainerChildren(e)
124+
let elements = try parseGraphicsElements(e.children)
125125

126126
for e in elements {
127127
guard let id = e.id else {
@@ -153,7 +153,7 @@ extension XMLParser {
153153
let att = try parseAttributes(e)
154154
let id: String = try att.parseString("id")
155155

156-
let children = try parseContainerChildren(e)
156+
let children = try parseGraphicsElements(e.children)
157157
return DOM.ClipPath(id: id, childElements: children)
158158
}
159159

@@ -176,7 +176,7 @@ extension XMLParser {
176176
let att = try parseAttributes(e)
177177
let id: String = try att.parseString("id")
178178

179-
let children = try parseContainerChildren(e)
179+
let children = try parseGraphicsElements(e.children)
180180
return DOM.Mask(id: id, childElements: children)
181181
}
182182

@@ -198,7 +198,7 @@ extension XMLParser {
198198

199199
let att = try parseAttributes(e)
200200
var pattern = try parsePattern(att)
201-
pattern.childElements = try parseContainerChildren(e)
201+
pattern.childElements = try parseGraphicsElements(e.children)
202202
return pattern
203203
}
204204
}

SwiftDraw/Parser.XML.Text.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ extension XMLParser {
4646
func parseAnchor(_ att: AttributeParser, element: XML.Element) throws -> DOM.Anchor? {
4747
let anchor = DOM.Anchor()
4848
anchor.href = try att.parseUrl("href")
49-
anchor.childElements = try parseContainerChildren(element)
5049
return anchor
5150
}
5251

0 commit comments

Comments
 (0)