about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--res/default.css5
-rw-r--r--src/css/parser.nim15
-rw-r--r--src/css/selector.nim3
-rw-r--r--src/css/style.nim56
-rw-r--r--src/html/dom.nim93
-rw-r--r--src/io/buffer.nim24
-rw-r--r--src/layout/box.nim14
-rw-r--r--src/layout/layout.nim340
-rw-r--r--src/types/enums.nim3
9 files changed, 233 insertions, 320 deletions
diff --git a/res/default.css b/res/default.css
index 6a176492..5bb5526c 100644
--- a/res/default.css
+++ b/res/default.css
@@ -8,9 +8,8 @@ menu, noframes, body {
 	display: block;
 }
 
-br {
-	display: block;
-	content: ' ';
+pre {
+	margin-top: 1em;
 }
 
 a, abbr, b, bdo, button, cite, code, del, dfn, em, font, i, img, ins,
diff --git a/src/css/parser.nim b/src/css/parser.nim
index fac1bce8..25a0e21d 100644
--- a/src/css/parser.nim
+++ b/src/css/parser.nim
@@ -132,7 +132,7 @@ proc reconsume(state: var CSSTokenizerState) =
 func peek(state: CSSTokenizerState, i: int): Rune =
   return state.buf[state.at + i]
 
-proc has(state: var CSSTokenizerState, i: int): bool =
+proc has(state: var CSSTokenizerState, i: int = 0): bool =
   if state.at + i >= state.buf.len and not state.stream.atEnd():
     state.buf &= state.stream.readLine().toRunes() & Rune('\n')
   return state.at + i < state.buf.len
@@ -143,11 +143,6 @@ func curr(state: CSSTokenizerState): Rune =
 proc isValidEscape*(state: var CSSTokenizerState): bool =
   return state.has(1) and state.curr() == Rune('\\') and state.peek(1) != Rune('\n')
 
-proc has(state: var CSSTokenizerState): bool =
-  if state.at >= state.buf.len and not state.stream.atEnd():
-    state.buf &= state.stream.readLine().toRunes() & Rune('\n')
-  return state.at < state.buf.len
-
 proc startsWithIdentifier*(state: var CSSTokenizerState): bool =
   if not state.has():
     return false
@@ -345,16 +340,16 @@ proc consumeIdentLikeToken(state: var CSSTokenizerState): CSSToken =
   return CSSToken(tokenType: CSS_IDENT_TOKEN, value: s)
 
 proc consumeComments(state: var CSSTokenizerState) =
-  if state.has(2) and state.peek(1) == Rune('/') and state.peek(2) == Rune('*'):
+  if state.has(1) and state.curr() == Rune('/') and state.peek(1) == Rune('*'):
     discard state.consume()
     discard state.consume()
-    while state.has(2) and not (state.peek(1) == Rune('*') and state.peek(2) == Rune('/')):
+    while state.has(1) and not (state.curr() == Rune('*') and state.peek(1) == Rune('/')):
       discard state.consume()
 
-    if state.has(2):
-      discard state.consume()
     if state.has(1):
       discard state.consume()
+    if state.has():
+      discard state.consume()
 
 proc consumeToken(state: var CSSTokenizerState): CSSToken =
   state.consumeComments()
diff --git a/src/css/selector.nim b/src/css/selector.nim
index d381e618..0ec1ac27 100644
--- a/src/css/selector.nim
+++ b/src/css/selector.nim
@@ -13,6 +13,9 @@ type
     QUERY_TYPE, QUERY_CLASS, QUERY_ATTR, QUERY_DELIM, QUERY_VALUE,
     QUERY_PSEUDO, QUERY_PSELEM
 
+  PseudoElem* = enum
+    PSEUDO_NONE, PSEUDO_BEFORE, PSEUDO_AFTER
+
   SelectorParser = object
     selectors: seq[SelectorList]
     query: QueryMode
diff --git a/src/css/style.nim b/src/css/style.nim
index 6d6935d3..5f426e58 100644
--- a/src/css/style.nim
+++ b/src/css/style.nim
@@ -12,34 +12,6 @@ type
     unit*: CSSUnit
     auto*: bool
 
-  CSS2Properties* = ref object
-    rawtext*: string
-    fmttext*: seq[string]
-    x*: int
-    y*: int
-    ex*: int
-    ey*: int
-    width*: int
-    height*: int
-    hidden*: bool
-    before*: CSS2Properties
-    after*: CSS2Properties
-    margintop*: CSSLength
-    marginbottom*: CSSLength
-    marginleft*: CSSLength
-    marginright*: CSSLength
-    centered*: bool
-    display*: DisplayType
-    bold*: bool
-    fontStyle*: CSSFontStyle
-    underscore*: bool
-    islink*: bool
-    selected*: bool
-    indent*: int
-    color*: CSSColor
-    position*: CSSPosition
-    content*: seq[Rune]
-
   CSSValues* = array[low(CSSRuleType)..high(CSSRuleType), CSSComputedValue]
 
   CSSColor* = tuple[r: uint8, g: uint8, b: uint8, a: uint8]
@@ -59,6 +31,8 @@ type
       content*: seq[Rune]
     of VALUE_NONE: discard
 
+  CSSComputedValues* = array[low(CSSRuleType)..high(CSSRuleType), CSSComputedValue]
+
   CSSSpecifiedValue* = object of CSSComputedValue
     hasGlobalValue: bool
     globalValue: CSSGlobalValueType
@@ -79,7 +53,7 @@ const ValueTypes = {
 func getValueType*(rule: CSSRuleType): CSSValueType =
   return ValueTypes[rule]
 
-func cells(l: CSSLength): int =
+func cells*(l: CSSLength): int =
   case l.unit
   of UNIT_EM:
     return int(l.num)
@@ -214,18 +188,18 @@ func cssLength(d: CSSDeclaration): CSSLength =
 
   return CSSLength(num: 0, unit: UNIT_EM)
 
-func hasColor*(style: CSS2Properties): bool =
-  return style.color.r != 0 or style.color.b != 0 or style.color.g != 0 or style.color.a != 0
-
-func termColor*(style: CSS2Properties): ForegroundColor =
-  if style.color.r > 120:
-    return fgRed
-  elif style.color.b > 120:
-    return fgBlue
-  elif style.color.g > 120:
-    return fgGreen
-  else:
-    return fgWhite
+#func hasColor*(style: CSS2Properties): bool =
+#  return style.color.r != 0 or style.color.b != 0 or style.color.g != 0 or style.color.a != 0
+#
+#func termColor*(style: CSS2Properties): ForegroundColor =
+#  if style.color.r > 120:
+#    return fgRed
+#  elif style.color.b > 120:
+#    return fgBlue
+#  elif style.color.g > 120:
+#    return fgGreen
+#  else:
+#    return fgWhite
 
 func isToken(d: CSSDeclaration): bool = d.value.len > 0 and d.value[0] of CSSToken
 func getToken(d: CSSDeclaration): CSSToken = (CSSToken)d.value[0]
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 82301146..647b1f7c 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -7,6 +7,7 @@ import streams
 import sequtils
 import sugar
 import algorithm
+import options
 
 import css/style
 import css/parser
@@ -84,7 +85,9 @@ type
     id*: string
     classList*: seq[string]
     attributes*: Table[string, Attr]
-    cssvalues*: array[low(CSSRuleType)..high(CSSRuleType), CSSComputedValue]
+    cssvalues*: CSSComputedValues
+    cssvalues_before*: Option[CSSComputedValues]
+    cssvalues_after*: Option[CSSComputedValues]
 
   HTMLElement* = ref HTMLElementObj
   HTMLElementObj = object of ElementObj
@@ -284,13 +287,13 @@ func pseudoSelectorMatches(elem: Element, sel: Selector): bool =
   of "last-child": return elem.parentNode.lastElementChild == elem
   else: return false
 
-func pseudoElemSelectorMatches(elem: Element, sel: Selector): bool =
+func pseudoElemSelectorMatches(elem: Element, sel: Selector, pseudo: PseudoElem = PSEUDO_NONE): bool =
   case sel.elem
-  of "after": return false
-  of "before": return false
+  of "after": return pseudo == PSEUDO_AFTER
+  of "before": return pseudo == PSEUDO_BEFORE
   else: return false
 
-func selectorMatches(elem: Element, sel: Selector): bool =
+func selectorMatches(elem: Element, sel: Selector, pseudo: PseudoElem = PSEUDO_NONE): bool =
   case sel.t
   of TYPE_SELECTOR:
     return elem.tagType == sel.tag
@@ -303,15 +306,15 @@ func selectorMatches(elem: Element, sel: Selector): bool =
   of PSEUDO_SELECTOR:
     return pseudoSelectorMatches(elem, sel)
   of PSELEM_SELECTOR:
-    return pseudoElemSelectorMatches(elem, sel)
+    return pseudoElemSelectorMatches(elem, sel, pseudo)
   of UNIVERSAL_SELECTOR:
     return true
   of FUNC_SELECTOR:
     return false
 
-func selectorsMatch(elem: Element, selectors: SelectorList): bool =
+func selectorsMatch(elem: Element, selectors: SelectorList, pseudo: PseudoElem = PSEUDO_NONE): bool =
   for sel in selectors.sels:
-    if not selectorMatches(elem, sel):
+    if not selectorMatches(elem, sel, pseudo):
       return false
   return true
 
@@ -365,47 +368,69 @@ proc querySelector*(document: Document, q: string): seq[Element] =
   for sel in selectors:
     result.add(document.selectElems(sel))
 
-func calcRules(elem: Element, rules: CSSStylesheet): seq[CSSSimpleBlock] =
-  var tosort: seq[tuple[s:int,b:CSSSimpleBlock]]
-  for rule in rules.value:
-    let selectors = parseSelectors(rule.prelude) #TODO perf: compute this once
-    for sel in selectors:
-      if elem.selectorsMatch(sel):
-        let spec = getSpecificity(sel)
-        tosort.add((spec,rule.oblock))
 
-  tosort.sort((x, y) => cmp(x.s,y.s))
-  return tosort.map((x) => x.b)
-
-proc applyProperty(elem: Element, decl: CSSDeclaration) =
+proc applyProperty(elem: Element, decl: CSSDeclaration, pseudo: PseudoElem = PSEUDO_NONE) =
   var parentprops: array[low(CSSRuleType)..high(CSSRuleType), CSSComputedValue]
   if elem.parentElement != nil:
     parentprops = elem.parentElement.cssvalues
   else:
     parentprops = getInitialProperties()
   let cval = getComputedValue(decl, parentprops)
-  elem.cssvalues[cval.t] = cval
+  case pseudo
+  of PSEUDO_NONE:
+    elem.cssvalues[cval.t] = cval
+  of PSEUDO_BEFORE:
+    if elem.cssvalues_before.isNone:
+      elem.cssvalues_before = some(getInitialProperties())
+    elem.cssvalues_before.get[cval.t] = cval
+  of PSEUDO_AFTER:
+    if elem.cssvalues_after.isNone:
+      elem.cssvalues_after = some(getInitialProperties())
+    elem.cssvalues_after.get[cval.t] = cval
+
+type ParsedRule = tuple[sels: seq[SelectorList], oblock: CSSSimpleBlock]
+
+func calcRules(elem: Element, rules: seq[ParsedRule]):
+    array[low(PseudoElem)..high(PseudoElem), seq[CSSSimpleBlock]] =
+  var tosorts: array[low(PseudoElem)..high(PseudoElem), seq[tuple[s:int,b:CSSSimpleBlock]]]
+  for rule in rules:
+    for sel in rule.sels:
+      #TODO: optimize, like rewrite selector match algorithm output or something
+      for pseudo in low(PseudoElem)..high(PseudoElem):
+        if elem.selectorsMatch(sel, pseudo):
+          let spec = getSpecificity(sel)
+          tosorts[pseudo].add((spec,rule.oblock))
+
+  for i in low(PseudoElem)..high(PseudoElem):
+    tosorts[i].sort((x, y) => cmp(x.s,y.s))
+    result[i] = tosorts[i].map((x) => x.b)
 
 proc applyRules*(document: Document, rules: CSSStylesheet): seq[tuple[e:Element,d:CSSDeclaration]] =
   var stack: seq[Element]
 
   stack.add(document.root)
 
+  let parsed = rules.value.map((x) => (sels: parseSelectors(x.prelude), oblock: x.oblock))
   while stack.len > 0:
     let elem = stack.pop()
-    for oblock in calcRules(elem, rules):
-      let decls = parseCSSListOfDeclarations(oblock.value)
-      for item in decls:
-        if item of CSSDeclaration:
-          let decl = CSSDeclaration(item)
-          if decl.important:
-            result.add((elem, decl))
-          else:
-            elem.applyProperty(decl)
-
-    for child in elem.children:
-      stack.add(child)
-
+    #TODO: optimize
+    #ok this whole idea was stupid, what I should've done is to just check for
+    #pseudo elem selectors, this is way too slow
+    let rules_pseudo = calcRules(elem, parsed)
+    for pseudo in low(PseudoElem)..high(PseudoElem):
+      let rules = rules_pseudo[pseudo]
+      for rule in rules:
+        let decls = parseCSSListOfDeclarations(rule.value)
+        for item in decls:
+          if item of CSSDeclaration:
+            let decl = CSSDeclaration(item)
+            if decl.important:
+              result.add((elem, decl))
+            else:
+              elem.applyProperty(decl, pseudo)
+
+      for child in elem.children:
+        stack.add(child)
 
 proc applyDefaultStylesheet*(document: Document) =
   let important = document.applyRules(stylesheet)
diff --git a/src/io/buffer.nim b/src/io/buffer.nim
index a64dfb12..a3dde1b7 100644
--- a/src/io/buffer.nim
+++ b/src/io/buffer.nim
@@ -182,14 +182,23 @@ func width(line: seq[FlexibleCell]): int =
   for c in line:
     result += c.rune.width()
 
-func cellWidthOverlap*(buffer: Buffer, x: int, y: int): int =
+func cellOrigin(buffer: Buffer, x: int, y: int): int =
   let row = y * buffer.width
   var ox = x
   while buffer.display[row + ox].runes.len == 0 and ox > 0:
     dec ox
+  return ox
+
+func currentCellOrigin(buffer: Buffer): int =
+  return buffer.cellOrigin(buffer.cursorx - buffer.fromx, buffer.cursory - buffer.fromy)
+
+func cellWidthOverlap*(buffer: Buffer, x: int, y: int): int =
+  let ox = buffer.cellOrigin(x, y)
+  let row = y * buffer.width
   return buffer.display[row + ox].runes.width()
 
-func currentCellWidth*(buffer: Buffer): int = buffer.cellWidthOverlap(buffer.cursorx - buffer.fromx, buffer.cursory - buffer.fromy)
+func currentCellWidth*(buffer: Buffer): int =
+  return buffer.cellWidthOverlap(buffer.cursorx - buffer.fromx, buffer.cursory - buffer.fromy)
 
 func currentLineWidth*(buffer: Buffer): int =
   if buffer.cursory > buffer.lines.len:
@@ -299,21 +308,22 @@ proc cursorUp*(buffer: Buffer) =
 
 proc cursorRight*(buffer: Buffer) =
   let cellwidth = buffer.currentCellWidth()
+  let cellorigin = buffer.currentCellOrigin()
   let lw = buffer.currentLineWidth()
   if buffer.cursorx < lw - 1:
-    buffer.cursorx = min(lw - 1, buffer.cursorx + cellwidth)
+    buffer.cursorx = min(lw - 1, cellorigin + cellwidth)
     buffer.xend = buffer.cursorx
     if buffer.cursorx - buffer.width >= buffer.fromx:
       inc buffer.fromx
       buffer.redraw = true
 
 proc cursorLeft*(buffer: Buffer) =
-  let cellwidth = buffer.currentCellWidth()
+  let cellorigin = buffer.currentCellOrigin()
   if buffer.fromx > buffer.cursorx:
     buffer.fromx = buffer.cursorx
     buffer.redraw = true
   elif buffer.cursorx > 0:
-    buffer.cursorx = max(0, buffer.cursorx - cellwidth)
+    buffer.cursorx = max(0, cellorigin - 1)
     if buffer.fromx > buffer.cursorx:
       buffer.fromx = buffer.cursorx
       buffer.redraw = true
@@ -644,12 +654,12 @@ proc renderDocument*(buffer: Buffer) =
     if box of CSSInlineBox:
       let inline = CSSInlineBox(box)
       var i = 0
-      eprint "NEW BOX"
+      eprint "NEW BOX", inline.context.conty
       for line in inline.content:
         eprint line
         buffer.setRowBox(line)
     else:
-      eprint "BLOCK"
+      eprint "BLOCK h", box.height
 
     var i = box.children.len - 1
     while i >= 0:
diff --git a/src/layout/box.nim b/src/layout/box.nim
index a36bb424..bacaa1b1 100644
--- a/src/layout/box.nim
+++ b/src/layout/box.nim
@@ -2,6 +2,8 @@ import unicode
 
 import utils/twtstr
 import io/cell
+import types/enums
+import css/style
 
 type
   CSSRect* = object
@@ -14,10 +16,20 @@ type
   CSSBoxObj = object of RootObj
     x*: int
     y*: int
-    fromx*: int
     width*: int
     height*: int
+    bh*: int
+    bw*: int
     children*: seq[CSSBox]
+    context*: FormatContext
+
+  FormatContext* = ref object
+    context*: FormatContextType
+    fromx*: int
+    fromy*: int
+    conty*: bool
+    whitespace*: bool
+    cssvalues*: CSSComputedValues
 
   CSSRowBox* = object
     x*: int
diff --git a/src/layout/layout.nim b/src/layout/layout.nim
index 96f27c9c..fe96b416 100644
--- a/src/layout/layout.nim
+++ b/src/layout/layout.nim
@@ -1,4 +1,5 @@
 import unicode
+import options
 
 import layout/box
 import types/enums
@@ -8,252 +9,143 @@ import io/buffer
 import io/cell
 import utils/twtstr
 
-#type
-#  Frame = object
-#    node: Node
-#    maxwidth: int
-#    maxheight: int
-#    box: CSSBox
-#    context: FormatContext
+func newContext*(box: CSSBox): FormatContext =
+  new(result)
+  result.fromx = box.x
+  result.whitespace = true
 
+func newBlockBox*(parent: CSSBox): CSSBlockBox =
+  new(result)
+  result.x = parent.x
+  if parent.context.conty:
+    inc parent.height
+    parent.context.conty = false
+  result.y = parent.y + parent.height
 
-#proc addText(state: var LayoutState, frame: var Frame, text: Text) =
-#  let maxwidth = frame.maxwidth
-#  let fromx = state.fromx
-#  let x = state.x
-#  if maxwidth == 0: return
-#  if not (frame.box of CSSInlineBox): return
-#  var box = CSSInlineBox(frame.box)
-#  var r: Rune
-#  var i = 0
-#  var rowbox: CSSRowBox
-#  if fromx > x:
-#    rowbox = CSSRowBox(x: state.fromx, y: state.y)
-#    let m = maxwidth - fromx + x
-#    var w = 0
-#    var lf = false
-#    while i < text.data.len:
-#      let pi = i
-#      fastRuneAt(text.data, i, r)
-#      let rw = r.width()
-#      if rw + w > m:
-#        i = pi
-#        inc state.y
-#        lf = true
-#        break
-#      else:
-#        if r.isWhitespace():
-#          if not state.whitespace:
-#            state.whitespace = true
-#            rowbox.runes.add(Rune(' '))
-#            inc rowbox.width
-#            w += rw
-#        else:
-#          if state.whitespace:
-#            state.whitespace = false
-#          rowbox.runes.add(r)
-#          inc rowbox.width
-#          w += rw
-#
-#    box.content.add(rowbox)
-#    if lf:
-#      state.fromx = 0
-#    else:
-#      state.fromx += rowbox.width
-#
-#  if i < text.data.len:
-#    rowbox = CSSRowBox(x: state.x, y: state.y)
-#    var w = 0
-#    while i < text.data.len:
-#      let pi = i
-#      fastRuneAt(text.data, i, r)
-#      let rw = r.width()
-#      if rw + w > maxwidth:
-#        i = pi
-#        w = 0
-#        box.content.add(rowbox)
-#        state.fromx += rowbox.width
-#        inc state.y
-#        rowbox = CSSRowBox(x: state.x, y: state.y)
-#      else:
-#        rowbox.runes.add(r)
-#        inc rowbox.width
-#        w += rw
-#
-#  if rowbox.width > 0:
-#    box.content.add(rowbox)
-#    state.fromx += rowbox.width
+  result.width = parent.width
+  result.context = newContext(parent)
 
-#proc alignBoxes*(buffer: Buffer) =
-#  #buffer.rootbox = buffer.document.root.genBox(buffer.width, buffer.height)
-#  buffer.rootbox = CSSBlockBox(x: 0, y: 0, width: buffer.width, height: buffer.height)
-#  buffer.rootbox.children.add(CSSInlineBox(x: 0, y: 0, width: buffer.width, height: buffer.height))
-#  var x = 0
-#  var stack: seq[Frame]
-#  var state: LayoutState
-#  stack.add(Frame(node: buffer.document.root, box: buffer.rootbox, maxwidth: 80, context: CONTEXT_BLOCK))
-#  while stack.len > 0:
-#    var frame = stack.pop()
-#    let node = frame.node
-#
-#    case frame.context 
-#    of CONTEXT_BLOCK:
-#      case node.nodeType
-#      of TEXT_NODE: #anonymous
-#        discard
-#      of ELEMENT_NODE: #new formatting context
-#        let elem = Element(node)
-#        case elem.cssvalues[RULE_DISPLAY].display
-#        of DISPLAY_BLOCK:
-#          let parent = frame.box
-#          state.whitespace = false
-#          frame.box = CSSBlockBox(x: state.x, y: state.y, width: frame.maxwidth)
-#          parent.children.add(frame.box)
-#          frame.context = CONTEXT_BLOCK
-#        of DISPLAY_INLINE:
-#          let parent = frame.box
-#          frame.box = CSSInlineBox(x: state.x, y: state.y, width: frame.maxwidth)
-#          parent.children.add(frame.box)
-#          frame.context = CONTEXT_INLINE
-#        of DISPLAY_NONE: continue
-#        else: discard #TODO
-#      else: discard
-#    of CONTEXT_INLINE:
-#      case node.nodeType
-#      of TEXT_NODE: #just add to existing inline box no problem
-#        let text = Text(node)
-#        state.addText(frame, text)
-#      of ELEMENT_NODE:
-#        let elem = Element(node)
-#        case elem.cssvalues[RULE_DISPLAY].display
-#        of DISPLAY_NONE: continue
-#        else:
-#          #ok this is the difficult part (TODO)
-#          #NOTE we're assuming the parent isn't inline, if it is we're screwed
-#          #basically what we have to do is:
-#          #* create a new anonymous BLOCK box
-#          #* for every previous INLINE box in parent (BLOCK) box, do:
-#          #*  copy INLINE box into new anonymous BLOCK box
-#          #*  delete INLINE box
-#          #* create a new BLOCK box (this one)
-#          #* NOTE after our BLOCK there's a continuation of the last INLINE box
-#
-#          eprint "?????"
-#      else: discard
-#
-#    var i = node.childNodes.len - 1
-#    while i >= 0:
-#      let child = node.childNodes[i]
-#      stack.add(Frame(node: child, box: frame.box, maxwidth: frame.maxwidth, context: frame.context))
-#      dec i
+func newInlineBox*(parent: CSSBox): CSSInlineBox =
+  assert parent != nil
+  new(result)
+  result.x = parent.x
+  result.y = parent.y + parent.height
 
-type
-  LayoutState = object
-    x: int
-    y: int
-    fromx: int
-    whitespace: bool
-    context: FormatContext
+  result.width = parent.width
+  result.context = parent.context
+  if result.context == nil:
+    result.context = newContext(parent)
 
-  FormatContext = enum
-    CONTEXT_BLOCK, CONTEXT_INLINE
+proc processInlineBox(parent: CSSBox, str: string): CSSBox =
+  var ibox: CSSInlineBox
+  var use_parent = false
+  if parent of CSSInlineBox:
+    ibox = CSSInlineBox(parent)
+    use_parent = true
+  else:
+    ibox = newInlineBox(parent)
 
-proc addChild(parent: var CSSBox, box: CSSBox) =
+  if str.len == 0:
+    return
+
+  var i = 0
+  var rowi = 0
+  var fromx = ibox.context.fromx
+  var rowbox = CSSRowBox(x: fromx, y: ibox.y + rowi)
+  var r: Rune
+  while i < str.len:
+    fastRuneAt(str, i, r)
+    if rowbox.width + r.width() > ibox.width:
+      ibox.content.add(rowbox)
+      inc rowi
+      fromx = ibox.x
+      ibox.context.whitespace = true
+      ibox.context.conty = false
+      rowbox = CSSRowBox(x: ibox.x, y: ibox.y + rowi)
+    if r.isWhitespace():
+      if ibox.context.whitespace:
+        continue
+      else:
+        ibox.context.whitespace = true
+    else:
+      ibox.context.whitespace = false
+    rowbox.width += r.width()
+    rowbox.runes.add(r)
+  if rowbox.runes.len > 0:
+    ibox.content.add(rowbox)
+    ibox.context.fromx = fromx + rowbox.width
+    ibox.context.conty = true
+
+  ibox.height += rowi
+  if use_parent:
+    return nil
+  return ibox
+
+proc processElemBox(parent: CSSBox, elem: Element): CSSBox =
+  case elem.cssvalues[RULE_DISPLAY].display
+  of DISPLAY_BLOCK:
+    result = newBlockBox(parent)
+    result.context.cssvalues = elem.cssvalues
+  of DISPLAY_INLINE:
+    result = newInlineBox(parent)
+    result.context.cssvalues = elem.cssvalues
+  of DISPLAY_NONE:
+    return nil
+  else:
+    return nil
+
+proc add(parent: var CSSBox, box: CSSBox) =
   if box == nil:
     return
+  if box of CSSBlockBox:
+    parent.context.fromx = 0
+    parent.context.whitespace = true
+    if box.context.conty:
+      inc box.height
   parent.height += box.height
   parent.children.add(box)
 
+proc processPseudoBox(parent: CSSBox, cssvalues: CSSComputedValues): CSSBox =
+  case cssvalues[RULE_DISPLAY].display
+  of DISPLAY_BLOCK:
+    result = newBlockBox(parent)
+    result.context.cssvalues = cssvalues
+    result.add(processInlineBox(parent, $cssvalues[RULE_CONTENT].content)) 
+  of DISPLAY_INLINE:
+    result = processInlineBox(parent, $cssvalues[RULE_CONTENT].content)
+  of DISPLAY_NONE:
+    return nil
+  else:
+    return nil
+
 proc processNode(parent: CSSBox, node: Node): CSSBox =
   case node.nodeType
   of ELEMENT_NODE:
     let elem = Element(node)
-    var box: CSSBox
-    case elem.cssvalues[RULE_DISPLAY].display
-    of DISPLAY_BLOCK:
-      box = CSSBlockBox(x: parent.x, y: parent.y + parent.height, width: parent.width)
-    of DISPLAY_INLINE:
-      #TODO split this into its own thing
-      #TODO also rethink this bc it isn't very great
-      #TODO like, it doesn't work
-      var fromx = parent.x
-      if parent.children.len > 0 and parent.children[^1] of CSSInlineBox:
-        let sib = CSSInlineBox(parent.children[^1])
-        if sib.content.len > 0:
-          fromx = sib.content[^1].x + sib.content[^1].width
-        else:
-          eprint "???"
-      elif parent of CSSInlineBox:
-        let sib = CSSInlineBox(parent)
-        if sib.content.len > 0:
-          fromx = sib.content[^1].x + sib.content[^1].width
-        else:
-          eprint "???"
-      box = CSSInlineBox(x: parent.x, y: parent.y + parent.height, width: parent.width)
-      CSSInlineBox(box).content.add(CSSRowBox(x: fromx, y: box.y))
-    of DISPLAY_NONE:
-      return nil
-    else:
-      return nil
 
-    for child in elem.childNodes:
-      CSSBox(box).addChild(processNode(box, child))
-    return box
-  of TEXT_NODE:
-    let text = Text(node)
-    #TODO not always anonymous
-    var ibox = CSSInlineBox(x: parent.x, y: parent.y + parent.height, width: parent.width)
-    var ws = true #TODO doesn't always start with newline
-    let str = text.data
-
-    if text.data.len == 0:
+    result = processElemBox(parent, elem)
+    if result == nil:
       return
 
-    #TODO ok we'll have to rethink this methinks
-    var fromx = ibox.x
-    if parent.children.len > 0 and parent.children[^1] of CSSInlineBox:
-      let sib = CSSInlineBox(parent.children[^1])
-      if sib.content.len > 0:
-        fromx = sib.content[^1].x + sib.content[^1].width
-      else:
-        eprint "???"
-    elif parent of CSSInlineBox:
-      let sib = CSSInlineBox(parent)
-      if sib.content.len > 0:
-        fromx = sib.content[^1].x + sib.content[^1].width
-      else:
-        eprint "???"
+    if elem.cssvalues_before.isSome:
+      let bbox = processPseudoBox(parent, elem.cssvalues_before.get)
+      if bbox != nil:
+        result.add(bbox)
 
-    var i = 0
-    var w = 0
-    var rowi = 0
-    var rowbox = CSSRowBox(x: fromx, y: ibox.y + rowi)
-    var r: Rune
-    while i < text.data.len:
-      fastRuneAt(text.data, i, r)
-      if w + r.width() > ibox.width:
-        ibox.content.add(rowbox)
-        inc rowi
-        rowbox = CSSRowBox(x: ibox.x, y: ibox.y + rowi)
-      if r.isWhitespace():
-        if ws:
-          continue
-        else:
-          ws = true
-      else:
-        ws = false
-      rowbox.width += r.width()
-      rowbox.runes.add(r)
-    if rowbox.runes.len > 0:
-      ibox.content.add(rowbox)
-      inc rowi
+    for child in elem.childNodes:
+      result.add(processNode(result, child))
 
-    ibox.height += rowi
-    return ibox
+    if elem.cssvalues_after.isSome:
+      let abox = processPseudoBox(parent, elem.cssvalues_after.get)
+      if abox != nil:
+        result.add(abox)
+  of TEXT_NODE:
+    let text = Text(node)
+    return processInlineBox(parent, text.data)
   else: discard
-  return nil
 
 proc alignBoxes*(buffer: Buffer) =
   buffer.rootbox = CSSBlockBox(x: 0, y: 0, width: buffer.width, height: 0)
+  buffer.rootbox.context = newContext(buffer.rootbox)
   for child in buffer.document.root.childNodes:
-    buffer.rootbox.addChild(processNode(buffer.rootbox, child))
+    buffer.rootbox.add(processNode(buffer.rootbox, child))
diff --git a/src/types/enums.nim b/src/types/enums.nim
index e257e579..3d79091b 100644
--- a/src/types/enums.nim
+++ b/src/types/enums.nim
@@ -84,6 +84,9 @@ type
   DrawInstructionType* = enum
     DRAW_TEXT, DRAW_GOTO, DRAW_FGCOLOR, DRAW_BGCOLOR, DRAW_STYLE, DRAW_RESET
 
+  FormatContextType* = enum
+    CONTEXT_BLOCK, CONTEXT_INLINE
+
 const SelfClosingTagTypes* = {
   TAG_LI, TAG_P
 }