From 0348b51a400e3f4b90fcddf57e7c3707cff22b00 Mon Sep 17 00:00:00 2001 From: j-james Date: Mon, 4 Jul 2022 20:46:09 -0700 Subject: Fix layout tree generation by making two separate passes --- src/layout.nim | 115 ++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 93 insertions(+), 22 deletions(-) diff --git a/src/layout.nim b/src/layout.nim index 14eebaa..4b43b3e 100644 --- a/src/layout.nim +++ b/src/layout.nim @@ -25,44 +25,108 @@ type Layout = ref object of Document: discard node: Node + children: seq[Layout] parent: Option[Layout] previous: Option[Layout] - children: seq[Layout] # x, y, width, height: float -# Recursively construct the layout tree -func layout(node: Node, parent: Option[Layout], previous: Option[Layout]): Layout = - result = Layout() # !!! ref types are nil by default :-( +# The layout tree is constructed in a two-part process: +# 1. We iterate through the node tree to create a simple layout tree. +# 2. We iterate through the new layout tree to create references to parent and previous nodes. +# Other functions will use these references to compute positioning coordinates. +# This greatly cuts down code complexity. It likely has a performance impact on large trees. +func construct_tree(node: Node): Layout = var children: seq[Layout] = @[] - var prevchild: Option[Layout] = none(Layout) var kind: LayoutKind = Inline case node.kind: of Element: for child in node.children: - # FIXME: parent nodes are broken. we pass a ref to result, which is different from the returned layout. - let current = child.layout(parent=some(result), previous=prevchild) + let current = child.construct_tree() children.add(current) - prevchild = some(current) if kind == Inline and child.kind == Element and child.tag in block_elements: kind = Block if node.children.len == 0: kind = Block of Text: discard - return Layout(node: node, parent: parent, previous: previous, children: children, kind: kind) + return Layout(node: node, children: children, kind: kind) + +func populate_tree(self: Layout, parent: Option[Layout] = none(Layout), previous: Option[Layout] = none(Layout)) = + self.parent = parent + self.previous = previous + var prevchild: Option[Layout] = none(Layout) + for child in self.children: + child.populate_tree(parent=some(self), previous=prevchild) + prevchild = some(child) # Assuming the first node of an HTML object is the tag. # Right now, HTML generation is Bad so this will change in the future func layout(html: Html): Layout = - result = Layout(kind: Document, node: html[0], parent: none(Layout), previous: none(Layout), children: @[]) - for child in html[0].children: - result.children.add(child.layout(none(Layout), none(Layout))) + var self = Layout(kind: Document, node: html[0], children: @[]) + for child in self.node.children: + self.children.add(child.construct_tree()) + self.populate_tree() + return self # TODO: change Html into a distinct Node and adjust layout accordingly - -#[type DocumentLayout = ref object +type DocumentLayout = ref object node: Node - children: seq[Layout]]# + children: seq[Layout] + +# Layout function that mutates a previously constructed layout object, +# so that we can make valid references to parent nodes. Not great. +# FIXME: Mutable cases within an object cause problems +#[func layout(self: var Layout, node: Node, parent: Option[Layout], previous: Option[Layout]) = + var children: seq[Layout] = @[] + var prevchild: Option[Layout] = none(Layout) + var kind: LayoutKind = Inline + case node.kind: + of Element: + for child in node.children: + var mutable = Layout() + mutable.layout(child, parent=some(self), previous=prevchild) + children.add(mutable) + prevchild = some(mutable) + if kind == Inline and child.kind == Element and child.tag in block_elements: + kind = Block + if node.children.len == 0: + kind = Block + of Text: + discard + self.node = node + self.parent = parent + self.previous = previous + self.children = children + self.kind = kind]# + +# I need to come up with a better way to generate a parent-child tree... +#[func layout(html: Html): Layout = + result = Layout(kind: Document, node: html[0], parent: none(Layout), previous: none(Layout), children: @[]) + for child in html[0].children: + var mutable = Layout() + mutable.layout(child, none(Layout), none(Layout)) + result.children.add(mutable)]# + +# Recursively construct the layout tree +#[func layout(node: Node, parent: Option[Layout], previous: Option[Layout]): Layout = + result = Layout() # !!! ref types are nil by default :-( + var children: seq[Layout] = @[] + var prevchild: Option[Layout] = none(Layout) + var kind: LayoutKind = Inline + case node.kind: + of Element: + for child in node.children: + # FIXME: parent nodes are broken. we pass a ref to result, which is different from the returned layout. + let current = child.layout(parent=some(result), previous=prevchild) + children.add(current) + prevchild = some(current) + if kind == Inline and child.kind == Element and child.tag in block_elements: + kind = Block + if node.children.len == 0: + kind = Block + of Text: + discard + return Layout(node: node, parent: parent, previous: previous, children: children, kind: kind)]# #[func layout_mode(node: Node): LayoutKind = if node.kind == Text: @@ -92,20 +156,27 @@ func layout(html: Html): Layout = result = Layout(node: node, parent: parent, previous: previous, children: @[])]# when isMainModule: - import formats/uri, protocols/http, std/strutils, print + import formats/uri, protocols/http, std/strutils - proc printLayout(layout: Layout, indentation=0) = + proc print_layout(layout: Layout, indentation=0) = if layout.node.kind == Element: stdout.write(" ".repeat(indentation)) stdout.write(layout.node.tag) - stdout.write(" ") + stdout.write(":") stdout.write(layout.kind) + let parent: Layout = layout.parent.get(Layout(node: Node(kind: Text))) + if parent.node.kind == Element: + stdout.write(" ") + stdout.write("parent:") + stdout.write(parent.node.tag) + let previous: Layout = layout.previous.get(Layout(node: Node(kind: Text))) + if previous.node.kind == Element: + stdout.write(" ") + stdout.write("previous:") + stdout.write(previous.node.tag) stdout.write("\n") - print layout.parent for child in layout.children: child.printLayout(indentation + 2) let text = parseHTML(httpRequest(parseURL("https://example.org:443/index.html")).body) - print layout(text) - for node in text: - printLayout node.layout(none(Layout), none(Layout)) + print_layout layout(text) -- cgit v1.2.3-70-g09d2