about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/css/cascade.nim205
-rw-r--r--src/css/stylednode.nim18
-rw-r--r--src/css/values.nim6
-rw-r--r--src/html/dom.nim42
-rw-r--r--src/io/buffer.nim6
-rw-r--r--src/layout/engine.nim157
-rw-r--r--src/render/renderdocument.nim16
7 files changed, 261 insertions, 189 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim
index 8a52cc58..d71a3dab 100644
--- a/src/css/cascade.nim
+++ b/src/css/cascade.nim
@@ -2,13 +2,15 @@ import algorithm
 import streams
 import sugar
 
-import css/mediaquery
 import css/cssparser
+import css/mediaquery
 import css/select
 import css/selectorparser
 import css/sheet
+import css/stylednode
 import css/values
 import html/dom
+import html/tags
 
 type
   ApplyResult = object
@@ -32,6 +34,17 @@ proc applyProperty(elem: Element, d: CSSDeclaration, pseudo: PseudoElem) =
 
   elem.cssapplied = true
 
+proc applyProperty(styledNode: StyledNode, parent: CSSComputedValues, d: CSSDeclaration) =
+  
+  styledNode.computed.applyValue(parent, d)
+  #else:
+    #if styled.pseudo[pseudo] == nil:
+    #  elem.pseudo[pseudo] = elem.css.inheritProperties()
+    #elem.pseudo[pseudo].applyValue(elem.css, d)
+
+  if styledNode.node != nil:
+    Element(styledNode.node).cssapplied = true
+
 func applies(mq: MediaQuery): bool =
   case mq.t
   of CONDITION_MEDIA:
@@ -144,6 +157,58 @@ proc applyDeclarations(element: Element, ua, user: DeclarationList, author: seq[
   for rule in ares.important:
     element.applyProperty(rule, pseudo)
 
+# Always returns a new styled node, with the passed declarations applied.
+proc applyDeclarations(elem: Element, parent: CSSComputedValues, ua, user: DeclarationList, author: seq[DeclarationList]): StyledNode =
+  let pseudo = PSEUDO_NONE
+  var ares: ApplyResult
+
+  ares.applyNormal(ua[pseudo])
+  ares.applyNormal(user[pseudo])
+  for rule in author:
+    ares.applyNormal(rule[pseudo])
+
+  for rule in author:
+    ares.applyImportant(rule[pseudo])
+
+  let style = Element(elem).attr("style")
+  if style.len > 0:
+    let inline_rules = newStringStream(style).parseListOfDeclarations2()
+    ares.applyNormal(inline_rules)
+    ares.applyImportant(inline_rules)
+
+  ares.applyImportant(user[pseudo])
+  ares.applyImportant(ua[pseudo])
+
+  result = StyledNode(t: STYLED_ELEMENT, node: elem, computed: parent.inheritProperties())
+  for rule in ares.normal:
+    result.applyProperty(parent, rule)
+
+  for rule in ares.important:
+    result.applyProperty(parent, rule)
+
+# Either returns a new styled node or nil.
+proc applyDeclarations(pseudo: PseudoElem, parent: CSSComputedValues, ua, user: DeclarationList, author: seq[DeclarationList]): StyledNode =
+  var ares: ApplyResult
+
+  ares.applyNormal(ua[pseudo])
+  ares.applyNormal(user[pseudo])
+  for rule in author:
+    ares.applyNormal(rule[pseudo])
+
+  for rule in author:
+    ares.applyImportant(rule[pseudo])
+
+  ares.applyImportant(user[pseudo])
+  ares.applyImportant(ua[pseudo])
+
+  if ares.normal.len > 0 or ares.important.len > 0:
+    result = StyledNode(t: STYLED_ELEMENT, node: nil, computed: parent.inheritProperties(), pseudo: pseudo)
+    for rule in ares.normal:
+      result.applyProperty(parent, rule)
+
+    for rule in ares.important:
+      result.applyProperty(parent, rule)
+
 func applyMediaQuery(ss: CSSStylesheet): CSSStylesheet =
   result = ss
   for mq in ss.mq_list:
@@ -159,7 +224,7 @@ proc resetRules(elem: Element) =
   for pseudo in PSEUDO_BEFORE..PSEUDO_AFTER:
     elem.pseudo[pseudo] = nil
 
-proc applyRules(elem: Element, ua, user: CSSStylesheet, author: seq[CSSStylesheet]) {.inline.} =
+proc applyRules(elem: Element, ua, user: CSSStylesheet, author: seq[CSSStylesheet]) =
   let uadecls = calcRules(elem, ua)
   let userdecls = calcRules(elem, user)
   var authordecls: seq[DeclarationList]
@@ -169,49 +234,129 @@ proc applyRules(elem: Element, ua, user: CSSStylesheet, author: seq[CSSStyleshee
   for pseudo in PseudoElem:
     elem.applyDeclarations(uadecls, userdecls, authordecls, pseudo)
 
-proc applyRules(document: Document, ua, user: CSSStylesheet) =
+func calcRules(elem: Element, ua, user: CSSStylesheet, author: seq[CSSStylesheet]): tuple[uadecls, userdecls: DeclarationList, authordecls: seq[DeclarationList]] =
+  result.uadecls = calcRules(elem, ua)
+  result.userdecls = calcRules(elem, user)
+  for rule in author:
+    result.authordecls.add(calcRules(elem, rule))
+
+proc applyStyle(parent: StyledNode, elem: Element, uadecls, userdecls: DeclarationList, authordecls: seq[DeclarationList]): StyledNode =
+  let parentComputed = if parent != nil:
+    parent.computed
+  else:
+    rootProperties()
+
+  result = elem.applyDeclarations(parentComputed, uadecls, userdecls, authordecls)
+  assert result != nil
+
+proc applyRules(document: Document, ua, user: CSSStylesheet, previousStyled: StyledNode): StyledNode =
+  if document.html == nil:
+    return
+
   var author: seq[CSSStylesheet]
 
   if document.head != nil:
     for sheet in document.head.sheets:
       author.add(sheet)
 
-  var stack: seq[Element]
-
-  if document.html != nil:
-    stack.add(document.html)
-  var lenstack = newSeqOfCap[int](15)
+  var lenstack = newSeqOfCap[int](256)
+  var styledStack: seq[(StyledNode, Node, PseudoElem, StyledNode)]
+  if previousStyled != nil:
+    styledStack.add((nil, document.html, PSEUDO_NONE, previousStyled))
+  else:
+    styledStack.add((nil, document.html, PSEUDO_NONE, nil))
 
-  while stack.len > 0:
-    let elem = stack.pop()
+  #TODO TODO TODO this can't work as we currently store cached children in the
+  # same seq we use for storing new children...
+  # For now we just reset previous children which effectively disables caching.
+  while styledStack.len > 0:
+    let (styledParent, child, pseudo, cachedChild) = styledStack.pop()
 
     # Remove stylesheets on nil
-    if elem == nil:
+    if pseudo == PSEUDO_NONE and child == nil:
       let len = lenstack.pop()
       author.setLen(author.len - len)
       continue
 
-    if not elem.cssapplied:
-      let prev = elem.css
-      let ppseudo = elem.pseudo
-      elem.resetRules()
-      elem.applyRules(ua, user, author)
-      elem.checkRendered(prev, ppseudo)
-
-    # Add nil before the last element (in-stack), so we can remove the
-    # stylesheets
-    if elem.sheets.len > 0:
-      author.add(elem.sheets)
-      lenstack.add(elem.sheets.len)
-      stack.add(nil)
-
-    for i in countdown(elem.children.high, 0):
-      stack.add(elem.children[i])
-
-proc applyStylesheets*(document: Document, uass, userss: CSSStylesheet) =
+    template stack_append(styledParent: StyledNode, child: Node) =
+      if child.nodeType != ELEMENT_NODE or Element(child).cssapplied:
+        var cachedChild: StyledNode
+        for it in styledParent.children:
+          if it.node == child:
+            cachedChild = it
+            break
+        styledStack.add((styledParent, child, PSEUDO_NONE, cachedChild))
+      else:
+        eprint "else branch"
+        styledStack.add((styledParent, child, PSEUDO_NONE, nil))
+
+    template stack_append(styledParent: StyledNode, ps: PseudoElem) =
+      if Element(styledParent.node).cssapplied:
+        var cachedChild: StyledNode
+        for it in styledParent.children:
+          if it.t == STYLED_ELEMENT and it.pseudo == ps:
+            cachedChild = it
+            break
+        styledStack.add((styledParent, nil, ps, cachedChild))
+      else:
+        eprint "else branch 2"
+        styledStack.add((styledParent, nil, ps, nil))
+
+    var styledChild: StyledNode
+    if cachedChild != nil:
+      styledChild = cachedChild
+      if styledParent == nil:
+        result = styledChild
+      else:
+        styledParent.children.add(styledChild)
+      styledChild.children.setLen(0)
+    else:
+      if pseudo != PSEUDO_NONE:
+        let (ua, user, authordecls) = Element(styledParent.node).calcRules(ua, user, author)
+        let styledPseudo = pseudo.applyDeclarations(styledParent.computed, ua, user, authordecls)
+        if styledPseudo != nil:
+          styledParent.children.add(styledPseudo)
+          let content = styledPseudo.computed{"content"}
+          if content.len > 0:
+            styledPseudo.children.add(StyledNode(t: STYLED_TEXT, text: content))
+      else:
+        assert child != nil
+        if styledParent != nil:
+          if child.nodeType == ELEMENT_NODE:
+            let (ua, user, authordecls) = Element(child).calcRules(ua, user, author)
+            styledChild = applyStyle(styledParent, Element(child), ua, user, authordecls)
+            styledParent.children.add(styledChild)
+          elif child.nodeType == TEXT_NODE:
+            let text = Text(child)
+            styledChild = StyledNode(t: STYLED_TEXT, node: child, text: text.data)
+            styledParent.children.add(styledChild)
+        else:
+          # Root element
+          assert result == nil
+          let (ua, user, authordecls) = Element(child).calcRules(ua, user, author)
+          styledChild = applyStyle(styledParent, Element(child), ua, user, authordecls)
+          result = styledChild
+
+    if styledChild != nil and styledChild.node != nil and styledChild.node.nodeType == ELEMENT_NODE:
+      let elem = Element(styledChild.node)
+      # Add a nil before the last element (in-stack), so we can remove the
+      # stylesheets
+      if elem.sheets.len > 0:
+        author.add(elem.sheets)
+        lenstack.add(elem.sheets.len)
+        styledStack.add((nil, nil, PSEUDO_NONE, nil))
+
+      stack_append styledChild, PSEUDO_AFTER
+
+      for i in countdown(elem.childNodes.high, 0):
+        stack_append styledChild, elem.childNodes[i]
+
+      stack_append styledChild, PSEUDO_BEFORE
+
+proc applyStylesheets*(document: Document, uass, userss: CSSStylesheet, previousStyled: StyledNode): StyledNode =
   let uass = uass.applyMediaQuery()
   let userss = userss.applyMediaQuery()
-  document.applyRules(uass, userss)
+  return document.applyRules(uass, userss, previousStyled)
 
 proc refreshStyle*(elem: Element) =
   elem.cssapplied = false
diff --git a/src/css/stylednode.nim b/src/css/stylednode.nim
new file mode 100644
index 00000000..d6293187
--- /dev/null
+++ b/src/css/stylednode.nim
@@ -0,0 +1,18 @@
+import css/values
+import html/dom
+
+# Container to hold a style and a node.
+# Pseudo elements are implemented using StyledNode objects without nodes.
+type
+  StyledType* = enum
+    STYLED_ELEMENT, STYLED_TEXT
+
+  StyledNode* = ref object
+    case t*: StyledType
+    of STYLED_ELEMENT:
+      pseudo*: PseudoElem
+      computed*: CSSComputedValues
+    of STYLED_TEXT:
+      text*: string
+    node*: Node
+    children*: seq[StyledNode]
diff --git a/src/css/values.nim b/src/css/values.nim
index 73299f9e..e78af603 100644
--- a/src/css/values.nim
+++ b/src/css/values.nim
@@ -110,7 +110,7 @@ type
     of VALUE_DISPLAY:
       display*: CSSDisplay
     of VALUE_CONTENT:
-      content*: seq[Rune]
+      content*: string
     of VALUE_WHITESPACE:
       whitespace*: CSSWhitespace
     of VALUE_INTEGER:
@@ -603,12 +603,12 @@ func cssGlobal*(d: CSSDeclaration): CSSGlobalValueType =
       of "revert": return VALUE_REVERT
   return VALUE_NOGLOBAL
 
-func cssString(d: CSSDeclaration): seq[Rune] =
+func cssString(d: CSSDeclaration): string =
   if isToken(d):
     let tok = getToken(d)
     case tok.tokenType
     of CSS_IDENT_TOKEN, CSS_STRING_TOKEN:
-      return tok.value
+      return $tok.value
     else: return
 
 func cssDisplay(d: CSSDeclaration): CSSDisplay =
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 8562caa0..6dbfcc0f 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -122,18 +122,14 @@ type
   HTMLBRElement* = ref object of HTMLElement
 
   HTMLMenuElement* = ref object of HTMLElement
-    ordinalcounter*: int
 
   HTMLUListElement* = ref object of HTMLElement
-    ordinalcounter*: int
 
   HTMLOListElement* = ref object of HTMLElement
     start*: Option[int]
-    ordinalcounter*: int
 
   HTMLLIElement* = ref object of HTMLElement
     value*: Option[int]
-    ordinalvalue*: int
 
   HTMLStyleElement* = ref object of HTMLElement
     sheet*: CSSStylesheet
@@ -508,14 +504,14 @@ func newText*(document: Document, data: string = ""): Text =
   result.nodeType = TEXT_NODE
   result.document = document
   result.data = data
-  result.rootNode = result
+  result.rootNode = result #TODO apparently we shouldn't be doing this
 
 func newComment*(document: Document, data: string = ""): Comment =
   new(result)
   result.nodeType = COMMENT_NODE
   result.document = document
   result.data = data
-  result.rootNode = result
+  result.rootNode = result #TODO apparently we shouldn't be doing this
 
 # note: we do not implement custom elements
 func newHTMLElement*(document: Document, tagType: TagType, namespace = Namespace.HTML, prefix = none[string]()): HTMLElement =
@@ -538,10 +534,8 @@ func newHTMLElement*(document: Document, tagType: TagType, namespace = Namespace
     result = new(HTMLOListElement)
   of TAG_UL:
     result = new(HTMLUListElement)
-    HTMLUListElement(result).ordinalcounter = 1
   of TAG_MENU:
     result = new(HTMLMenuElement)
-    HTMLMenuElement(result).ordinalcounter = 1
   of TAG_LI:
     result = new(HTMLLIElement)
   of TAG_STYLE:
@@ -567,7 +561,7 @@ func newHTMLElement*(document: Document, tagType: TagType, namespace = Namespace
   result.css = rootProperties()
   result.namespace = namespace
   result.namespacePrefix = prefix
-  result.rootNode = result
+  result.rootNode = result #TODO apparently we shouldn't be doing this
   result.document = document
 
 func newHTMLElement*(document: Document, localName: string, namespace = Namespace.HTML, prefix = none[string](), tagType = tagType(localName)): Element =
@@ -578,7 +572,7 @@ func newHTMLElement*(document: Document, localName: string, namespace = Namespac
 func newDocument*(): Document =
   new(result)
   result.nodeType = DOCUMENT_NODE
-  result.rootNode = result
+  result.rootNode = result #TODO apparently we shouldn't be doing this
   result.document = result
 
 func newDocumentType*(document: Document, name: string, publicId = "", systemId = ""): DocumentType =
@@ -587,7 +581,7 @@ func newDocumentType*(document: Document, name: string, publicId = "", systemId
   result.name = name
   result.publicId = publicId
   result.systemId = systemId
-  result.rootNode = result
+  result.rootNode = result #TODO apparently we shouldn't be doing this
 
 func newAttr*(parent: Element, key, value: string): Attr =
   new(result)
@@ -596,7 +590,7 @@ func newAttr*(parent: Element, key, value: string): Attr =
   result.ownerElement = parent
   result.name = key
   result.value = value
-  result.rootNode = result
+  result.rootNode = result #TODO apparently we shouldn't be doing this
 
 func getElementById*(document: Document, id: string): Element =
   if id.len == 0:
@@ -781,30 +775,6 @@ proc preInsert*(parent, node, before: Node) =
 proc append*(parent, node: Node) =
   parent.preInsert(node, nil)
 
-proc applyOrdinal*(elem: HTMLLIElement) =
-  let val = elem.attri("value")
-  if val.issome:
-    elem.ordinalvalue = val.get
-  else:
-    let owner = elem.findAncestor({TAG_OL, TAG_UL, TAG_MENU})
-    if owner == nil:
-      elem.ordinalvalue = 1
-    else:
-      case owner.tagType
-      of TAG_OL:
-        let ol = HTMLOListElement(owner)
-        elem.ordinalvalue = ol.ordinalcounter
-        inc ol.ordinalcounter
-      of TAG_UL:
-        let ul = HTMLUListElement(owner)
-        elem.ordinalvalue = ul.ordinalcounter
-        inc ul.ordinalcounter
-      of TAG_MENU:
-        let menu = HTMLMenuElement(owner)
-        elem.ordinalvalue = menu.ordinalcounter
-        inc menu.ordinalcounter
-      else: discard
-
 proc reset*(element: Element) = 
   case element.tagType
   of TAG_INPUT:
diff --git a/src/io/buffer.nim b/src/io/buffer.nim
index e329b777..9fa23fe6 100644
--- a/src/io/buffer.nim
+++ b/src/io/buffer.nim
@@ -8,6 +8,7 @@ import unicode
 
 import css/cascade
 import css/sheet
+import css/stylednode
 import html/dom
 import html/tags
 import html/htmlparser
@@ -40,6 +41,7 @@ type
     attrs*: TermAttributes
     document*: Document
     viewport*: Viewport
+    prevstyled*: StyledNode
     redraw*: bool
     reshape*: bool
     nostatus*: bool
@@ -754,7 +756,9 @@ proc render*(buffer: Buffer) =
   of "text/html":
     if buffer.viewport == nil:
       buffer.viewport = Viewport(term: buffer.attrs)
-    buffer.lines = renderDocument(buffer.document, buffer.attrs, buffer.userstyle, buffer.viewport)
+    let ret = renderDocument(buffer.document, buffer.attrs, buffer.userstyle, buffer.viewport, buffer.prevstyled)
+    buffer.lines = ret[0]
+    buffer.prevstyled = ret[1]
   else: discard
   buffer.updateCursor()
 
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index 981a95ce..b15f27b1 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -2,12 +2,13 @@ import math
 import options
 import unicode
 
-import layout/box
+import css/stylednode
+import css/values
 import html/tags
 import html/dom
-import css/values
-import utils/twtstr
 import io/term
+import layout/box
+import utils/twtstr
 
 # Build phase
 
@@ -646,12 +647,12 @@ func getInputBox(parent: BoxBuilder, input: HTMLInputElement, viewport: Viewport
   return textbox
 
 # Don't generate empty anonymous inline blocks between block boxes
-func canGenerateAnonymousInline(blockgroup: seq[BoxBuilder], computed: CSSComputedValues, text: Text): bool =
+func canGenerateAnonymousInline(blockgroup: seq[BoxBuilder], computed: CSSComputedValues, str: string): bool =
   return blockgroup.len > 0 and blockgroup[^1].computed{"display"} == DISPLAY_INLINE or
     computed{"white-space"} in {WHITESPACE_PRE_LINE, WHITESPACE_PRE, WHITESPACE_PRE_WRAP} or
-    not text.data.onlyWhitespace()
+    not str.onlyWhitespace()
 
-proc generateBlockBox(elem: Element, viewport: Viewport): BlockBoxBuilder
+proc generateBlockBox(styledNode: StyledNode, viewport: Viewport): BlockBoxBuilder
 
 template flush_block_group(computed: CSSComputedValues) =
   if blockgroup.len > 0:
@@ -667,142 +668,74 @@ template flush_ibox() =
     blockgroup.add(ibox)
     ibox = nil
 
-proc generateInlineBoxes(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[BoxBuilder], viewport: Viewport)
+proc generateInlineBoxes(box: BlockBoxBuilder, styledNode: StyledNode, blockgroup: var seq[BoxBuilder], viewport: Viewport)
 
-proc generateFromElem(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[BoxBuilder], viewport: Viewport, ibox: var InlineBoxBuilder, listItemCounter: var int) =
-  if elem.tagType == TAG_BR:
-    ibox = box.getTextBox()
-    ibox.newline = true
-    flush_ibox
+proc generateFromElem(box: BlockBoxBuilder, styledNode: StyledNode, blockgroup: var seq[BoxBuilder], viewport: Viewport, ibox: var InlineBoxBuilder, listItemCounter: var int) =
+  if styledNode.node != nil:
+    let elem = Element(styledNode.node)
+    if elem.tagType == TAG_BR:
+      ibox = box.getTextBox()
+      ibox.newline = true
+      flush_ibox
 
-  case elem.css{"display"}
+  case styledNode.computed{"display"}
   of DISPLAY_BLOCK:
-    flush_block_group(elem.css)
-    let childbox = elem.generateBlockBox(viewport)
+    flush_block_group(styledNode.computed)
+    let childbox = styledNode.generateBlockBox(viewport)
     box.children.add(childbox)
   of DISPLAY_LIST_ITEM:
-    flush_block_group(elem.css)
-    let childbox = getListItemBox(elem.css, listItemCounter)
-    childbox.content = elem.generateBlockBox(viewport)
+    flush_block_group(styledNode.computed)
+    let childbox = getListItemBox(styledNode.computed, listItemCounter)
+    childbox.content = styledNode.generateBlockBox(viewport)
     box.children.add(childbox)
     inc listItemCounter
   of DISPLAY_INLINE:
     flush_ibox
-    box.generateInlineBoxes(elem, blockgroup, viewport)
+    box.generateInlineBoxes(styledNode, blockgroup, viewport)
   of DISPLAY_INLINE_BLOCK:
     flush_ibox
     let childbox = getInlineBlockBox(box.computed)
-    childbox.content = elem.generateBlockBox(viewport)
+    childbox.content = styledNode.generateBlockBox(viewport)
     blockgroup.add(childbox)
   else:
     discard #TODO
 
-proc generateInlinePseudoBox(box: BlockBoxBuilder, computed: CSSComputedValues, blockgroup: var seq[BoxBuilder], viewport: Viewport) =
+proc generateInlineBoxes(box: BlockBoxBuilder, styledNode: StyledNode, blockgroup: var seq[BoxBuilder], viewport: Viewport) =
   var ibox: InlineBoxBuilder = nil
 
-  if computed{"content"}.len > 0:
-    ibox = getTextBox(computed)
-    ibox.text.add($computed{"content"})
-
-  flush_ibox
-
-proc generateBlockPseudoBox(computed: CSSComputedValues, viewport: Viewport): BlockBoxBuilder =
-  let box = getBlockBox(computed)
-  var blockgroup: seq[BoxBuilder]
-  var ibox: InlineBoxBuilder = nil
-
-  if computed{"content"}.len > 0:
-    ibox = getTextBox(computed)
-    ibox.text.add($computed{"content"})
-    flush_ibox
-    flush_block_group(computed)
-
-  return box
-
-proc generatePseudo(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[BoxBuilder], viewport: Viewport, ibox: var InlineBoxBuilder, computed: CSSComputedValues) =
-  case computed{"display"}
-  of DISPLAY_BLOCK:
-    flush_block_group(elem.css)
-    let childbox = generateBlockPseudoBox(computed, viewport)
-    box.children.add(childbox)
-  of DISPLAY_LIST_ITEM:
-    flush_block_group(elem.css)
-    let childbox = getListItemBox(computed, 1)
-    childbox.content = generateBlockPseudoBox(computed, viewport)
-    box.children.add(childbox)
-  of DISPLAY_INLINE:
-    flush_ibox
-    box.generateInlinePseudoBox(computed, blockgroup, viewport)
-  of DISPLAY_INLINE_BLOCK:
-    flush_ibox
-    let childbox = getInlineBlockBox(box.computed)
-    childbox.content = generateBlockPseudoBox(computed, viewport)
-    blockgroup.add(childbox)
-  else:
-    discard #TODO
-
-proc generateBoxBefore(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[BoxBuilder], viewport: Viewport, ibox: var InlineBoxBuilder) =
-  if elem.pseudo[PSEUDO_BEFORE] != nil:
-    box.generatePseudo(elem, blockgroup, viewport, ibox, elem.pseudo[PSEUDO_BEFORE])
-
-  if elem.tagType == TAG_INPUT:
-    flush_ibox
-    let input = HTMLInputElement(elem)
-    ibox = box.getInputBox(input, viewport)
-
-proc generateBoxAfter(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[BoxBuilder], viewport: Viewport, ibox: var InlineBoxBuilder) =
-  if elem.pseudo[PSEUDO_AFTER] != nil:
-    box.generatePseudo(elem, blockgroup, viewport, ibox, elem.pseudo[PSEUDO_AFTER])
-
-proc generateInlineBoxes(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[BoxBuilder], viewport: Viewport) =
-  var ibox: InlineBoxBuilder = nil
-
-  generateBoxBefore(box, elem, blockgroup, viewport, ibox)
-
   var listItemCounter = 1 # ordinal value of current list
 
-  for child in elem.childNodes:
-    case child.nodeType
-    of ELEMENT_NODE:
-      let child = Element(child)
+  for child in styledNode.children:
+    case child.t
+    of STYLED_ELEMENT:
       box.generateFromElem(child, blockgroup, viewport, ibox, listItemCounter)
-    of TEXT_NODE:
-      let child = Text(child)
+    of STYLED_TEXT:
       if ibox == nil:
-        ibox = getTextBox(elem.css)
-        ibox.node = elem
-      ibox.text.add(child.data)
-    else: discard
-
-  generateBoxAfter(box, elem, blockgroup, viewport, ibox)
+        ibox = getTextBox(styledNode.computed)
+        ibox.node = child.node
+      ibox.text.add(child.text)
 
   flush_ibox
 
-proc generateBlockBox(elem: Element, viewport: Viewport): BlockBoxBuilder =
-  let box = getBlockBox(elem.css)
+proc generateBlockBox(styledNode: StyledNode, viewport: Viewport): BlockBoxBuilder =
+  let elem = Element(styledNode.node)
+  let box = getBlockBox(styledNode.computed)
   var blockgroup: seq[BoxBuilder]
   var ibox: InlineBoxBuilder = nil
 
-  generateBoxBefore(box, elem, blockgroup, viewport, ibox)
-
   var listItemCounter = 1 # ordinal value of current list
   
-  for child in elem.childNodes:
-    case child.nodeType
-    of ELEMENT_NODE:
+  for child in styledNode.children:
+    case child.t
+    of STYLED_ELEMENT:
       flush_ibox
-      let child = Element(child)
       box.generateFromElem(child, blockgroup, viewport, ibox, listItemCounter)
-    of TEXT_NODE:
-      let child = Text(child)
-      if canGenerateAnonymousInline(blockgroup, box.computed, child):
+    of STYLED_TEXT:
+      if canGenerateAnonymousInline(blockgroup, box.computed, child.text):
         if ibox == nil:
-          ibox = getTextBox(elem.css)
-          ibox.node = elem
-        ibox.text.add(child.data)
-    else: discard
-
-  generateBoxAfter(box, elem, blockgroup, viewport, ibox)
+          ibox = getTextBox(styledNode.computed)
+          ibox.node = child.node
+        ibox.text.add(child.text)
 
   flush_ibox
   if blockgroup.len > 0:
@@ -811,10 +744,10 @@ proc generateBlockBox(elem: Element, viewport: Viewport): BlockBoxBuilder =
       box.children = blockgroup
       box.inlinelayout = true
     else:
-      flush_block_group(elem.css)
+      flush_block_group(styledNode.computed)
   return box
 
 
-proc renderLayout*(viewport: var Viewport, document: Document) =
-  let builder = document.html.generateBlockBox(viewport)
+proc renderLayout*(viewport: var Viewport, document: Document, root: StyledNode) =
+  let builder = root.generateBlockBox(viewport)
   viewport.root = buildRootBlock(builder, viewport)
diff --git a/src/render/renderdocument.nim b/src/render/renderdocument.nim
index b01728dd..196c1496 100644
--- a/src/render/renderdocument.nim
+++ b/src/render/renderdocument.nim
@@ -3,6 +3,7 @@ import unicode
 
 import css/cascade
 import css/sheet
+import css/stylednode
 import css/values
 import html/dom
 import io/cell
@@ -249,10 +250,11 @@ proc renderBlockContext(grid: var FlexibleGrid, ctx: BlockContext, x, y: int, te
 
 const css = staticRead"res/ua.css"
 let uastyle = css.parseStylesheet()
-proc renderDocument*(document: Document, term: TermAttributes, userstyle: CSSStylesheet, layout: var Viewport): FlexibleGrid =
-  document.applyStylesheets(uastyle, userstyle)
-  layout.renderLayout(document)
-  result.setLen(0)
-  result.renderBlockContext(layout.root, 0, 0, term)
-  if result.len == 0:
-    result.addLine()
+proc renderDocument*(document: Document, term: TermAttributes, userstyle: CSSStylesheet, layout: var Viewport, previousStyled: StyledNode): (FlexibleGrid, StyledNode) =
+  let styledNode = document.applyStylesheets(uastyle, userstyle, previousStyled)
+  result[1] = styledNode
+  layout.renderLayout(document, styledNode)
+  result[0].setLen(0)
+  result[0].renderBlockContext(layout.root, 0, 0, term)
+  if result[0].len == 0:
+    result[0].addLine()