about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client.nim50
-rw-r--r--src/css/cascade.nim72
-rw-r--r--src/css/mediaquery.nim306
-rw-r--r--src/css/parser.nim164
-rw-r--r--src/css/select.nim2
-rw-r--r--src/css/sheet.nim52
-rw-r--r--src/io/buffer.nim5
-rw-r--r--src/layout/box.nim20
-rw-r--r--src/layout/engine.nim8
9 files changed, 529 insertions, 150 deletions
diff --git a/src/client.nim b/src/client.nim
index 23ebb9f3..7ec5b3de 100644
--- a/src/client.nim
+++ b/src/client.nim
@@ -43,17 +43,19 @@ proc actionError(s: string) =
 proc interruptError() =
   raise newException(InterruptError, "Interrupted")
 
-proc getRemotePage(client: Client, url: string): Stream =
-  return client.http.get(url).bodyStream
-
-proc getLocalPage(url: string): Stream =
-  return newFileStream(url, fmRead)
-
-proc getPage(client: Client, url: Url): Stream =
+proc getPage(client: Client, url: Url): tuple[s: Stream, contenttype: string] =
   if url.scheme == "file":
-    return getLocalPage($url.path)
+    let path = url.path.serialize()
+    result.contenttype = guessContentType(path)
+    result.s = newFileStream(path, fmRead)
   elif url.scheme == "http" or url.scheme == "https":
-    return client.getRemotePage(url.serialize())
+    let resp = client.http.get(url.serialize(true))
+    let ct = resp.contentType()
+    if ct != "":
+      result.contenttype = ct
+    else:
+      result.contenttype = guessContentType(url.path.serialize())
+    result.s = resp.bodyStream
 
 proc addBuffer(client: Client) =
   if client.buffer == nil:
@@ -91,8 +93,6 @@ proc discardBuffer(client: Client) =
 
 proc setupBuffer(client: Client) =
   let buffer = client.buffer
-  if not buffer.ispipe:
-    buffer.contenttype = guessContentType($buffer.location.path)
   buffer.userstyle = client.userstyle
   buffer.load()
   buffer.render()
@@ -126,15 +126,17 @@ proc gotoUrl(client: Client, url: string, prevurl = none(Url), force = false, ne
     let prevurl = oldurl
     if force or prevurl.issome or not prevurl.get.equals(url, true):
       try:
-        let s = client.getPage(url)
-        if s != nil:
+        let page = client.getPage(url)
+        if page.s != nil:
           if newbuf:
             client.addBuffer()
             g_client = client
             setControlCHook(proc() {.noconv.} =
-              g_client.discardBuffer()
+              if g_client.buffer.prev != nil or g_client.buffer.next != nil:
+                g_client.discardBuffer()
               interruptError())
-          client.buffer.istream = s
+          client.buffer.istream = page.s
+          client.buffer.contenttype = page.contenttype
           client.buffer.streamclosed = false
         else:
           loadError("Couldn't load " & $url)
@@ -156,7 +158,6 @@ proc loadUrl(client: Client, url: string) =
     try:
       let cdir = parseUrl("file://" & getCurrentDir() & '/')
       client.gotoUrl(url, cdir, true)
-    except InterruptError: discard
     except LoadError:
       client.gotoUrl("http://" & url, none(Url), true)
 
@@ -191,6 +192,7 @@ proc toggleSource*(client: Client) =
     client.buffer.sourcepair = client.buffer.prev
     client.buffer.sourcepair.sourcepair = client.buffer
     client.buffer.source = client.buffer.prev.source
+    client.buffer.streamclosed = true
     let prevtype = client.buffer.prev.contenttype
     if prevtype == "text/html":
       client.buffer.contenttype = "text/plain"
@@ -198,6 +200,11 @@ proc toggleSource*(client: Client) =
       client.buffer.contenttype = "text/html"
     client.setupBuffer()
 
+proc quit(client: Client) =
+  eraseScreen()
+  print(HVP(0, 0))
+  quit(0)
+
 proc input(client: Client) =
   let buffer = client.buffer
   if not client.feednext:
@@ -208,10 +215,7 @@ proc input(client: Client) =
   client.s &= c
   let action = getNormalAction(client.s)
   case action
-  of ACTION_QUIT:
-    eraseScreen()
-    print(HVP(0, 0))
-    quit(0)
+  of ACTION_QUIT: client.quit()
   of ACTION_CURSOR_LEFT: buffer.cursorLeft()
   of ACTION_CURSOR_DOWN: buffer.cursorDown()
   of ACTION_CURSOR_UP: buffer.cursorUp()
@@ -256,6 +260,12 @@ proc input(client: Client) =
 
 proc inputLoop(client: Client) =
   while true:
+    g_client = client
+    setControlCHook(proc() {.noconv.} =
+      g_client.buffer.setStatusMessage("Interrupted rendering procedure")
+      g_client.buffer.redraw = true
+      g_client.buffer.reshape = false
+      g_client.inputLoop())
     client.buffer.refreshBuffer()
     try:
       client.input()
diff --git a/src/css/cascade.nim b/src/css/cascade.nim
index 56c3dc09..50dbd139 100644
--- a/src/css/cascade.nim
+++ b/src/css/cascade.nim
@@ -1,11 +1,12 @@
-import streams
+import algorithm
 import sequtils
+import streams
 import sugar
-import algorithm
 
+import css/mediaquery
+import css/parser
 import css/select
 import css/selparser
-import css/parser
 import css/sheet
 import css/values
 import html/dom
@@ -35,16 +36,57 @@ proc applyProperty(elem: Element, d: CSSDeclaration, pseudo: PseudoElem) =
   elem.cssapplied = true
   elem.rendered = false
 
+func applies(mq: MediaQuery): bool =
+  case mq.t
+  of CONDITION_MEDIA:
+    case mq.media
+    of MEDIA_TYPE_ALL: return true
+    of MEDIA_TYPE_PRINT: return false
+    of MEDIA_TYPE_SCREEN: return true
+    of MEDIA_TYPE_SPEECH: return false
+    of MEDIA_TYPE_TTY: return true
+    of MEDIA_TYPE_UNKNOWN: return false
+  of CONDITION_NOT:
+    return not mq.n.applies()
+  of CONDITION_AND:
+    return mq.anda.applies() and mq.andb.applies()
+  of CONDITION_OR:
+    return mq.ora.applies() or mq.orb.applies()
+  of CONDITION_FEATURE:
+    case mq.feature.t
+    of FEATURE_COLOR:
+      return true #TODO
+    of FEATURE_GRID:
+      return mq.feature.b
+    of FEATURE_HOVER:
+      return mq.feature.b
+    of FEATURE_PREFERS_COLOR_SCHEME:
+      return mq.feature.b
+
+func applies(mqlist: MediaQueryList): bool =
+  for mq in mqlist:
+    if mq.applies():
+      return true
+  return false
+
+func calcRule(tosorts: var array[PseudoElem, seq[tuple[s:int,b:CSSSimpleBlock]]], elem: Element, rule: CSSRuleBase) =
+  if rule of CSSRuleDef:
+    let rule = CSSRuleDef(rule)
+    for sel in rule.sels:
+      let match = elem.selectorsMatch(sel)
+      if match.success:
+        let spec = getSpecificity(sel)
+        tosorts[match.pseudo].add((spec,rule.oblock))
+  elif rule of CSSMediaQueryDef:
+    let def = CSSMediaQueryDef(rule)
+    if def.query.applies():
+      for child in def.children:
+        tosorts.calcRule(elem, child)
+
 func calcRules(elem: Element, rules: CSSStylesheet): RuleList =
-  var tosorts: array[low(PseudoElem)..high(PseudoElem), seq[tuple[s:int,b:CSSSimpleBlock]]]
+  var tosorts: array[PseudoElem, seq[tuple[s:int,b:CSSSimpleBlock]]]
   for rule in rules:
-    if rule of CSSRuleDef:
-      let rule = CSSRuleDef(rule)
-      for sel in rule.sels:
-        let match = elem.selectorsMatch(sel)
-        if match.success:
-          let spec = getSpecificity(sel)
-          tosorts[match.pseudo].add((spec,rule.oblock))
+    tosorts.calcRule(elem, rule)
 
   for i in low(PseudoElem)..high(PseudoElem):
     tosorts[i].sort((x, y) => cmp(x.s,y.s))
@@ -64,23 +106,23 @@ proc applyRules(element: Element, ua, user, author: RuleList, pseudo: PseudoElem
 
   let rules_user_agent = ua[pseudo]
   for rule in rules_user_agent:
-    let decls = parseCSSListOfDeclarations(rule.value)
+    let decls = parseListOfDeclarations(rule.value)
     ares.applyItems(decls)
 
   let rules_user = user[pseudo]
   for rule in rules_user:
-    let decls = parseCSSListOfDeclarations(rule.value)
+    let decls = parseListOfDeclarations(rule.value)
     ares.applyItems(decls)
 
   let rules_author = author[pseudo]
   for rule in rules_author:
-    let decls = parseCSSListOfDeclarations(rule.value)
+    let decls = parseListOfDeclarations(rule.value)
     ares.applyItems(decls)
 
   if pseudo == PSEUDO_NONE:
     let style = element.attr("style")
     if style.len > 0:
-      let inline_rules = newStringStream(style).parseCSSListOfDeclarations()
+      let inline_rules = newStringStream(style).parseListOfDeclarations()
       ares.applyItems(inline_rules)
 
   for rule in ares.normal:
diff --git a/src/css/mediaquery.nim b/src/css/mediaquery.nim
new file mode 100644
index 00000000..bf9d2535
--- /dev/null
+++ b/src/css/mediaquery.nim
@@ -0,0 +1,306 @@
+import tables
+import unicode
+
+import css/parser
+
+type
+  MediaQueryParser = object
+    at: int
+    cvals: seq[CSSComponentValue]
+
+  MediaType* = enum
+    MEDIA_TYPE_UNKNOWN, MEDIA_TYPE_ALL, MEDIA_TYPE_PRINT, MEDIA_TYPE_SCREEN,
+    MEDIA_TYPE_SPEECH, MEDIA_TYPE_TTY
+
+  MediaConditionType* = enum
+    CONDITION_NOT, CONDITION_AND, CONDITION_OR, CONDITION_FEATURE,
+    CONDITION_MEDIA
+
+  MediaFeatureType* = enum
+    FEATURE_COLOR, FEATURE_GRID, FEATURE_HOVER, FEATURE_PREFERS_COLOR_SCHEME
+
+  MediaFeature* = object
+    case t*: MediaFeatureType
+    of FEATURE_COLOR:
+      color*: Slice[int]
+    of FEATURE_GRID, FEATURE_HOVER, FEATURE_PREFERS_COLOR_SCHEME:
+      b*: bool
+
+  MediaQuery* = ref object
+    case t*: MediaConditionType
+    of CONDITION_MEDIA:
+      media*: MediaType
+    of CONDITION_FEATURE:
+      feature*: MediaFeature
+    of CONDITION_NOT:
+      n*: MediaQuery
+    of CONDITION_OR:
+      ora*: MediaQuery
+      orb*: MediaQuery
+    of CONDITION_AND:
+      anda*: MediaQuery
+      andb*: MediaQuery
+
+  MediaQueryList* = seq[MediaQuery]
+
+const MediaTypes = {
+  "all": MEDIA_TYPE_ALL,
+  "print": MEDIA_TYPE_PRINT,
+  "screen": MEDIA_TYPE_SCREEN,
+  "speech": MEDIA_TYPE_SPEECH,
+  "tty": MEDIA_TYPE_TTY
+}.toTable()
+
+proc has(parser: MediaQueryParser, i = 0): bool {.inline.} =
+  return parser.cvals.len > parser.at + i
+
+proc consume(parser: var MediaQueryParser): CSSComponentValue {.inline.} =
+  result = parser.cvals[parser.at]
+  inc parser.at
+
+proc reconsume(parser: var MediaQueryParser) {.inline.} =
+  dec parser.at
+
+proc peek(parser: MediaQueryParser, i = 0): CSSComponentValue {.inline.} =
+  return parser.cvals[parser.at + i]
+
+proc skipBlanks(parser: var MediaQueryParser) {.inline.} =
+  while parser.has():
+    let cval = parser.peek()
+    if cval of CSSToken and CSSToken(cval).tokenType == CSS_WHITESPACE_TOKEN:
+      inc parser.at
+    else:
+      break
+
+proc getBoolFeature(feature: MediaFeatureType): MediaQuery =
+  result = MediaQuery(t: CONDITION_FEATURE)
+  case feature
+  of FEATURE_GRID, FEATURE_HOVER, FEATURE_PREFERS_COLOR_SCHEME:
+    result.feature = MediaFeature(t: feature, b: true)
+  of FEATURE_COLOR:
+    result.feature = MediaFeature(t: feature, color: 1..high(int))
+
+template get_tok(tok: untyped) =
+  if not (cval of CSSToken): return nil
+  tok = CSSToken(cval)
+
+template get_idtok(tok: untyped) =
+  get_tok(tok)
+  if tok.tokenType != CSS_IDENT_TOKEN: return nil
+
+template expect_mq_int(b: bool, ifalse: int, itrue: int) =
+  let cval = parser.consume()
+  if not (cval of CSSToken): return nil
+  let tok = CSSToken(cval)
+  if tok.tokenType != CSS_NUMBER_TOKEN: return nil
+  let i = int(tok.nvalue)
+  if i == ifalse: b = false
+  elif i == itrue: b = true
+  else: return nil
+
+template expect_bool(b: bool, sfalse: string, strue: string) =
+  let cval = parser.consume()
+  if not (cval of CSSToken): return nil
+  let tok = CSSToken(cval)
+  if tok.tokenType != CSS_IDENT_TOKEN: return nil
+  let s = $tok.value
+  case s
+  of strue: b = true
+  of sfalse: b = false
+  else: return nil
+
+proc parseFeature(parser: var MediaQueryParser, feature: MediaFeatureType): MediaQuery =
+  if not parser.has(): return getBoolFeature(feature)
+  let cval = parser.consume()
+  var tok: CSSToken
+  get_tok(tok)
+  if tok.tokenType != CSS_COLON_TOKEN: return nil
+  parser.skipBlanks()
+  case feature
+  of FEATURE_GRID:
+    var b: bool
+    expect_mq_int(b, 0, 1)
+    result = MediaQuery(t: CONDITION_FEATURE, feature: MediaFeature(t: feature, b: b))
+  of FEATURE_HOVER:
+    var b: bool
+    expect_bool(b, "none", "hover")
+    result = MediaQuery(t: CONDITION_FEATURE, feature: MediaFeature(t: feature, b: b))
+  of FEATURE_PREFERS_COLOR_SCHEME:
+    var b: bool
+    expect_bool(b, "light", "dark")
+    result = MediaQuery(t: CONDITION_FEATURE, feature: MediaFeature(t: feature, b: b))
+  else: return nil
+
+  parser.skipBlanks()
+  if parser.has():
+    return nil
+
+proc parseMediaCondition(parser: var MediaQueryParser, non = false, noor = false): MediaQuery
+
+proc parseMediaInParens(parser: var MediaQueryParser): MediaQuery =
+  var fparser: MediaQueryParser
+  block:
+    let cval = parser.consume()
+    if not (cval of CSSSimpleBlock): return nil
+
+    let sb = CSSSimpleBlock(cval)
+    if sb.token.tokenType != CSS_LPAREN_TOKEN: return nil
+
+    fparser.cvals = sb.value
+    fparser.skipBlanks()
+
+  block:
+    let cval = fparser.consume()
+    var tok: CSSToken
+    get_tok(tok)
+    fparser.skipBlanks()
+    if tok.tokenType == CSS_IDENT_TOKEN:
+      let tokval = $tok.value
+      case tokval
+      of "not":
+        return fparser.parseMediaCondition(true)
+      of "color":
+        return fparser.parseFeature(FEATURE_COLOR)
+      of "grid":
+        return fparser.parseFeature(FEATURE_GRID)
+      of "hover":
+        return fparser.parseFeature(FEATURE_HOVER)
+      of "prefers-color-scheme":
+        return fparser.parseFeature(FEATURE_PREFERS_COLOR_SCHEME)
+      else: discard
+  return nil
+
+proc parseMediaOr(parser: var MediaQueryParser, left: MediaQuery): MediaQuery =
+  let right = parser.parseMediaCondition()
+  return MediaQuery(t: CONDITION_OR, ora: left, orb: right)
+
+proc parseMediaAnd(parser: var MediaQueryParser, left: MediaQuery): MediaQuery =
+  let right = parser.parseMediaCondition()
+  return MediaQuery(t: CONDITION_AND, anda: left, andb: right)
+
+proc parseMediaCondition(parser: var MediaQueryParser, non = false, noor = false): MediaQuery =
+  var non = non
+  if not non:
+    let cval = parser.consume()
+    if cval of CSSToken:
+      if $CSSToken(cval).value == "not":
+        non = true
+    else:
+      parser.reconsume()
+
+  if not parser.has():
+    return nil
+
+  result = parser.parseMediaInParens()
+
+  if result == nil:
+    return nil
+
+  if non:
+    result = MediaQuery(t: CONDITION_NOT, n: result)
+
+  parser.skipBlanks()
+  if not parser.has():
+    return result
+
+  let cval = parser.consume()
+  var tok: CSSToken
+  get_idtok(tok)
+  parser.skipBlanks()
+  let tokval = $tok.value
+  case tokval
+  of "and":
+    return parser.parseMediaAnd(result)
+  of "or":
+    if noor:
+      return nil
+    return parser.parseMediaOr(result)
+  else: discard
+
+proc parseMediaQuery(parser: var MediaQueryParser): MediaQuery =
+  parser.skipBlanks()
+  if not parser.has():
+    return nil
+  var non = false
+  block:
+    let cval = parser.consume()
+    if cval of CSSToken:
+      let tok = CSSToken(cval)
+      if tok.tokenType == CSS_IDENT_TOKEN: 
+        let tokval = $tok.value
+        case tokval
+        of "not":
+          non = true
+        of "only":
+          discard
+        elif tokval in MediaTypes:
+          result = MediaQuery(t: CONDITION_MEDIA, media: MediaTypes[tokval])
+        else:
+          return nil
+      else:
+        return nil
+    else:
+      parser.reconsume()
+      return parser.parseMediaCondition()
+
+  parser.skipBlanks()
+  if not parser.has():
+    return result
+
+  block:
+    let cval = parser.consume()
+    if cval of CSSToken:
+      let tok = CSSToken(cval)
+      if tok.tokenType == CSS_IDENT_TOKEN: 
+        let tokval = $tok.value
+        if result == nil:
+          if tokval in MediaTypes:
+            let mq = MediaQuery(t: CONDITION_MEDIA, media: MediaTypes[tokval])
+            if non:
+              result = MediaQuery(t: CONDITION_NOT, n: mq)
+            else:
+              result = mq
+          else:
+            return nil
+        else:
+          if tokval == "and":
+            parser.reconsume()
+            return parser.parseMediaAnd(result)
+          else:
+            return nil
+      else:
+        return nil
+    else:
+      parser.reconsume()
+      return parser.parseMediaCondition(non)
+
+  parser.skipBlanks()
+  if not parser.has():
+    return result
+
+  block:
+    let cval = parser.consume()
+    if cval of CSSToken:
+      let tok = CSSToken(cval)
+      if tok.tokenType == CSS_IDENT_TOKEN: 
+        let tokval = $tok.value
+        if tokval != "and":
+          return nil
+      else:
+        return nil
+
+    parser.skipBlanks()
+    if not parser.has():
+      return nil
+
+    parser.reconsume()
+    return parser.parseMediaAnd(result)
+
+proc parseMediaQueryList*(cvals: seq[CSSComponentValue]): MediaQueryList =
+  let cseplist = cvals.parseCommaSeparatedListOfComponentValues()
+  for list in cseplist:
+    var parser: MediaQueryParser
+    parser.cvals = list
+    let query = parser.parseMediaQuery()
+    if query != nil:
+      result.add(query)
diff --git a/src/css/parser.nim b/src/css/parser.nim
index c13018de..a3668739 100644
--- a/src/css/parser.nim
+++ b/src/css/parser.nim
@@ -1,7 +1,7 @@
-import unicode
-import streams
 import options
+import streams
 import sugar
+import unicode
 
 import utils/twtstr
 
@@ -74,6 +74,65 @@ type
 
   SyntaxError = object of ValueError
 
+func `$`*(c: CSSParsedItem): string =
+  if c of CSSToken:
+    case CSSToken(c).tokenType:
+    of CSS_FUNCTION_TOKEN, CSS_AT_KEYWORD_TOKEN, CSS_URL_TOKEN:
+      result &= $CSSToken(c).tokenType & $CSSToken(c).value & '\n'
+    of CSS_HASH_TOKEN:
+      result &= '#' & $CSSToken(c).value
+    of CSS_IDENT_TOKEN:
+      result &= $CSSToken(c).value
+    of CSS_STRING_TOKEN:
+      result &= ("\"" & $CSSToken(c).value & "\"")
+    of CSS_DELIM_TOKEN:
+      result &= $CSSToken(c).rvalue
+    of CSS_DIMENSION_TOKEN:
+      result &= $CSSToken(c).tokenType & $CSSToken(c).nvalue & "unit" & $CSSToken(c).unit & $CSSToken(c).tflagb
+    of CSS_NUMBER_TOKEN:
+      result &= $CSSToken(c).nvalue & $CSSToken(c).unit
+    of CSS_PERCENTAGE_TOKEN:
+      result &= $CSSToken(c).nvalue & "%"
+    of CSS_COLON_TOKEN:
+      result &= ":"
+    of CSS_WHITESPACE_TOKEN:
+      result &= " "
+    of CSS_SEMICOLON_TOKEN:
+      result &= ";\n"
+    of CSS_COMMA_TOKEN:
+      result &= ","
+    else:
+      result &= $CSSToken(c).tokenType & '\n'
+  elif c of CSSDeclaration:
+    result &= $CSSDeclaration(c).name
+    result &= ": "
+    for s in CSSDeclaration(c).value:
+      result &= $s
+    result &= ";\n"
+  elif c of CSSFunction:
+    result &= $CSSFunction(c).name & "("
+    for s in CSSFunction(c).value:
+      result &= $s
+    result &= ")"
+  elif c of CSSSimpleBlock:
+    case CSSSimpleBlock(c).token.tokenType
+    of CSS_LBRACE_TOKEN: result &= "{\n"
+    of CSS_LPAREN_TOKEN: result &= "("
+    of CSS_LBRACKET_TOKEN: result &= "["
+    else: discard
+    for s in CSSSimpleBlock(c).value:
+      result &= $s
+    case CSSSimpleBlock(c).token.tokenType
+    of CSS_LBRACE_TOKEN: result &= "\n}"
+    of CSS_LPAREN_TOKEN: result &= ")"
+    of CSS_LBRACKET_TOKEN: result &= "]"
+    else: discard
+  elif c of CSSRule:
+    if c of CSSAtRule:
+      result &= $CSSAtRule(c).name & " "
+    result &= $CSSRule(c).prelude & "\n"
+    result &= $CSSRule(c).oblock
+
 func `==`*(a: CSSParsedItem, b: CSSTokenType): bool =
   return a of CSSToken and CSSToken(a).tokenType == b
 
@@ -466,7 +525,7 @@ proc consumeQualifiedRule(state: var CSSParseState): Option[CSSQualifiedRule] =
   while state.has():
     let t = state.consume()
     if t of CSSSimpleBlock:
-      r.oblock = state.consumeSimpleBlock()
+      r.oblock = CSSSimpleBlock(t)
       return some(r)
     elif t == CSS_LBRACE_TOKEN:
       r.oblock = state.consumeSimpleBlock()
@@ -484,7 +543,7 @@ proc consumeAtRule(state: var CSSParseState): CSSAtRule =
   while state.at < state.tokens.len:
     let t = state.consume()
     if t of CSSSimpleBlock:
-      result.oblock = state.consumeSimpleBlock()
+      result.oblock = CSSSimpleBlock(t)
     elif t == CSS_SEMICOLON_TOKEN:
       return result
     elif t ==  CSS_LBRACE_TOKEN:
@@ -535,7 +594,6 @@ proc consumeDeclaration(state: var CSSParseState): Option[CSSDeclaration] =
 #> and at-rules, as CSS 2.1 does for @page. Unexpected at-rules (which could be
 #> all of them, in a given context) are invalid and should be ignored by the
 #> consumer.
-#Wow this is ugly.
 proc consumeListOfDeclarations(state: var CSSParseState): seq[CSSParsedItem] =
   while state.has():
     let t = state.consume()
@@ -594,9 +652,11 @@ proc parseStylesheet(inputStream: Stream): CSSRawStylesheet =
 proc parseListOfRules(state: var CSSParseState): seq[CSSRule] =
   return state.consumeListOfRules()
 
-proc parseListOfRules(inputStream: Stream): seq[CSSRule] =
+proc parseListOfRules*(cvals: seq[CSSComponentValue]): seq[CSSRule] =
   var state = CSSParseState()
-  state.tokens = tokenizeCSS(inputStream)
+  state.tokens = collect(newSeq):
+    for cval in cvals:
+      CSSParsedItem(cval)
   return state.parseListOfRules()
 
 proc parseRule(state: var CSSParseState): CSSRule =
@@ -637,7 +697,7 @@ proc parseDeclaration(state: var CSSParseState): CSSDeclaration =
 
   raise newException(SyntaxError, "No declaration found!")
 
-proc parseCSSDeclaration*(inputStream: Stream): CSSDeclaration =
+proc parseDeclaration*(inputStream: Stream): CSSDeclaration =
   var state = CSSParseState()
   state.tokens = tokenizeCSS(inputStream)
   return state.parseDeclaration()
@@ -645,15 +705,15 @@ 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()
+proc parseListOfDeclarations*(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()
+proc parseListOfDeclarations*(inputStream: Stream): seq[CSSParsedItem] =
+  var state: CSSParseState
   state.tokens = tokenizeCSS(inputStream)
   return state.parseListOfDeclarations()
 
@@ -670,8 +730,8 @@ proc parseComponentValue(state: var CSSParseState): CSSComponentValue =
   if state.has():
     raise newException(SyntaxError, "EOF not reached!")
 
-proc parseCSSComponentValue*(inputStream: Stream): CSSComponentValue =
-  var state = CSSParseState()
+proc parseComponentValue*(inputStream: Stream): CSSComponentValue =
+  var state: CSSParseState
   state.tokens = tokenizeCSS(inputStream)
   return state.parseComponentValue()
 
@@ -679,76 +739,34 @@ proc parseListOfComponentValues(state: var CSSParseState): seq[CSSComponentValue
   while state.has():
     result.add(state.consumeComponentValue())
 
-proc parseCSSListOfComponentValues*(inputStream: Stream): seq[CSSComponentValue] =
+proc parseListOfComponentValues*(inputStream: Stream): seq[CSSComponentValue] =
   var state = CSSParseState()
   state.tokens = tokenizeCSS(inputStream)
   return state.parseListOfComponentValues()
 
-proc parseCommaSeparatedListOfComponentValues(state: var CSSParseState): seq[CSSComponentValue] =
-  while state.has(1):
+proc parseCommaSeparatedListOfComponentValues(state: var CSSParseState): seq[seq[CSSComponentValue]] =
+  if state.has():
+    result.add(newSeq[CSSComponentValue]())
+
+  while state.has():
     let cvl = state.consumeComponentValue()
     if cvl != CSS_COMMA_TOKEN:
-      result.add(state.consumeComponentValue())
+      result[^1].add(cvl)
+    else:
+      result.add(newSeq[CSSComponentValue]())
+
+proc parseCommaSeparatedListOfComponentValues*(cvals: seq[CSSComponentValue]): seq[seq[CSSComponentValue]] =
+  var state: CSSParseState
+  state.tokens = collect(newSeq):
+    for cval in cvals:
+      CSSParsedItem(cval)
+  return state.parseCommaSeparatedListOfComponentValues()
 
-proc parseCommaSeparatedListOfComponentValues(inputStream: Stream): seq[CSSComponentValue] =
+proc parseCommaSeparatedListOfComponentValues(inputStream: Stream): seq[seq[CSSComponentValue]] =
   var state = CSSParseState()
   state.tokens = tokenizeCSS(inputStream)
   return state.parseCommaSeparatedListOfComponentValues()
 
-func `$`*(c: CSSComponentValue): string =
-  if c of CSSToken:
-    case CSSToken(c).tokenType:
-    of CSS_FUNCTION_TOKEN, CSS_AT_KEYWORD_TOKEN, CSS_URL_TOKEN:
-      result &= $CSSToken(c).tokenType & $CSSToken(c).value & '\n'
-    of CSS_HASH_TOKEN:
-      result &= '#' & $CSSToken(c).value
-    of CSS_IDENT_TOKEN:
-      result &= $CSSToken(c).value
-    of CSS_STRING_TOKEN:
-      result &= ("\"" & $CSSToken(c).value & "\"")
-    of CSS_DELIM_TOKEN:
-      result &= $CSSToken(c).rvalue
-    of CSS_DIMENSION_TOKEN:
-      result &= $CSSToken(c).tokenType & $CSSToken(c).nvalue & "unit" & $CSSToken(c).unit & $CSSToken(c).tflagb
-    of CSS_NUMBER_TOKEN:
-      result &= $CSSToken(c).nvalue & $CSSToken(c).unit
-    of CSS_PERCENTAGE_TOKEN:
-      result &= $CSSToken(c).nvalue & "%"
-    of CSS_COLON_TOKEN:
-      result &= ":"
-    of CSS_WHITESPACE_TOKEN:
-      result &= " "
-    of CSS_SEMICOLON_TOKEN:
-      result &= ";\n"
-    of CSS_COMMA_TOKEN:
-      result &= ","
-    else:
-      result &= $CSSToken(c).tokenType & '\n'
-  elif c of CSSDeclaration:
-    result &= $CSSDeclaration(c).name
-    result &= ": "
-    for s in CSSDeclaration(c).value:
-      result &= $s
-    result &= ";\n"
-  elif c of CSSFunction:
-    result &= $CSSFunction(c).name & "("
-    for s in CSSFunction(c).value:
-      result &= $s
-    result &= ")"
-  elif c of CSSSimpleBlock:
-    case CSSSimpleBlock(c).token.tokenType
-    of CSS_LBRACE_TOKEN: result &= "{\n"
-    of CSS_LPAREN_TOKEN: result &= "("
-    of CSS_LBRACKET_TOKEN: result &= "["
-    else: discard
-    for s in CSSSimpleBlock(c).value:
-      result &= $s
-    case CSSSimpleBlock(c).token.tokenType
-    of CSS_LBRACE_TOKEN: result &= "\n}"
-    of CSS_LPAREN_TOKEN: result &= ")"
-    of CSS_LBRACKET_TOKEN: result &= "]"
-    else: discard
-
 proc parseCSS*(inputStream: Stream): CSSRawStylesheet =
   if inputStream.atEnd():
     return CSSRawStylesheet()
diff --git a/src/css/select.nim b/src/css/select.nim
index 1f43844f..9da20d4f 100644
--- a/src/css/select.nim
+++ b/src/css/select.nim
@@ -184,7 +184,7 @@ func selectElems(document: Document, selectors: SelectorList): seq[Element] =
 
 proc querySelector*(document: Document, q: string): seq[Element] =
   let ss = newStringStream(q)
-  let cvals = parseCSSListOfComponentValues(ss)
+  let cvals = parseListOfComponentValues(ss)
   let selectors = parseSelectors(cvals)
 
   for sel in selectors:
diff --git a/src/css/sheet.nim b/src/css/sheet.nim
index c79dcd2a..468585fa 100644
--- a/src/css/sheet.nim
+++ b/src/css/sheet.nim
@@ -1,49 +1,51 @@
 import streams
+import unicode
 
+import css/mediaquery
 import css/parser
 import css/selparser
 
 type
-  CSSRuleBase* = object of RootObj
+  CSSRuleBase* = ref object of RootObj
 
-  CSSRuleDef* = object of CSSRuleBase
+  CSSRuleDef* = ref object of CSSRuleBase
     sels*: seq[SelectorList]
     oblock*: CSSSimpleBlock
 
-  CSSConditionalDef* = object of CSSRuleBase
-    rules*: CSSSimpleBlock
-    nested*: seq[CSSConditionalDef]
+  CSSConditionalDef* = ref object of CSSRuleBase
+    children*: seq[CSSRuleBase]
 
-  CSSMediaQueryDef = object of CSSConditionalDef
-    query*: string
+  CSSMediaQueryDef* = ref object of CSSConditionalDef
+    query*: MediaQueryList
 
-  CSSStylesheet* = seq[CSSRuleDef]
+  CSSStylesheet* = seq[CSSRuleBase]
 
-proc addRule(stylesheet: var CSSStylesheet, rule: CSSRule) =
+proc addRule(stylesheet: var CSSStylesheet, rule: CSSQualifiedRule) =
   let sels = parseSelectors(rule.prelude)
   if sels.len > 1 or sels[^1].len > 0:
     let r = CSSRuleDef(sels: sels, oblock: rule.oblock)
     stylesheet.add(r)
 
-proc parseAtRule(atrule: CSSAtRule): CSSConditionalDef =
-  for v in atrule.oblock.value:
-    if v of CSSRule:
-      if v of CSSAtRule:
-        #let atrule = CSSAtRule(v)
-        discard
-      else:
-        discard
-        #let rule = CSSRule(v)
-        #let sels = parseSelectors(rule.prelude)
-        #result.rules.add(CSSRule)
+proc addAtRule(stylesheet: var CSSStylesheet, atrule: CSSAtRule) =
+  case $atrule.name
+  of "media":
+    let query = parseMediaQueryList(atrule.prelude)
+    let rules = atrule.oblock.value.parseListOfRules()
+    if rules.len > 0:
+      var media = CSSMediaQueryDef()
+      media.query = query
+      for rule in rules:
+        if rule of CSSAtRule:
+          media.children.addAtRule(CSSAtRule(rule))
+        else:
+          media.children.addRule(CSSQualifiedRule(rule)) #qualified rule
+      stylesheet.add(media)
+  else: discard #TODO
 
 proc parseStylesheet*(s: Stream): CSSStylesheet =
   for v in parseCSS(s).value:
-    if v of CSSAtRule:
-      discard
-      #result.add(CSSAtRule(v).parseAtRule())
-    else:
-      result.addRule(v)
+    if v of CSSAtRule: result.addAtRule(CSSAtRule(v))
+    else: result.addRule(CSSQualifiedRule(v))
 
 proc parseStylesheet*(s: string): CSSStylesheet =
   return newStringStream(s).parseStylesheet()
diff --git a/src/io/buffer.nim b/src/io/buffer.nim
index a2d44b8c..c25731b7 100644
--- a/src/io/buffer.nim
+++ b/src/io/buffer.nim
@@ -725,13 +725,14 @@ proc load*(buffer: Buffer) =
       #config option like a) store source b) generate source
       buffer.source = buffer.istream.readAll()
       buffer.istream.close()
-      buffer.document = parseHtml(newStringStream(buffer.source))
       buffer.streamclosed = true
+    buffer.document = parseHtml(newStringStream(buffer.source))
   else:
     if not buffer.streamclosed:
-      buffer.lines = renderStream(buffer.istream)
+      buffer.source = buffer.istream.readAll()
       buffer.istream.close()
       buffer.streamclosed = true
+    buffer.lines = renderPlainText(buffer.source)
 
 proc render*(buffer: Buffer) =
   case buffer.contenttype
diff --git a/src/layout/box.nim b/src/layout/box.nim
index 556ad88d..a165c729 100644
--- a/src/layout/box.nim
+++ b/src/layout/box.nim
@@ -46,16 +46,16 @@ type
     rows*: seq[CSSRowBox]
     thisrow*: seq[CSSRowBox]
 
-    dimensions*: Rectangle
-    content*: seq[CSSInlineRow]
-    rcontent*: CSSInlineRow
-    color*: CSSColor
-    fontstyle*: CSSFontStyle
-    fontweight*: int
-    textdecoration*: CSSTextDecoration
-    nodes*: seq[Node]
-
-    maxwidth*: int
+    #dimensions*: Rectangle
+    #content*: seq[CSSInlineRow]
+    #rcontent*: CSSInlineRow
+    #color*: CSSColor
+    #fontstyle*: CSSFontStyle
+    #fontweight*: int
+    #textdecoration*: CSSTextDecoration
+    #nodes*: seq[Node]
+
+    #maxwidth*: int
 
   BlockContext* = ref object
     fromy*: int
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index bea3371d..97c24d01 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -497,7 +497,7 @@ proc alignBoxes*(document: Document, term: TermAttributes): CSSBox =
   state.processElemChildren(rootbox, document.root)
   return rootbox
 
-proc alignBoxes2*(document: Document, term: TermAttributes): CSSBlockBox =
-  result = CSSBlockBox()
-  result.bcontext = BlockContext()
-  result.bcontext.content.add(CSSInlineBox())
+#proc alignBoxes2*(document: Document, term: TermAttributes): CSSBlockBox =
+#  result = CSSBlockBox()
+#  result.bcontext = BlockContext()
+#  result.bcontext.content.add(CSSInlineBox())