about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2021-08-04 17:54:27 +0200
committerbptato <nincsnevem662@gmail.com>2021-08-04 17:54:27 +0200
commitcaad7b577162a73524277a943050493c489bfb59 (patch)
treea149be40ceebc4303d94c797d2a62ef62b1a42a0 /src
parent34b023515599bc746c10c597467ecb07f53c49fe (diff)
downloadchawan-caad7b577162a73524277a943050493c489bfb59.tar.gz
More css stuff
Diffstat (limited to 'src')
-rw-r--r--src/css/cssparser.nim16
-rw-r--r--src/css/selector.nim61
-rw-r--r--src/css/style.nim193
-rw-r--r--src/html/dom.nim128
-rw-r--r--src/html/htmlparser.nim68
-rw-r--r--src/io/buffer.nim (renamed from src/buffer.nim)18
-rw-r--r--src/io/display.nim4
-rw-r--r--src/io/twtio.nim11
-rw-r--r--src/main.nim13
-rw-r--r--src/types/enums.nim41
10 files changed, 325 insertions, 228 deletions
diff --git a/src/css/cssparser.nim b/src/css/cssparser.nim
index 5ecb470a..5e76bcc0 100644
--- a/src/css/cssparser.nim
+++ b/src/css/cssparser.nim
@@ -6,6 +6,8 @@ import unicode
 import streams
 import math
 import options
+import sequtils
+import sugar
 
 import ../io/twtio
 
@@ -464,6 +466,8 @@ func curr(state: CSSParseState): CSSParsedItem =
 func has(state: CSSParseState): bool =
   return state.at < state.tokens.len
 
+proc consumeComponentValue(state: var CSSParseState): CSSComponentValue
+
 proc consumeSimpleBlock(state: var CSSParseState): CSSSimpleBlock =
   state.reconsume()
   let t = CSSToken(state.consume())
@@ -483,11 +487,10 @@ proc consumeSimpleBlock(state: var CSSParseState): CSSSimpleBlock =
       if t == CSS_LBRACE_TOKEN or t == CSS_LBRACKET_TOKEN or t == CSS_LPAREN_TOKEN:
         result.value.add(state.consumeSimpleBlock())
       else:
-        result.value.add(CSSComponentValue(t))
+        state.reconsume()
+        result.value.add(state.consumeComponentValue())
   return result
 
-proc consumeComponentValue*(state: var CSSParseState): CSSComponentValue
-
 proc consumeFunction(state: var CSSParseState): CSSFunction =
   let t = (CSSToken)state.consume()
   result = CSSFunction(name: t.value)
@@ -691,6 +694,13 @@ proc parseCSSDeclaration*(inputStream: Stream): CSSDeclaration =
 proc parseListOfDeclarations(state: var CSSParseState): seq[CSSParsedItem] =
   return state.consumeListOfDeclarations()
 
+proc parseCSSListOfDeclarations*(cvals: seq[CSSComponentValue]): seq[CSSParsedItem] =
+  var state = CSSParseState()
+  state.tokens = collect(newSeq):
+    for cval in cvals:
+      CSSParsedItem(cval)
+  return state.consumeListOfDeclarations()
+
 proc parseCSSListOfDeclarations*(inputStream: Stream): seq[CSSParsedItem] =
   var state = CSSParseState()
   state.tokens = tokenizeCSS(inputStream)
diff --git a/src/css/selector.nim b/src/css/selector.nim
index 1ca417dd..a474bd8f 100644
--- a/src/css/selector.nim
+++ b/src/css/selector.nim
@@ -3,7 +3,7 @@ import unicode
 import ../types/enums
 import ../types/tagtypes
 
-import cssparser
+import ./cssparser
 
 type
   SelectorType* = enum
@@ -52,10 +52,65 @@ proc setLen*(sellist: SelectorList, i: int) = sellist.sels.setLen(i)
 proc `[]`*(sellist: SelectorList, i: int): Selector = sellist.sels[i]
 proc len*(sellist: SelectorList): int = sellist.sels.len
 
+func getSpecificity(sel: Selector): int =
+  case sel.t
+  of ID_SELECTOR:
+    result += 1000000
+  of CLASS_SELECTOR, ATTR_SELECTOR, PSEUDO_SELECTOR:
+    result += 1000
+  of TYPE_SELECTOR, PSELEM_SELECTOR:
+    result += 1
+  of FUNC_SELECTOR:
+    case sel.name
+    of "is":
+      var best = 0
+      for child in sel.selectors.sels:
+        let s = getSpecificity(child)
+        if s > best:
+          best = s
+      result += best
+    of "not":
+      for child in sel.selectors.sels:
+        result += getSpecificity(child)
+    else: discard
+  of UNIVERSAL_SELECTOR:
+    discard
+
+func getSpecificity*(sels: SelectorList): int =
+  for sel in sels.sels:
+    result += getSpecificity(sel)
+
+func optimizeSelectorList*(selectors: SelectorList): SelectorList =
+  new(result)
+  #pass 1: check for invalid sequences
+  var i = 1
+  while i < selectors.len:
+    let sel = selectors[i]
+    if sel.t == TYPE_SELECTOR or sel.t == UNIVERSAL_SELECTOR:
+      return SelectorList()
+    inc i
+
+  #pass 2: move selectors in combination
+  if selectors.len > 1:
+    var i = 0
+    var slow = SelectorList()
+    if selectors[0].t == UNIVERSAL_SELECTOR:
+      inc i
+
+    while i < selectors.len:
+      if selectors[i].t in {ATTR_SELECTOR, PSEUDO_SELECTOR, PSELEM_SELECTOR}:
+        slow.add(selectors[i])
+      else:
+        result.add(selectors[i])
+      inc i
+
+    result.add(slow)
+  else:
+    result.add(selectors[0])
+
 proc parseSelectorToken(state: var SelectorParser, csstoken: CSSToken) =
   case csstoken.tokenType
   of CSS_IDENT_TOKEN:
-    var sel: Selector
     case state.query
     of QUERY_CLASS:
       state.selectors[^1].add(Selector(t: CLASS_SELECTOR, class: $csstoken.value))
@@ -120,7 +175,7 @@ proc parseSelectorSimpleBlock(state: var SelectorParser, cssblock: CSSSimpleBloc
 
 proc parseSelectorFunction(state: var SelectorParser, cssfunction: CSSFunction) =
   case $cssfunction.name
-  of "not":
+  of "not", "is":
     if state.query != QUERY_PSEUDO:
       return
     state.query = QUERY_TYPE
diff --git a/src/css/style.nim b/src/css/style.nim
index 56e6b00b..d98c1f6c 100644
--- a/src/css/style.nim
+++ b/src/css/style.nim
@@ -1,4 +1,3 @@
-import streams
 import unicode
 import terminal
 import tables
@@ -9,7 +8,7 @@ import ../utils/twtstr
 
 import ../types/enums
 
-import cssparser
+import ./cssparser
 
 type
   CSSLength* = object
@@ -36,13 +35,14 @@ type
     centered*: bool
     display*: DisplayType
     bold*: bool
-    italic*: bool
+    fontStyle*: CSSFontStyle
     underscore*: bool
     islink*: bool
     selected*: bool
     indent*: int
     color*: CSSColor
     position*: CSSPosition
+    content*: seq[Rune]
 
   CSSCanvas* = object
     rootBox*: CSSBox
@@ -63,13 +63,32 @@ type
     paddingEdge*: CSSRect
     borderEdge*: CSSRect
     marginEdge*: CSSRect
-    color*: CSSColor
     props*: CSS2Properties
     content*: seq[Rune]
     dispcontent*: string
     children*: seq[CSSBox]
 
   CSSColor* = tuple[r: uint8, g: uint8, b: uint8, a: uint8]
+  
+  CSSComputedValue* = object of RootObj
+    case t: CSSRuleType
+    of RULE_ALL: discard
+    of RULE_COLOR:
+      color: CSSColor
+    of RULE_MARGIN, RULE_MARGIN_TOP, RULE_MARGIN_LEFT, RULE_MARGIN_RIGHT,
+       RULE_MARGIN_BOTTOM:
+      length: CSSLength
+    of RULE_FONT_STYLE:
+      fontStyle: CSSFontStyle
+    of RULE_DISPLAY:
+      display: DisplayType
+    of RULE_CONTENT:
+      content: seq[Rune]
+
+  CSSSpecifiedValue* = object of CSSComputedValue
+    hasGlobalValue: bool
+    globalValue: CSSGlobalValueType
+
 
 func `+`(a: CSSRect, b: CSSRect): CSSRect =
   result.x1 = a.x1 + b.x1
@@ -82,7 +101,7 @@ proc `+=`(a: var CSSRect, b: CSSRect) =
 
 func cells(l: CSSLength): int =
   case l.unit
-  of EM_UNIT:
+  of UNIT_EM:
     return int(l.num)
   else:
     #TODO
@@ -112,22 +131,22 @@ const defaultColor = (0xffu8, 0xffu8, 0xffu8, 0x00u8)
 
 func cssLength(val: float64, unit: string): CSSLength =
   case unit
-  of "%": return CSSLength(num: val, unit: PERC_UNIT)
-  of "cm": return CSSLength(num: val, unit: CM_UNIT)
-  of "mm": return CSSLength(num: val, unit: MM_UNIT)
-  of "in": return CSSLength(num: val, unit: IN_UNIT)
-  of "px": return CSSLength(num: val, unit: PX_UNIT)
-  of "pt": return CSSLength(num: val, unit: PT_UNIT)
-  of "pc": return CSSLength(num: val, unit: PC_UNIT)
-  of "em": return CSSLength(num: val, unit: EM_UNIT)
-  of "ex": return CSSLength(num: val, unit: EX_UNIT)
-  of "ch": return CSSLength(num: val, unit: CH_UNIT)
-  of "rem": return CSSLength(num: val, unit: REM_UNIT)
-  of "vw": return CSSLength(num: val, unit: VW_UNIT)
-  of "vh": return CSSLength(num: val, unit: VH_UNIT)
-  of "vmin": return CSSLength(num: val, unit: VMIN_UNIT)
-  of "vmax": return CSSLength(num: val, unit: VMAX_UNIT)
-  else: return CSSLength(num: 0, unit: EM_UNIT)
+  of "%": return CSSLength(num: val, unit: UNIT_PERC)
+  of "cm": return CSSLength(num: val, unit: UNIT_CM)
+  of "mm": return CSSLength(num: val, unit: UNIT_MM)
+  of "in": return CSSLength(num: val, unit: UNIT_IN)
+  of "px": return CSSLength(num: val, unit: UNIT_PX)
+  of "pt": return CSSLength(num: val, unit: UNIT_PT)
+  of "pc": return CSSLength(num: val, unit: UNIT_PC)
+  of "em": return CSSLength(num: val, unit: UNIT_EM)
+  of "ex": return CSSLength(num: val, unit: UNIT_EX)
+  of "ch": return CSSLength(num: val, unit: UNIT_CH)
+  of "rem": return CSSLength(num: val, unit: UNIT_REM)
+  of "vw": return CSSLength(num: val, unit: UNIT_VW)
+  of "vh": return CSSLength(num: val, unit: UNIT_VH)
+  of "vmin": return CSSLength(num: val, unit: UNIT_VMIN)
+  of "vmax": return CSSLength(num: val, unit: UNIT_VMAX)
+  else: return CSSLength(num: 0, unit: UNIT_EM)
 
 func cssColor*(d: CSSDeclaration): CSSColor =
   if d.value.len > 0:
@@ -165,7 +184,7 @@ func cssColor*(d: CSSDeclaration): CSSColor =
       else:
         eprint "else", tok.tokenType
         return defaultColor
-    elif d of CSSFunction:
+    elif d.value[0] of CSSFunction:
       let f = CSSFunction(d.value[0])
       eprint "func", f.name
       #todo calc etc (cssnumber function or something)
@@ -209,11 +228,11 @@ func cssLength(d: CSSDeclaration): CSSLength =
       return cssLength(tok.nvalue, $tok.unit)
     of CSS_IDENT_TOKEN:
       if $tok.value == "auto":
-        return CSSLength(num: 0, unit: EM_UNIT, auto: true)
+        return CSSLength(auto: true)
     else:
-      return CSSLength(num: 0, unit: EM_UNIT)
+      return CSSLength(num: 0, unit: UNIT_EM)
 
-  return CSSLength(num: 0, unit: EM_UNIT)
+  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
@@ -228,35 +247,101 @@ func termColor*(style: CSS2Properties): ForegroundColor =
   else:
     return fgWhite
 
-proc applyProperties*(box: CSSBox, s: string) =
-  let decls = parseCSSListOfDeclarations(newStringStream(s))
-  if box.props == nil:
-    box.props = CSS2Properties()
-  let props = box.props
-
-  for item in decls:
-    if item of CSSDeclaration:
-      let d = CSSDeclaration(item)
-      case $d.name
-      of "color":
-        props.color = cssColor(d)
-        eprint props.color #TODO
-      of "margin":
-        let l = cssLength(d)
-        props.margintop = l
-        props.marginbottom = l
-        props.marginleft = l
-        props.marginright = l
-      of "margin-top":
-        props.margintop = cssLength(d)
-      of "margin-left":
-        props.marginleft = cssLength(d)
-      of "margin-right":
-        props.marginright = cssLength(d)
-      of "margin-bottom":
-        props.marginbottom = cssLength(d)
-      else:
-        printc(d) #TODO
+func isToken(d: CSSDeclaration): bool = d.value.len > 0 and d.value[0] of CSSToken
+func getToken(d: CSSDeclaration): CSSToken = (CSSToken)d.value[0]
+
+func cssString(d: CSSDeclaration): seq[Rune] =
+  if isToken(d):
+    let tok = getToken(d)
+    case tok.tokenType
+    of CSS_IDENT_TOKEN, CSS_STRING_TOKEN:
+      return tok.value
+    else: return
+
+func cssDisplay(d: CSSDeclaration): DisplayType =
+  if isToken(d):
+    let tok = getToken(d)
+    if tok.tokenType == CSS_IDENT_TOKEN:
+      case $tok.value
+      of "block": return DISPLAY_BLOCK
+      of "inline": return DISPLAY_INLINE
+      of "inline-block": return DISPLAY_INLINE_BLOCK
+      of "list-item": return DISPLAY_LIST_ITEM
+      of "table-column": return DISPLAY_TABLE_COLUMN
+      of "none": return DISPLAY_NONE
+      else: return DISPLAY_INLINE
+  return DISPLAY_INLINE
+
+func cssFontStyle(d: CSSDeclaration): CSSFontStyle =
+  if isToken(d):
+    let tok = getToken(d)
+    if tok.tokenType == CSS_IDENT_TOKEN:
+      case $tok.value
+      of "normal": return FONTSTYLE_NORMAL
+      of "italic": return FONTSTYLE_ITALIC
+      of "oblique": return FONTSTYLE_OBLIQUE
+      else: return FONTSTYLE_NORMAL
+  return FONTSTYLE_NORMAL
+
+func getSpecifiedValue*(d: CSSDeclaration): CSSSpecifiedValue =
+  case $d.name
+  of "color":
+    return CSSSpecifiedValue(t: RULE_COLOR, color: cssColor(d))
+  of "margin":
+    return CSSSpecifiedValue(t: RULE_MARGIN, length: cssLength(d))
+  of "margin-top":
+    return CSSSpecifiedValue(t: RULE_MARGIN_TOP, length: cssLength(d))
+  of "margin-left":
+    return CSSSpecifiedValue(t: RULE_MARGIN_LEFT, length: cssLength(d))
+  of "margin-bottom":
+    return CSSSpecifiedValue(t: RULE_MARGIN_BOTTOM, length: cssLength(d))
+  of "margin-right":
+    return CSSSpecifiedValue(t: RULE_MARGIN_RIGHT, length: cssLength(d))
+  of "font-style":
+    return CSSSpecifiedValue(t: RULE_FONT_STYLE, fontStyle: cssFontStyle(d))
+  of "display":
+    return CSSSpecifiedValue(t: RULE_DISPLAY, display: cssDisplay(d))
+  of "content":
+    return CSSSpecifiedValue(t: RULE_CONTENT, content: cssString(d))
+
+func getComputedValue*(rule: CSSSpecifiedValue): CSSComputedValue =
+  let inherit = rule.hasGlobalValue and (rule.globalValue == VALUE_INHERIT)
+  let initial = rule.hasGlobalValue and (rule.globalValue == VALUE_INHERIT)
+  let unset = rule.hasGlobalValue and (rule.globalValue == VALUE_INHERIT)
+  let revert = rule.hasGlobalValue and (rule.globalValue == VALUE_INHERIT)
+  #case rule.t
+  #of RULE_COLOR:
+  #  return CSSComputedValue(t: rule.t, 
+
+func getComputedValue*(d: CSSDeclaration): CSSComputedValue =
+  return getComputedValue(getSpecifiedValue(d))
+
+proc applyProperty*(props: CSS2Properties, d: CSSDeclaration) =
+  case $d.name
+  of "color":
+    props.color = cssColor(d)
+  of "margin":
+    let l = cssLength(d)
+    props.margintop = l
+    props.marginbottom = l
+    props.marginleft = l
+    props.marginright = l
+  of "margin-top":
+    props.margintop = cssLength(d)
+  of "margin-left":
+    props.marginleft = cssLength(d)
+  of "margin-right":
+    props.marginright = cssLength(d)
+  of "margin-bottom":
+    props.marginbottom = cssLength(d)
+  of "font-style":
+    props.fontStyle = cssFontStyle(d)
+  of "display":
+    props.display = cssDisplay(d)
+  of "content":
+    props.content = cssString(d)
+  else:
+    printc(d) #TODO
 
 func getLength(s: seq[Rune], start: int, wlimit: int): tuple[wrap: bool, len: int, width: int] =
   var len = 0
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 74ebe5ea..1a393134 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -6,13 +6,13 @@ import tables
 import streams
 import sequtils
 import sugar
+import algorithm
 
 import ../css/style
 import ../css/cssparser
 import ../css/selector
 
 import ../types/enums
-import ../types/tagtypes
 
 import ../utils/twtstr
 
@@ -46,6 +46,7 @@ type
     width*: int
     height*: int
     hidden*: bool
+    box*: CSSBox
 
   Attr* = ref AttrObj
   AttrObj = object of NodeObj
@@ -89,7 +90,8 @@ type
     id*: string
     classList*: seq[string]
     attributes*: Table[string, Attr]
-    box*: CSSBox
+    style*: CSS2Properties
+    cssvalues*: seq[CSSComputedValue]
 
   HTMLElement* = ref HTMLElementObj
   HTMLElementObj = object of ElementObj
@@ -159,8 +161,8 @@ func nodeAttr*(node: Node): HtmlElement =
 
 func getStyle*(node: Node): CSS2Properties =
   case node.nodeType
-  of TEXT_NODE: return node.parentElement.box.props
-  of ELEMENT_NODE: return Element(node).box.props
+  of TEXT_NODE: return node.parentElement.style
+  of ELEMENT_NODE: return Element(node).style
   else: assert(false)
 
 func displayed*(node: Node): bool =
@@ -260,11 +262,11 @@ proc getRawText*(htmlNode: Node): string =
     #eprint "char data", chardata.data
     if htmlNode.parentElement != nil and htmlNode.parentElement.tagType != TAG_PRE:
       result = chardata.data.remove("\n")
-      if unicode.strip(result).runeLen() > 0:
-        if htmlNode.getStyle().display != DISPLAY_INLINE:
-          result = unicode.strip(result)
-      else:
-        result = ""
+      #if unicode.strip(result).runeLen() > 0:
+      #  if htmlNode.getStyle().display != DISPLAY_INLINE:
+      #    result = unicode.strip(result)
+      #else:
+      #  result = ""
     else:
       result = unicode.strip(chardata.data)
     if htmlNode.parentElement != nil and htmlNode.parentElement.tagType == TAG_OPTION:
@@ -290,7 +292,7 @@ func getFmtText*(node: Node): seq[string] =
 
       if style.bold:
         result = result.ansiStyle(styleBright).ansiReset()
-      if style.italic:
+      if style.fontStyle == FONTSTYLE_ITALIC or style.fontStyle == FONTSTYLE_OBLIQUE:
         result = result.ansiStyle(styleItalic).ansiReset()
       if style.underscore:
         result = result.ansiStyle(styleUnderscore).ansiReset()
@@ -307,10 +309,6 @@ func newComment*(): Comment =
   new(result)
   result.nodeType = COMMENT_NODE
 
-func newBox*(element: HTMLElement): CSSBox =
-  new(result)
-  result.props = CSS2Properties()
-
 func newHtmlElement*(tagType: TagType): HTMLElement =
   case tagType
   of TAG_INPUT:
@@ -332,7 +330,7 @@ func newHtmlElement*(tagType: TagType): HTMLElement =
 
   result.nodeType = ELEMENT_NODE
   result.tagType = tagType
-  result.box = result.newBox()
+  result.style = CSS2Properties()
 
 func newDocument*(): Document =
   new(result)
@@ -414,7 +412,6 @@ func selectElems(document: Document, sel: Selector): seq[Element] =
     return document.class_elements[sel.class]
   of UNIVERSAL_SELECTOR:
     return document.all_elements
-  #TODO: following selectors are rather inefficient
   of ATTR_SELECTOR:
     return document.all_elements.filter((elem) => attrSelectorMatches(elem, sel))
   of PSEUDO_SELECTOR:
@@ -422,38 +419,13 @@ func selectElems(document: Document, sel: Selector): seq[Element] =
   of PSELEM_SELECTOR:
     return document.all_elements.filter((elem) => pseudoElemSelectorMatches(elem, sel))
   of FUNC_SELECTOR:
-    if sel.name == "not":
+    case sel.name
+    of "not":
       return document.all_elements.filter((elem) => not selectorsMatch(elem, sel.selectors))
+    of "is", "where":
+      return document.all_elements.filter((elem) => selectorsMatch(elem, sel.selectors))
     return newSeq[Element]()
 
-func optimizeSelectorList(selectors: SelectorList): SelectorList =
-  new(result)
-  #pass 1: check for invalid sequences
-  var i = 1
-  while i < selectors.len:
-    let sel = selectors[i]
-    if sel.t == TYPE_SELECTOR or sel.t == UNIVERSAL_SELECTOR:
-      return SelectorList()
-    inc i
-
-  #pass 2: move selectors in combination
-  if selectors.len > 1:
-    var i = 0
-    var slow = SelectorList()
-    if selectors[0].t == UNIVERSAL_SELECTOR:
-      inc i
-
-    while i < selectors.len:
-      if selectors[i].t in {ATTR_SELECTOR, PSEUDO_SELECTOR, PSELEM_SELECTOR}:
-        slow.add(selectors[i])
-      else:
-        result.add(selectors[i])
-      inc i
-
-    result.add(slow)
-  else:
-    result.add(selectors[0])
-
 func selectElems(document: Document, selectors: SelectorList): seq[Element] =
   assert(selectors.len > 0)
   let sellist = optimizeSelectorList(selectors)
@@ -462,8 +434,12 @@ func selectElems(document: Document, selectors: SelectorList): seq[Element] =
 
   while i < sellist.len:
     if sellist[i].t == FUNC_SELECTOR:
-      if sellist[i].name == "not":
+      case sellist[i].name
+      of "not":
         result = result.filter((elem) => not selectorsMatch(elem, sellist[i].selectors))
+      of "is", "where":
+        result = result.filter((elem) => selectorsMatch(elem, sellist[i].selectors))
+      else: discard
     else:
       result = result.filter((elem) => selectorMatches(elem, sellist[i]))
     inc i
@@ -476,17 +452,65 @@ proc querySelector*(document: Document, q: string): seq[Element] =
   for sel in selectors:
     result.add(document.selectElems(sel))
 
-proc applyRule(elem: Element, rule: CSSRule) =
-  let selectors = parseSelectors(rule.prelude)
-  for sel in selectors:
-    if elem.selectorsMatch(sel):
-      eprint "match!"
+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))
 
-proc applyRules(document: Document, rules: CSSStylesheet) =
+  tosort.sort((x, y) => cmp(x.s,y.s))
+  return tosort.map((x) => x.b)
+
+proc applyRules*(document: Document, rules: CSSStylesheet): seq[tuple[e:Element,d:CSSDeclaration]] =
+  var stack: seq[Element]
+
+  stack.add(document.firstElementChild)
+  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:
+          if ((CSSDeclaration)item).important:
+            result.add((elem, (CSSDeclaration)item))
+          else:
+            elem.style.applyProperty((CSSDeclaration)item)
+
+    for child in elem.children:
+      stack.add(child)
+
+proc addBoxes*(elem: Element) =
+  var b = false
+  for child in elem.childNodes:
+    if child.nodeType == ELEMENT_NODE:
+      if ((Element)child).style.display == DISPLAY_BLOCK:
+        b = true
+        break
+  for child in elem.childNodes:
+    if child.nodeType == ELEMENT_NODE:
+      if b:
+        child.box = CSSBox(display: DISPLAY_BLOCK)
+      else:
+        child.box = CSSBox()
+
+proc generateBoxModel(document: Document) =
   var stack: seq[Element]
 
   stack.add(document.firstElementChild)
+  document.firstElementChild.box = CSSBox()
   while stack.len > 0:
     let elem = stack.pop()
+    elem.addBoxes()
     for child in elem.children:
       stack.add(child)
+
+proc applyDefaultStylesheet*(document: Document) =
+  let important = document.applyRules(stylesheet)
+  for rule in important:
+    rule.e.style.applyProperty(rule.d)
+    rule.e.cssvalues.add(getComputedValue(rule.d))
+
+  document.generateBoxModel()
diff --git a/src/html/htmlparser.nim b/src/html/htmlparser.nim
index f43bcf40..3cfc1d8d 100644
--- a/src/html/htmlparser.nim
+++ b/src/html/htmlparser.nim
@@ -31,71 +31,6 @@ type
     parentNode: Node
     textNode: Text
 
-#func newHtmlElement(tagType: TagType, parentNode: Node): HtmlElement =
-#  case tagType
-#  of TAG_INPUT: result = new(HtmlInputElement)
-#  of TAG_A: result = new(HtmlAnchorElement)
-#  of TAG_SELECT: result = new(HtmlSelectElement)
-#  of TAG_OPTION: result = new(HtmlOptionElement)
-#  else: result = new(HtmlElement)
-#
-#  result.nodeType = ELEMENT_NODE
-#  result.tagType = tagType
-#  result.parentNode = parentNode
-#  if parentNode.isElemNode():
-#    result.parentElement = HtmlElement(parentNode)
-#
-#  if tagType in DisplayInlineTags:
-#    result.display = DISPLAY_INLINE
-#  elif tagType in DisplayBlockTags:
-#    result.display = DISPLAY_BLOCK
-#  elif tagType in DisplayInlineBlockTags:
-#    result.display = DISPLAY_INLINE_BLOCK
-#  elif tagType == TAG_LI:
-#    result.display = DISPLAY_LIST_ITEM
-#  else:
-#    result.display = DISPLAY_NONE
-#
-#  case tagType
-#  of TAG_CENTER:
-#    result.centered = true
-#  of TAG_B:
-#    result.bold = true
-#  of TAG_I:
-#    result.italic = true
-#  of TAG_U:
-#    result.underscore = true
-#  of TAG_HEAD:
-#    result.hidden = true
-#  of TAG_STYLE:
-#    result.hidden = true
-#  of TAG_SCRIPT:
-#    result.hidden = true
-#  of TAG_OPTION:
-#    result.hidden = true #TODO
-#  of TAG_PRE, TAG_TD, TAG_TH:
-#    result.margin = 1
-#  of TAG_UL, TAG_OL:
-#    result.indent = 2
-#    result.margin = 1
-#  of TAG_H1, TAG_H2, TAG_H3, TAG_H4, TAG_H5, TAG_H6:
-#    result.bold = true
-#    result.margin = 1
-#  of TAG_A:
-#    result.islink = true
-#  of TAG_INPUT:
-#    HtmlInputElement(result).size = 20
-#  else: discard
-#
-#  if parentNode.isElemNode():
-#    let parent = HtmlElement(parentNode)
-#    result.centered = result.centered or parent.centered
-#    result.bold = result.bold or parent.bold
-#    result.italic = result.italic or parent.italic
-#    result.underscore = result.underscore or parent.underscore
-#    result.hidden = result.hidden or parent.hidden
-#    result.islink = result.islink or parent.islink
-
 func inputSize*(str: string): int =
   if str.len == 0:
     return 20
@@ -104,7 +39,7 @@ func inputSize*(str: string): int =
       return 20
   return str.parseInt()
 
-#w3m's getescapecmd and parse_tag, transpiled to nim.
+#w3m's getescapecmd and parse_tag, transpiled to nim and heavily modified.
 #(C) Copyright 1994-2002 by Akinori Ito
 #(C) Copyright 2002-2011 by Akinori Ito, Hironori Sakamoto, Fumitoshi Ukai
 #
@@ -153,7 +88,6 @@ proc getescapecmd(buf: string, at: var int): string =
   elif not isAlphaAscii(buf[i]):
     return ""
 
-  #TODO this could be way more efficient (and radixnode needs better interface)
   when defined(small):
     var n = entityMap
     var s = ""
diff --git a/src/buffer.nim b/src/io/buffer.nim
index dc171137..ce28e84e 100644
--- a/src/buffer.nim
+++ b/src/io/buffer.nim
@@ -4,20 +4,19 @@ import tables
 import strutils
 import unicode
 
-import types/enums
+import ../types/enums
 
-import utils/termattrs
-import utils/twtstr
+import ../utils/termattrs
+import ../utils/twtstr
 
-import html/dom
+import ../html/dom
 
-import io/twtio
+import ./twtio
 
 type
   Buffer* = ref BufferObj
   BufferObj = object
-    fmttext*: seq[string]
-    rawtext*: seq[string]
+    text*: seq[Rune]
     title*: string
     hovertext*: string
     width*: int
@@ -37,11 +36,16 @@ type
     attrs*: TermAttributes
     document*: Document
 
+    #TODO remove these
+    fmttext*: seq[string]
+    rawtext*: seq[string]
+
 proc newBuffer*(attrs: TermAttributes): Buffer =
   return Buffer(width: attrs.termWidth,
                 height: attrs.termHeight,
                 attrs: attrs)
 
+#TODO go through these and remove ones that don't make sense in the new model
 func lastLine*(buffer: Buffer): int =
   assert(buffer.fmttext.len == buffer.rawtext.len)
   return buffer.fmttext.len - 1
diff --git a/src/io/display.nim b/src/io/display.nim
index e3ce6bac..1c72d959 100644
--- a/src/io/display.nim
+++ b/src/io/display.nim
@@ -11,10 +11,10 @@ import ../utils/twtstr
 
 import ../html/dom
 
-import ../buffer
 import ../config
 
-import twtio
+import ./buffer
+import ./twtio
 
 proc clearStatusMsg*(at: int) =
   setCursorPos(0, at)
diff --git a/src/io/twtio.nim b/src/io/twtio.nim
index 34cf4ce6..4e2789b0 100644
--- a/src/io/twtio.nim
+++ b/src/io/twtio.nim
@@ -3,6 +3,7 @@ import tables
 import unicode
 import strutils
 import sequtils
+import sugar
 
 import ../utils/twtstr
 import ../utils/radixtree
@@ -21,9 +22,6 @@ template printesc*(s: string) =
     else:
       stdout.write($r)
 
-template printspc(i: int) =
-  print(' '.repeat(i))
-
 template eprint*(s: varargs[string, `$`]) = {.cast(noSideEffect).}:
   var a = false
   for x in s:
@@ -134,11 +132,12 @@ proc zeroShiftRedraw(state: var LineState) =
 
 proc insertCharseq(state: var LineState, cs: var seq[Rune]) =
   let escNext = state.escNext
-  cs.keepIf(func(r: Rune): bool = escNext or not r.isControlChar())
+  cs.keepIf((r) => escNext or not r.isControlChar())
   state.escNext = false
   if cs.len == 0:
     return
-  elif state.cursor >= state.news.len and state.news.width(state.shift, state.cursor) + cs.width() < state.displen:
+
+  if state.cursor >= state.news.len and state.news.width(state.shift, state.cursor) + cs.width() < state.displen:
     state.news &= cs
     state.cursor += cs.len
     printesc($cs)
@@ -185,7 +184,7 @@ proc readLine*(current: var string, minlen: int, maxlen: int): bool =
   state.minlen = minlen
   state.maxlen = maxlen
   state.displen = state.maxlen - 1
-  #ugh
+  #cache strings
   for i in 0..(maxlen - minlen):
     state.spaces.add(' '.repeat(i))
   printesc(current)
diff --git a/src/main.nim b/src/main.nim
index 539527f1..92d6b3b9 100644
--- a/src/main.nim
+++ b/src/main.nim
@@ -3,8 +3,6 @@ import uri
 import os
 import streams
 
-import css/style
-
 import utils/termattrs
 
 import html/dom
@@ -12,8 +10,8 @@ import html/htmlparser
 
 import io/display
 import io/twtio
+import io/buffer
 
-import buffer
 import config
 
 let clientInstance = newHttpClient()
@@ -39,6 +37,9 @@ proc getPageUri(uri: Uri): Stream =
 
 var buffers: seq[Buffer]
 
+
+const defaultcss = staticRead"../res/default.css"
+
 proc main*() =
   if paramCount() != 1:
     eprint "Invalid parameters. Usage:\ntwt <url>"
@@ -50,10 +51,7 @@ proc main*() =
   let uri = parseUri(paramStr(1))
   buffers.add(buffer)
   buffer.document = parseHtml(getPageUri(uri))
-  let s = buffer.document.querySelector(":not(:first-child)")
-  eprint s.len
-  for q in s:
-    eprint q
+  buffer.document.applyDefaultStylesheet()
   buffer.setLocation(uri)
   buffer.renderHtml()
   var lastUri = uri
@@ -71,4 +69,3 @@ proc main*() =
       buffer.renderHtml()
     lastUri = newUri
 main()
-#parseCSS(newFileStream("default.css", fmRead))
diff --git a/src/types/enums.nim b/src/types/enums.nim
index 21b27ab6..f88f665b 100644
--- a/src/types/enums.nim
+++ b/src/types/enums.nim
@@ -28,9 +28,8 @@ type
     INPUT_URL, INPUT_WEEK
 
   WhitespaceType* = enum
-    WHITESPACE_UNKNOWN, WHITESPACE_NORMAL, WHITESPACE_NOWRAP, WHITESPACE_PRE,
-    WHITESPACE_PRE_LINE, WHITESPACE_PRE_WRAP, WHITESPACE_INITIAL,
-    WHITESPACE_INHERIT
+    WHITESPACE_NORMAL, WHITESPACE_NOWRAP, WHITESPACE_PRE, WHITESPACE_PRE_LINE,
+    WHITESPACE_PRE_WRAP
 
   TagType* = enum
     TAG_UNKNOWN, TAG_HTML, TAG_BASE, TAG_HEAD, TAG_LINK, TAG_META, TAG_STYLE,
@@ -62,34 +61,24 @@ type
     CSS_RBRACE_TOKEN
 
   CSSUnit* = enum
-    CM_UNIT, MM_UNIT, IN_UNIT, PX_UNIT, PT_UNIT, PC_UNIT,
-    EM_UNIT, EX_UNIT, CH_UNIT, REM_UNIT, VW_UNIT, VH_UNIT, VMIN_UNIT, VMAX_UNIT,
-    PERC_UNIT
+    UNIT_CM, UNIT_MM, UNIT_IN, UNIT_PX, UNIT_PT, UNIT_PC,
+    UNIT_EM, UNIT_EX, UNIT_CH, UNIT_REM, UNIT_VW, UNIT_VH, UNIT_VMIN, UNIT_VMAX,
+    UNIT_PERC
 
   CSSPosition* = enum
-    STATIC_POSITION, RELATIVE_POSITION, ABSOLUTE_POSITION, FIXED_POSITION,
-    INHERIT_POSITION
+    POSITION_STATIC, POSITION_RELATIVE, POSITION_ABSOLUTE, POSITION_FIXED,
+    POSITION_INHERIT
 
-const DisplayInlineTags* = {
-  TAG_A, TAG_ABBR, TAG_B, TAG_BDO, TAG_BR, TAG_BUTTON, TAG_CITE, TAG_CODE,
-  TAG_DEL, TAG_DFN, TAG_EM, TAG_FONT, TAG_I, TAG_IMG, TAG_INS, TAG_INPUT,
-  TAG_IFRAME, TAG_KBD, TAG_LABEL, TAG_MAP, TAG_OBJECT, TAG_Q, TAG_SAMP,
-  TAG_SCRIPT, TAG_SELECT, TAG_SMALL, TAG_SPAN, TAG_STRONG, TAG_SUB, TAG_SUP,
-  TAG_TEXTAREA, TAG_TT, TAG_VAR, TAG_FONT, TAG_IFRAME, TAG_U, TAG_S, TAG_STRIKE,
-  TAG_FRAME, TAG_IMG, TAG_INPUT
-}
+  CSSFontStyle* = enum
+    FONTSTYLE_NORMAL, FONTSTYLE_ITALIC, FONTSTYLE_OBLIQUE
 
-const DisplayNoneTags* = {
-  TAG_AREA, TAG_BASE, TAG_SOURCE, TAG_TRACK, TAG_LINK, TAG_META, TAG_PARAM, TAG_WBR
-}
+  CSSRuleType* = enum
+    RULE_ALL, RULE_COLOR, RULE_MARGIN, RULE_MARGIN_TOP, RULE_MARGIN_LEFT,
+    RULE_MARGIN_RIGHT, RULE_MARGIN_BOTTOM, RULE_FONT_STYLE, RULE_DISPLAY,
+    RULE_CONTENT
 
-const DisplayInlineBlockTags* = {
-  TAG_IMG
-}
-
-const DisplayTableColumnTags* = {
-  TAG_COL
-}
+  CSSGlobalValueType* = enum
+    VALUE_INITIAL, VALUE_INHERIT, VALUE_REVERT, VALUE_UNSET
 
 const SelfClosingTagTypes* = {
   TAG_LI, TAG_P