aboutsummaryrefslogtreecommitdiff
path: root/src/layout.nim
blob: 4b43b3e4dcc038857ee46a1427344ba56e720fd6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import std/options
import formats/html

# const inline_elements = []

const block_elements = [
  "html", "body", "article", "section", "nav", "aside",
  "h1", "h2", "h3", "h4", "h5", "h6", "hgroup", "header",
  "footer", "address", "p", "hr", "pre", "blockquote",
  "ol", "ul", "menu", "li", "dl", "dt", "dd", "figure",
  "figcaption", "main", "div", "table", "form", "fieldset",
  "legend", "details", "summary"
]

type LayoutKind = enum
  Inline, Block, Document

# Improve error messages: suggest a ref object when failing from recursion
type Layout = ref object
  case kind*: LayoutKind:
    of Inline:
      weight: int
    of Block:
      discard
    of Document:
      discard
  node: Node
  children: seq[Layout]
  parent: Option[Layout]
  previous: Option[Layout]
  # x, y, width, height: float

# 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 kind: LayoutKind = Inline
  case node.kind:
    of Element:
      for child in node.children:
        let current = child.construct_tree()
        children.add(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, 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 <html> tag.
# Right now, HTML generation is Bad so this will change in the future
func layout(html: Html): 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
  node: Node
  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:
    return Inline
  elif node.children.len > 0:
    for child in node.children:
      if child.kind == Text: continue
      if child.tag in block_elements:
        return Block
    return Inline
  else:
    return Block]#

#[func layout(node: Node, parent: Layout, previous: Layout = nil): Layout =
  if node.kind == Element and node.children.len > 0:
    var previous: Layout = previous
    var current: Layout
    for child in node.children:
      if previous == nil:
        current = child.layout(result)
        result.children.add(current)
      else:
        current = child.layout(result, previous)
        result.children.add(current)
      previous = current
  else:
    result = Layout(node: node, parent: parent, previous: previous, children: @[])]#

when isMainModule:
  import formats/uri, protocols/http, std/strutils

  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(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")
      for child in layout.children:
        child.printLayout(indentation + 2)

  let text = parseHTML(httpRequest(parseURL("https://example.org:443/index.html")).body)
  print_layout layout(text)