about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--res/ua.css9
-rw-r--r--src/buffer/container.nim35
-rw-r--r--src/css/cascade.nim2
-rw-r--r--src/css/values.nim38
-rw-r--r--src/html/htmlparser.nim2
-rw-r--r--src/io/lineedit.nim2
-rw-r--r--src/io/term.nim44
-rw-r--r--src/layout/box.nim7
-rw-r--r--src/layout/engine.nim404
-rw-r--r--src/render/renderdocument.nim2
-rw-r--r--src/types/color.nim16
11 files changed, 302 insertions, 259 deletions
diff --git a/res/ua.css b/res/ua.css
index 3c2b4daa..83e6ec98 100644
--- a/res/ua.css
+++ b/res/ua.css
@@ -26,6 +26,10 @@ ol, ul, menu, dir {
 	margin-bottom: unset;
 }
 
+blockquote {
+	margin: 1em;
+}
+
 table {
 	display: table;
 }
@@ -66,6 +70,11 @@ td {
 	vertical-align: inherit;
 }
 
+caption {
+	display: table-caption;
+	text-align: center;
+}
+
 input {
 	margin-right: 1ch;
 	white-space: pre;
diff --git a/src/buffer/container.nim b/src/buffer/container.nim
index b1f73629..20f04f62 100644
--- a/src/buffer/container.nim
+++ b/src/buffer/container.nim
@@ -511,32 +511,6 @@ proc popCursorPos*(container: Container, nojump = false) =
     container.sendCursorPosition()
     container.needslines = true
 
-macro proxy(fun: typed) =
-  let name = fun[0] # sym
-  let params = fun[3] # formalparams
-  let retval = params[0] # sym
-  var body = newStmtList()
-  assert params.len >= 2 # return type, container
-  var x = name.strVal.toScreamingSnakeCase()
-  if x[^1] == '=':
-    x = "SET_" & x[0..^2]
-  let nup = ident(x)
-  let container = params[1][0]
-  body.add(quote do:
-    `container`.ostream.swrite(`nup`))
-  for c in params[2..^1]:
-    let s = c[0] # sym e.g. url
-    body.add(quote do:
-      `container`.ostream.swrite(`s`))
-  body.add(quote do:
-    `container`.ostream.flush())
-  if retval.kind != nnkEmpty:
-    body.add(quote do:
-      `container`.istream.sread(result))
-  var params2: seq[NimNode]
-  for x in params.children: params2.add(x)
-  result = newProc(name, params2, body)
-
 proc cursorNextLink*(container: Container) {.jsfunc.} =
   container.writeCommand(FIND_NEXT_LINK, container.cursorx, container.cursory)
   container.expect(JUMP)
@@ -569,8 +543,13 @@ proc gotoAnchor*(container: Container, anchor: string) =
   container.expect(ANCHOR_FOUND)
   container.expect(ANCHOR_FAIL)
 
-proc readCanceled*(container: Container) {.proxy.} = discard
-proc readSuccess*(container: Container, s: string) {.proxy.} = discard
+proc readCanceled*(container: Container) =
+  container.writeCommand(READ_CANCELED)
+
+proc readSuccess*(container: Container, s: string) =
+  container.writeCommand(READ_SUCCESS, s)
+  container.expect(OPEN)
+  container.expect(RESHAPE)
 
 proc reshape*(container: Container, noreq = false) {.jsfunc.} =
   container.writeCommand(RENDER)
diff --git a/src/css/cascade.nim b/src/css/cascade.nim
index bc8db4d7..851e6f83 100644
--- a/src/css/cascade.nim
+++ b/src/css/cascade.nim
@@ -107,6 +107,8 @@ func calcPresentationalHints(element: Element): CSSComputedValues =
     map_bgcolor
   of TAG_COL:
     map_width
+  of TAG_BODY:
+    map_bgcolor
   else: discard
  
 proc applyDeclarations(styledNode: StyledNode, parent: CSSComputedValues, ua, user: DeclarationList, author: seq[DeclarationList]) =
diff --git a/src/css/values.nim b/src/css/values.nim
index 1e82fe3c..55ee663e 100644
--- a/src/css/values.nim
+++ b/src/css/values.nim
@@ -81,14 +81,23 @@ type
   CSSListStylePosition* = enum
     LIST_STYLE_POSITION_OUTSIDE, LIST_STYLE_POSITION_INSIDE
 
+const RowGroupBox* = {DISPLAY_TABLE_ROW_GROUP, DISPLAY_TABLE_HEADER_GROUP,
+                      DISPLAY_TABLE_FOOTER_GROUP}
+const ProperTableChild* = {DISPLAY_TABLE_ROW, DISPLAY_TABLE_COLUMN,
+                           DISPLAY_TABLE_COLUMN_GROUP, DISPLAY_TABLE_CAPTION} +
+                           RowGroupBox
+const ProperTableRowParent* = {DISPLAY_TABLE, DISPLAY_INLINE_TABLE} + RowGroupBox
+const InternalTableBox* = {DISPLAY_TABLE_CELL, DISPLAY_TABLE_ROW,
+                           DISPLAY_TABLE_COLUMN, DISPLAY_TABLE_COLUMN_GROUP} +
+                           RowGroupBox
+const TabularContainer* = {DISPLAY_TABLE_ROW} + ProperTableRowParent
+
 type
   CSSLength* = object
     num*: float64
     unit*: CSSUnit
     auto*: bool
 
-  CSSColor* = RGBAColor
-
   CSSVerticalAlign* = object
     length*: CSSLength
     keyword*: CSSVerticalAlign2
@@ -97,7 +106,7 @@ type
     t*: CSSPropertyType
     case v*: CSSValueType
     of VALUE_COLOR:
-      color*: CSSColor
+      color*: RGBAColor
     of VALUE_LENGTH:
       length*: CSSLength
     of VALUE_FONT_STYLE:
@@ -290,10 +299,10 @@ func listMarker*(t: CSSListStyleType, i: int): string =
   of LIST_STYLE_TYPE_LOWER_ROMAN: return romanNumber_lower(i) & ". "
   of LIST_STYLE_TYPE_JAPANESE_INFORMAL: return japaneseNumber(i) & "、"
 
-const Colors: Table[string, CSSColor] = ((func (): Table[string, CSSColor] =
+const Colors: Table[string, RGBAColor] = ((func (): Table[string, RGBAColor] =
   for name, rgb in ColorsRGB:
-    result[name] = CSSColor(rgb)
-  result["transparent"] = CSSColor(rgba(0x00, 0x00, 0x00, 0x00))
+    result[name] = rgb
+  result["transparent"] = rgba(0x00, 0x00, 0x00, 0x00)
 )())
 
 const Units = {
@@ -344,13 +353,13 @@ func parseDimensionValues*(s: string): Option[CSSLength] =
   if s[i] == '%': return some(CSSLength(num: n, unit: UNIT_PERC))
   return some(CSSLength(num: n, unit: UNIT_PX))
 
-func color(r, g, b: int): CSSColor =
-  return CSSColor(rgba(r, g, b, 256))
+func color(r, g, b: int): RGBAColor =
+  return rgba(r, g, b, 256)
 
-func color(r, g, b, a: int): CSSColor =
-  return CSSColor(rgba(r, g, b, a))
+func color(r, g, b, a: int): RGBAColor =
+  return rgba(r, g, b, a)
 
-func cssColor(d: CSSDeclaration): CSSColor =
+func cssColor(d: CSSDeclaration): RGBAColor =
   if d.value.len > 0:
     if d.value[0] of CSSToken:
       let tok = CSSToken(d.value[0])
@@ -358,7 +367,7 @@ func cssColor(d: CSSDeclaration): CSSColor =
       of CSS_HASH_TOKEN:
         let c = parseHexColor(tok.value)
         if c.isSome:
-          return CSSColor(c.get)
+          return c.get
         else:
           raise newException(CSSValueError, "Invalid color")
       of CSS_IDENT_TOKEN:
@@ -398,7 +407,7 @@ func cssColor(d: CSSDeclaration): CSSColor =
 
   raise newException(CSSValueError, "Invalid color")
 
-func cellColor*(color: CSSColor): CellColor =
+func cellColor*(color: RGBAColor): CellColor =
   return CellColor(rgb: true, rgbcolor: RGBColor(color))
 
 func isToken(d: CSSDeclaration): bool {.inline.} = d.value.len > 0 and d.value[0] of CSSToken
@@ -468,6 +477,7 @@ func cssDisplay(d: CSSDeclaration): CSSDisplay =
       of "table-row-group": return DISPLAY_TABLE_ROW_GROUP
       of "table-header-group": return DISPLAY_TABLE_HEADER_GROUP
       of "table-footer-group": return DISPLAY_TABLE_FOOTER_GROUP
+      of "table-caption": return DISPLAY_TABLE_CAPTION
       of "none": return DISPLAY_NONE
       else: return DISPLAY_INLINE
   raise newException(CSSValueError, "Invalid display")
@@ -634,7 +644,7 @@ proc getValueFromDecl(val: CSSComputedValue, d: CSSDeclaration, vtype: CSSValueT
   of VALUE_LIST_STYLE_POSITION: val.liststyleposition = cssListStylePosition(d)
   of VALUE_NONE: discard
 
-func getInitialColor(t: CSSPropertyType): CSSColor =
+func getInitialColor(t: CSSPropertyType): RGBAColor =
   case t
   of PROPERTY_COLOR:
     return Colors["white"]
diff --git a/src/html/htmlparser.nim b/src/html/htmlparser.nim
index f28cd300..123d8ac7 100644
--- a/src/html/htmlparser.nim
+++ b/src/html/htmlparser.nim
@@ -1576,7 +1576,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
   of IN_CAPTION:
     match token:
       "</caption>" => (block:
-        if parser.openElements.hasElementInTableScope(TAG_CAPTION):
+        if not parser.openElements.hasElementInTableScope(TAG_CAPTION):
           parse_error
         else:
           parser.generateImpliedEndTags()
diff --git a/src/io/lineedit.nim b/src/io/lineedit.nim
index eb812122..a12604cb 100644
--- a/src/io/lineedit.nim
+++ b/src/io/lineedit.nim
@@ -94,10 +94,12 @@ proc generateOutput*(edit: LineEdit): FixedGrid =
     for r in os:
       result[x].str = "*"
       x += r.lwidth()
+      if x > result.width: break
   else:
     for r in os:
       result[x].str &= $r
       x += r.lwidth()
+      if x > result.width: break
 
 proc getCursorX*(edit: LineEdit): int =
   return edit.promptw + edit.news.lwidth(edit.shift, edit.cursor)
diff --git a/src/io/term.nim b/src/io/term.nim
index f892761b..82cb19fd 100644
--- a/src/io/term.nim
+++ b/src/io/term.nim
@@ -40,7 +40,7 @@ type
     outfile: File
     cleared: bool
     canvas: FixedGrid
-    prevgrid: FixedGrid
+    pcanvas: FixedGrid
     attrs*: WindowAttributes
     mincontrast: float
     colormode: ColorMode
@@ -138,14 +138,6 @@ proc resetFormat(term: Terminal): string =
     return SGR()
 
 #TODO get rid of this
-proc eraseLine*(term: Terminal) =
-  term.write(term.clearEnd())
-
-#TODO ditto
-proc resetFormat2*(term: Terminal) =
-  term.write(term.resetFormat())
-
-#TODO ditto
 proc setCursor*(term: Terminal, x, y: int) =
   term.write(term.cursorGoto(x, y))
 
@@ -285,41 +277,35 @@ func generateFullOutput(term: Terminal, grid: FixedGrid): string =
       let cell = grid[y * grid.width + x]
       result &= term.processFormat(format, cell.format)
       result &= cell.str
-    result &= SGR()
     result &= term.clearEnd()
     if y != grid.height - 1:
       result &= "\r\n"
 
-func generateSwapOutput(term: Terminal, grid: FixedGrid): string =
+func generateSwapOutput(term: Terminal, grid: FixedGrid, prev: FixedGrid): string =
   var format = newFormat()
-  let curr = grid.cells
-  let prev = term.prevgrid.cells
-  var i = 0
   var x = 0
-  var y = 0
   var line = ""
   var lr = false
-  while i < curr.len:
+  for i in 0 ..< grid.cells.len:
     if x >= grid.width:
+      format = newFormat()
       if lr:
-        result &= SGR()
-        result &= term.cursorGoto(0, y)
+        result &= term.cursorGoto(0, i div grid.width - 1)
+        result &= term.resetFormat()
         result &= term.clearEnd()
         result &= line
         lr = false
       x = 0
-      inc y
       line = ""
-    lr = lr or (curr[i] != prev[i])
-    line &= term.processFormat(format, curr[i].format)
-    line &= curr[i].str
-    inc i
+    lr = lr or (grid[i] != prev[i])
+    line &= term.processFormat(format, grid.cells[i].format)
+    line &= grid.cells[i].str
     inc x
   if lr:
-    result &= term.cursorGoto(0, y)
-    result &= EL()
+    result &= term.cursorGoto(0, grid.height - 1)
+    result &= term.resetFormat()
+    result &= term.clearEnd()
     result &= line
-    lr = false
 
 proc hideCursor*(term: Terminal) =
   term.outfile.hideCursor()
@@ -333,13 +319,13 @@ proc writeGrid*(term: Terminal, grid: FixedGrid, x = 0, y = 0) =
       term.canvas[ly * term.canvas.width + lx] = grid[(ly - y) * grid.width + (lx - x)]
 
 proc outputGrid*(term: Terminal) =
-  term.outfile.write(SGR())
+  term.outfile.write(term.resetFormat())
   if not term.cleared:
     term.outfile.write(term.generateFullOutput(term.canvas))
     term.cleared = true
   else:
-    term.outfile.write(term.generateSwapOutput(term.canvas))
-  term.prevgrid = term.canvas
+    term.outfile.write(term.generateSwapOutput(term.canvas, term.pcanvas))
+  term.pcanvas = term.canvas
 
 when defined(posix):
   import posix
diff --git a/src/layout/box.nim b/src/layout/box.nim
index ef4f7e8a..8f460a19 100644
--- a/src/layout/box.nim
+++ b/src/layout/box.nim
@@ -4,6 +4,7 @@ import css/stylednode
 import css/values
 import html/dom
 import io/window
+import types/color
 
 type
   #LayoutUnit* = distinct int32
@@ -59,6 +60,8 @@ type
     rowgroups*: seq[TableRowGroupBoxBuilder]
     width*: Option[CSSLength] # WIDTH property
 
+  TableCaptionBoxBuilder* = ref object of BlockBoxBuilder
+
   InlineAtom* = ref object of RootObj
     offset*: Offset
     width*: int
@@ -72,7 +75,7 @@ type
     fontstyle*: CSSFontStyle
     fontweight*: int
     textdecoration*: CSSTextDecoration
-    color*: CSSColor
+    color*: RGBAColor
     node*: StyledNode
 
   InlineSpacing* = ref object of InlineAtom
@@ -141,10 +144,12 @@ type
     builder*: TableRowBoxBuilder
 
   TableContext* = object
+    caption*: TableCaptionBoxBuilder
     colwidths*: seq[int]
     reflow*: seq[bool]
     colwidths_specified*: seq[int]
     rows*: seq[RowContext]
+    maxwidth*: int
 
   InlineBlockBox* = ref object of InlineAtom
     bctx*: BlockBox
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index 0fa1c156..d1ce9f27 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -378,10 +378,13 @@ proc newFlowRootBox(viewport: Viewport, box: BoxBuilder, parentWidth: int, paren
   result.setPreferredDimensions(parentWidth, parentHeight)
   result.shrink = result.computed{"width"}.auto
 
-proc newBlockBox(parent: BlockBox, box: BoxBuilder, ignore_parent_shrink = false): BlockBox =
+proc newBlockBox(parent: BlockBox, box: BoxBuilder, ignore_parent_shrink = false, maxwidth = none(int)): BlockBox =
   new(result)
   result.newBlockBox_common2(parent, box)
   result.shrink = result.computed{"width"}.auto and (ignore_parent_shrink or parent.shrink)
+  if maxwidth.isSome:
+    #TODO TODO TODO this is ugly
+    result.setPreferredDimensions(maxwidth.get, parent.compheight)
 
 proc newTableCellBox(parent: BlockBox, box: TableCellBoxBuilder): BlockBox =
   return newBlockBox(parent, box, true)
@@ -431,7 +434,7 @@ proc positionInlines(bctx: BlockBox) =
   else:
     bctx.width = bctx.compwidth
 
-proc buildBlock(box: BlockBoxBuilder, parent: BlockBox): BlockBox
+proc buildBlock(box: BlockBoxBuilder, parent: BlockBox, maxwidth = none(int)): BlockBox
 proc buildInlines(bctx: BlockBox, inlines: seq[BoxBuilder]): InlineContext
 proc buildBlocks(bctx: BlockBox, blocks: seq[BoxBuilder], node: StyledNode)
 
@@ -678,34 +681,65 @@ proc buildTableRow(pctx: TableContext, ctx: RowContext, parent: BlockBox, builde
   row.width = x
   return row
 
-iterator rows(builder: TableBoxBuilder): TableRowBoxBuilder =
+iterator rows(builder: TableBoxBuilder): BoxBuilder {.inline.} =
+  var header: seq[TableRowBoxBuilder]
+  var body: seq[TableRowBoxBuilder]
+  var footer: seq[TableRowBoxBuilder]
+  var caption: TableCaptionBoxBuilder
+  #TODO this should be done 
   for child in builder.children:
-    assert child.computed{"display"} in {DISPLAY_TABLE_ROW, DISPLAY_TABLE_ROW_GROUP}
+    assert child.computed{"display"} in ProperTableChild, $child.computed{"display"}
     case child.computed{"display"}
     of DISPLAY_TABLE_ROW:
-      yield TableRowBoxBuilder(child)
+      body.add(TableRowBoxBuilder(child))
+    of DISPLAY_TABLE_HEADER_GROUP:
+      for child in child.children:
+        assert child.computed{"display"} == DISPLAY_TABLE_ROW
+        header.add(TableRowBoxBuilder(child))
     of DISPLAY_TABLE_ROW_GROUP:
-      for child in TableRowGroupBoxBuilder(child).children:
+      for child in child.children:
+        assert child.computed{"display"} == DISPLAY_TABLE_ROW
+        body.add(TableRowBoxBuilder(child))
+    of DISPLAY_TABLE_FOOTER_GROUP:
+      for child in child.children:
         assert child.computed{"display"} == DISPLAY_TABLE_ROW
-        yield TableRowBoxBuilder(child)
+        footer.add(TableRowBoxBuilder(child))
+    of DISPLAY_TABLE_CAPTION:
+      if caption == nil:
+        caption = TableCaptionBoxBuilder(child)
     else: discard
+  yield caption
+  for child in header:
+    yield child
+  for child in body:
+    yield child
+  for child in footer:
+    yield child
 
 proc buildTable(box: TableBoxBuilder, parent: BlockBox): BlockBox =
   let table = parent.newTableBox(box)
   var ctx: TableContext
-  var maxw = 0
   for row in box.rows:
-    let rctx = ctx.preBuildTableRow(row, table)
-    ctx.rows.add(rctx)
-    maxw = max(rctx.width, maxw)
-  if maxw > table.compwidth and false: #TODO
-    for n in ctx.colwidths_specified:
-      maxw -= n
-    ctx.reflow.setLen(ctx.colwidths.len)
-    for i in 0 ..< ctx.colwidths.len:
-      if ctx.colwidths[i] != 0:
-        ctx.colwidths[i] -= (maxw - table.compwidth) div ctx.colwidths[i]
-        ctx.reflow[i] = true
+    if unlikely(row.computed{"display"} == DISPLAY_TABLE_CAPTION):
+      ctx.caption = TableCaptionBoxBuilder(row)
+    else:
+      let row = TableRowBoxBuilder(row)
+      let rctx = ctx.preBuildTableRow(row, table)
+      ctx.rows.add(rctx)
+      ctx.maxwidth = max(rctx.width, ctx.maxwidth)
+  #TODO implement a better table layout
+  #if ctx.maxwidth > table.compwidth:
+  #  for n in ctx.colwidths_specified:
+  #    ctx.maxwidth -= n
+  #  ctx.reflow.setLen(ctx.colwidths.len)
+  #  for i in 0 ..< ctx.colwidths.len:
+  #    if ctx.colwidths[i] != 0 and (ctx.colwidths_specified.len <= i or ctx.colwidths_specified[i] == 0):
+  #      ctx.colwidths[i] -= (ctx.maxwidth - table.compwidth) div ctx.colwidths[i]
+  #      ctx.reflow[i] = true
+  if ctx.caption != nil:
+    let caption = buildBlock(ctx.caption, table, some(ctx.maxwidth))
+    table.nested.add(caption)
+    table.height += caption.height
   for roww in ctx.rows:
     let row = ctx.buildTableRow(roww, table, roww.builder)
     row.offset.y += table.height
@@ -726,9 +760,9 @@ proc buildBlocks(bctx: BlockBox, blocks: seq[BoxBuilder], node: StyledNode) =
   bctx.positionBlocks()
 
 # Build a block box inside another block box, based on a builder.
-proc buildBlock(box: BlockBoxBuilder, parent: BlockBox): BlockBox =
+proc buildBlock(box: BlockBoxBuilder, parent: BlockBox, maxwidth = none(int)): BlockBox =
   assert parent != nil
-  result = parent.newBlockBox(box)
+  result = parent.newBlockBox(box, maxwidth = maxwidth)
   if box.inlinelayout:
     result.buildInlineLayout(box.children)
   else:
@@ -803,16 +837,30 @@ proc getTableCellBox(computed: CSSComputedValues): TableCellBoxBuilder =
   result.computed = computed
   result.colspan = 1
 
+proc getTableCaptionBox(computed: CSSComputedValues): TableCaptionBoxBuilder =
+  new(result)
+  result.computed = computed
+
 type BlockGroup = object
   parent: BoxBuilder
   boxes: seq[BoxBuilder]
   listItemCounter: int
 
+type InnerBlockContext = object
+  styledNode: StyledNode
+  blockgroup: BlockGroup
+  viewport: Viewport
+  ibox: InlineBoxBuilder
+  anonRow: TableRowBoxBuilder
+  anonTable: TableBoxBuilder
+
 proc add(blockgroup: var BlockGroup, box: BoxBuilder) {.inline.} =
+  assert box.computed{"display"} in {DISPLAY_INLINE, DISPLAY_INLINE_BLOCK}, $box.computed{"display"}
   blockgroup.boxes.add(box)
 
 proc flush(blockgroup: var BlockGroup) {.inline.} =
   if blockgroup.boxes.len > 0:
+    assert blockgroup.parent.computed{"display"} != DISPLAY_INLINE
     let bbox = getBlockBox(blockgroup.parent.computed.inheritProperties())
     bbox.inlinelayout = true
     bbox.children = blockgroup.boxes
@@ -825,13 +873,14 @@ func canGenerateAnonymousInline(blockgroup: BlockGroup, computed: CSSComputedVal
     computed{"white-space"} in {WHITESPACE_PRE_LINE, WHITESPACE_PRE, WHITESPACE_PRE_WRAP} or
     not str.onlyWhitespace()
 
-template flush_ibox() =
+proc iflush(blockgroup: var BlockGroup, ibox: var InlineBoxBuilder) =
   if ibox != nil:
     assert ibox.computed{"display"} in {DISPLAY_INLINE, DISPLAY_INLINE_BLOCK}
     blockgroup.add(ibox)
     ibox = nil
 
 proc newBlockGroup(parent: BoxBuilder): BlockGroup =
+  assert parent.computed{"display"} != DISPLAY_INLINE
   result.parent = parent
   result.listItemCounter = 1
 
@@ -839,163 +888,197 @@ proc generateTableBox(styledNode: StyledNode, viewport: Viewport): TableBoxBuild
 proc generateTableRowGroupBox(styledNode: StyledNode, viewport: Viewport): TableRowGroupBoxBuilder
 proc generateTableRowBox(styledNode: StyledNode, viewport: Viewport): TableRowBoxBuilder
 proc generateTableCellBox(styledNode: StyledNode, viewport: Viewport): TableCellBoxBuilder
-
+proc generateTableCaptionBox(styledNode: StyledNode, viewport: Viewport): TableCaptionBoxBuilder
 proc generateBlockBox(styledNode: StyledNode, viewport: Viewport, marker = none(MarkerBoxBuilder)): BlockBoxBuilder
-
-proc generateInlineBoxes(box: BoxBuilder, styledNode: StyledNode, blockgroup: var BlockGroup, viewport: Viewport)
+proc generateInlineBoxes(ctx: var InnerBlockContext, styledNode: StyledNode)
 
 
-proc generateFromElem(styledNode: StyledNode, blockgroup: var BlockGroup, viewport: Viewport, ibox: var InlineBoxBuilder) =
-  let box = blockgroup.parent
+proc flushTableRow(ctx: var InnerBlockContext) =
+  if ctx.anonRow != nil:
+    if ctx.blockgroup.parent.computed{"display"} == DISPLAY_TABLE_ROW:
+      ctx.blockgroup.parent.children.add(ctx.anonRow)
+    else:
+      var wrappervals = ctx.styledNode.computed.inheritProperties()
+      wrappervals.setDisplay(DISPLAY_TABLE)
+      if ctx.anonTable == nil:
+        ctx.anonTable = getTableBox(wrappervals)
+      ctx.anonTable.children.add(ctx.anonRow)
+    ctx.anonRow = nil
+
+proc flushTable(ctx: var InnerBlockContext) =
+  ctx.flushTableRow()
+  if ctx.anonTable != nil:
+    ctx.blockgroup.parent.children.add(ctx.anonTable)
+
+proc iflush(ctx: var InnerBlockContext) =
+  ctx.blockgroup.iflush(ctx.ibox)
+
+proc bflush(ctx: var InnerBlockContext) =
+  ctx.iflush()
+  ctx.blockgroup.flush()
+
+proc flush(ctx: var InnerBlockContext) =
+  ctx.blockgroup.flush()
+  ctx.flushTableRow()
+  ctx.flushTable()
+
+proc generateFromElem(ctx: var InnerBlockContext, styledNode: StyledNode) =
+  let box = ctx.blockgroup.parent
   if styledNode.node != nil:
     let elem = Element(styledNode.node)
     if elem.tagType == TAG_BR:
-      ibox = box.getTextBox()
-      ibox.newline = true
-      flush_ibox
+      ctx.ibox = box.getTextBox()
+      ctx.ibox.newline = true
+      ctx.iflush()
 
   case styledNode.computed{"display"}
   of DISPLAY_BLOCK:
-    flush_ibox
-    blockgroup.flush()
-    let childbox = styledNode.generateBlockBox(viewport)
+    ctx.flush()
+    let childbox = styledNode.generateBlockBox(ctx.viewport)
     box.children.add(childbox)
   of DISPLAY_LIST_ITEM:
-    flush_ibox
-    blockgroup.flush()
-    let childbox = getListItemBox(styledNode.computed, blockgroup.listItemCounter)
+    ctx.flush()
+    let childbox = getListItemBox(styledNode.computed, ctx.blockgroup.listItemCounter)
     if childbox.computed{"list-style-position"} == LIST_STYLE_POSITION_INSIDE:
-      childbox.content = styledNode.generateBlockBox(viewport, some(childbox.marker))
+      childbox.content = styledNode.generateBlockBox(ctx.viewport, some(childbox.marker))
       childbox.marker = nil
     else:
-      childbox.content = styledNode.generateBlockBox(viewport)
+      childbox.content = styledNode.generateBlockBox(ctx.viewport)
     box.children.add(childbox)
-    inc blockgroup.listItemCounter
+    inc ctx.blockgroup.listItemCounter
   of DISPLAY_INLINE:
-    flush_ibox
-    box.generateInlineBoxes(styledNode, blockgroup, viewport)
+    ctx.iflush()
+    ctx.generateInlineBoxes(styledNode)
   of DISPLAY_INLINE_BLOCK:
-    flush_ibox
+    ctx.iflush()
     let childbox = getInlineBlockBox(styledNode.computed)
-    childbox.content = styledNode.generateBlockBox(viewport)
-    blockgroup.add(childbox)
+    childbox.content = styledNode.generateBlockBox(ctx.viewport)
+    ctx.blockgroup.add(childbox)
   of DISPLAY_TABLE:
-    flush_ibox
-    blockgroup.flush()
-    let childbox = styledNode.generateTableBox(viewport)
+    ctx.flush()
+    let childbox = styledNode.generateTableBox(ctx.viewport)
     box.children.add(childbox)
   of DISPLAY_TABLE_ROW:
-    flush_ibox
-    blockgroup.flush()
-    let childbox = styledNode.generateTableRowBox(viewport)
-    box.children.add(childbox)
-  of DISPLAY_TABLE_ROW_GROUP:
-    flush_ibox
-    blockgroup.flush()
-    let childbox = styledNode.generateTableRowGroupBox(viewport)
-    box.children.add(childbox)
+    ctx.bflush()
+    ctx.flushTableRow()
+    let childbox = styledNode.generateTableRowBox(ctx.viewport)
+    if box.computed{"display"} in ProperTableRowParent:
+      box.children.add(childbox)
+    else:
+      if ctx.anonTable == nil:
+        var wrappervals = box.computed.inheritProperties()
+        #TODO make this an inline-table if we're in an inline context
+        wrappervals.setDisplay(DISPLAY_TABLE)
+        ctx.anonTable = getTableBox(wrappervals)
+      ctx.anonTable.children.add(childbox)
+  of DISPLAY_TABLE_ROW_GROUP, DISPLAY_TABLE_HEADER_GROUP, DISPLAY_TABLE_FOOTER_GROUP:
+    ctx.bflush()
+    ctx.flushTableRow()
+    let childbox = styledNode.generateTableRowGroupBox(ctx.viewport)
+    if box.computed{"display"} in {DISPLAY_TABLE, DISPLAY_INLINE_TABLE}:
+      box.children.add(childbox)
+    else:
+      if ctx.anonTable == nil:
+        var wrappervals = box.computed.inheritProperties()
+        #TODO make this an inline-table if we're in an inline context
+        wrappervals.setDisplay(DISPLAY_TABLE)
+        ctx.anonTable = getTableBox(wrappervals)
   of DISPLAY_TABLE_CELL:
-    flush_ibox
-    blockgroup.flush()
-    let childbox = styledNode.generateTableCellBox(viewport)
-    box.children.add(childbox)
+    ctx.bflush()
+    let childbox = styledNode.generateTableCellBox(ctx.viewport)
+    if box.computed{"display"} == DISPLAY_TABLE_ROW:
+      box.children.add(childbox)
+    else:
+      if ctx.anonRow == nil:
+        var wrappervals = box.computed.inheritProperties()
+        wrappervals.setDisplay(DISPLAY_TABLE_ROW)
+        ctx.anonRow = getTableRowBox(wrappervals)
+      ctx.anonRow.children.add(childbox)
+  of DISPLAY_INLINE_TABLE:
+    ctx.iflush()
+    let childbox = styledNode.generateTableBox(ctx.viewport)
+    ctx.blockgroup.add(childbox)
+  of DISPLAY_TABLE_CAPTION:
+    ctx.bflush()
+    ctx.flushTableRow()
+    let childbox = styledNode.generateTableCaptionBox(ctx.viewport)
+    if box.computed{"display"} in {DISPLAY_TABLE, DISPLAY_INLINE_TABLE}:
+      box.children.add(childbox)
+    else:
+      if ctx.anonTable == nil:
+        var wrappervals = box.computed.inheritProperties()
+        #TODO make this an inline-table if we're in an inline context
+        wrappervals.setDisplay(DISPLAY_TABLE)
+        ctx.anonTable = getTableBox(wrappervals)
   of DISPLAY_TABLE_COLUMN:
     discard #TODO
   of DISPLAY_TABLE_COLUMN_GROUP:
     discard #TODO
   of DISPLAY_NONE: discard
-  else:
-    discard #TODO
-
-proc generateInlineBoxes(box: BoxBuilder, styledNode: StyledNode, blockgroup: var BlockGroup, viewport: Viewport) =
-  var ibox: InlineBoxBuilder = nil
 
+proc generateInlineBoxes(ctx: var InnerBlockContext, styledNode: StyledNode) =
   for child in styledNode.children:
     case child.t
     of STYLED_ELEMENT:
-      generateFromElem(child, blockgroup, viewport, ibox)
+      ctx.generateFromElem(child)
     of STYLED_TEXT:
-      if ibox == nil:
-        ibox = getTextBox(styledNode.computed)
-        ibox.node = styledNode
-      ibox.text.add(child.text)
-
-  flush_ibox
+      if ctx.ibox == nil:
+        ctx.ibox = getTextBox(styledNode.computed)
+        ctx.ibox.node = styledNode
+      ctx.ibox.text.add(child.text)
+  ctx.iflush()
+
+proc newInnerBlockContext(styledNode: StyledNode, blockgroup: BlockGroup, viewport: Viewport): InnerBlockContext =
+  return InnerBlockContext(
+    styledNode: styledNode,
+    blockgroup: blockgroup,
+    viewport: viewport
+  )
+
+proc generateInnerBlockBox(ctx: var InnerBlockContext) =
+  let box = ctx.blockgroup.parent
+  assert box.computed{"display"} != DISPLAY_INLINE
+  for child in ctx.styledNode.children:
+    case child.t
+    of STYLED_ELEMENT:
+      ctx.iflush()
+      ctx.generateFromElem(child)
+    of STYLED_TEXT:
+      if canGenerateAnonymousInline(ctx.blockgroup, box.computed, child.text):
+        if ctx.ibox == nil:
+          ctx.ibox = getTextBox(ctx.styledNode.computed)
+          ctx.ibox.node = ctx.styledNode
+        ctx.ibox.text.add(child.text)
+  ctx.iflush()
 
 proc generateBlockBox(styledNode: StyledNode, viewport: Viewport, marker = none(MarkerBoxBuilder)): BlockBoxBuilder =
   let box = getBlockBox(styledNode.computed)
-  var blockgroup = newBlockGroup(box)
-  var ibox: InlineBoxBuilder = nil
+  var ctx = newInnerBlockContext(styledNode, newBlockGroup(box), viewport)
 
   if marker.issome:
-    ibox = marker.get
-    flush_ibox
-  
-  for child in styledNode.children:
-    case child.t
-    of STYLED_ELEMENT:
-      flush_ibox
-      generateFromElem(child, blockgroup, viewport, ibox)
-    of STYLED_TEXT:
-      if canGenerateAnonymousInline(blockgroup, box.computed, child.text):
-        if ibox == nil:
-          ibox = getTextBox(styledNode.computed)
-          ibox.node = styledNode
-        ibox.text.add(child.text)
+    ctx.ibox = marker.get
+    ctx.iflush()
 
-  flush_ibox
-  if blockgroup.boxes.len > 0:
+  ctx.generateInnerBlockBox()
+
+  if ctx.blockgroup.boxes.len > 0:
     # Avoid unnecessary anonymous block boxes
     if box.children.len == 0:
-      box.children = blockgroup.boxes
+      box.children = ctx.blockgroup.boxes
       box.inlinelayout = true
     else:
-      blockgroup.flush()
+      ctx.blockgroup.flush()
+  ctx.flushTableRow()
+  ctx.flushTable()
   return box
 
-const RowGroupBox = {DISPLAY_TABLE_ROW_GROUP, DISPLAY_TABLE_HEADER_GROUP,
-                     DISPLAY_TABLE_FOOTER_GROUP}
-const ProperTableChild = {DISPLAY_TABLE_ROW, DISPLAY_TABLE_COLUMN,
-                          DISPLAY_TABLE_COLUMN_GROUP} + RowGroupBox
-const ProperTableRowParent = {DISPLAY_TABLE, DISPLAY_INLINE_TABLE} + RowGroupBox
-const InternalTableBox = {DISPLAY_TABLE_CELL, DISPLAY_TABLE_ROW, DISPLAY_TABLE_COLUMN, DISPLAY_TABLE_COLUMN_GROUP} + RowGroupBox
-const TabularContainer = {DISPLAY_TABLE_ROW} + ProperTableRowParent
-
-# Whether an internal table box is misparented.
-func isMisparented(box: BoxBuilder, parent: BoxBuilder): bool =
-  case box.computed{"display"}
-  of DISPLAY_TABLE_ROW:
-    return parent.computed{"display"} notin {DISPLAY_TABLE_COLUMN_GROUP, DISPLAY_TABLE, DISPLAY_INLINE_TABLE}
-  of DISPLAY_TABLE_COLUMN:
-    return parent.computed{"display"} notin {DISPLAY_TABLE_COLUMN_GROUP, DISPLAY_TABLE, DISPLAY_INLINE_TABLE}
-  of RowGroupBox, DISPLAY_TABLE_COLUMN_GROUP, DISPLAY_TABLE_CAPTION:
-    return parent.computed{"display"} notin {DISPLAY_TABLE, DISPLAY_INLINE_TABLE}
-  else: assert false
-
 proc generateTableCellBox(styledNode: StyledNode, viewport: Viewport): TableCellBoxBuilder =
   let box = getTableCellBox(styledNode.computed)
   if styledNode.node != nil and styledNode.node.nodeType == ELEMENT_NODE:
     box.colspan = Element(styledNode.node).attri("colspan").get(1)
-  var blockgroup = newBlockGroup(box)
-  var ibox: InlineBoxBuilder = nil
-  for child in styledNode.children:
-    if child.t == STYLED_ELEMENT:
-      flush_ibox
-      generateFromElem(child, blockgroup, viewport, ibox)
-    else:
-      if canGenerateAnonymousInline(blockgroup, box.computed, child.text):
-        if ibox == nil:
-          ibox = getTextBox(styledNode.computed)
-          ibox.node = styledNode
-        ibox.text.add(child.text)
-  flush_ibox
-  if blockgroup.boxes.len > 0:
-    # Avoid unnecessary anonymous block boxes
-    if box.children.len == 0:
-      box.children = blockgroup.boxes
-      box.inlinelayout = true
-    else:
-      blockgroup.flush()
+  var ctx = newInnerBlockContext(styledNode, newBlockGroup(box), viewport)
+  ctx.generateInnerBlockBox()
+  ctx.flush()
   return box
 
 proc generateTableRowChildWrappers(box: TableRowBoxBuilder) =
@@ -1013,49 +1096,41 @@ proc generateTableRowChildWrappers(box: TableRowBoxBuilder) =
 
 proc generateTableRowBox(styledNode: StyledNode, viewport: Viewport): TableRowBoxBuilder =
   let box = getTableRowBox(styledNode.computed)
-  var blockgroup = newBlockGroup(box)
-  var ibox: InlineBoxBuilder = nil
-  for child in styledNode.children:
-    if child.t == STYLED_ELEMENT:
-      generateFromElem(child, blockgroup, viewport, ibox)
-    else:
-      if canGenerateAnonymousInline(blockgroup, box.computed, child.text):
-        if ibox == nil:
-          ibox = getTextBox(styledNode.computed)
-          ibox.node = styledNode
-        ibox.text.add(child.text)
+  var ctx = newInnerBlockContext(styledNode, newBlockGroup(box), viewport)
+  ctx.generateInnerBlockBox()
+  ctx.flush()
   box.generateTableRowChildWrappers()
   return box
 
 proc generateTableRowGroupChildWrappers(box: TableRowGroupBoxBuilder) =
   var newchildren = newSeqOfCap[BoxBuilder](box.children.len)
   var wrappervals = box.computed.inheritProperties()
-  wrappervals.setDisplay(DISPLAY_TABLE_CELL)
+  wrappervals.setDisplay(DISPLAY_TABLE_ROW)
   for child in box.children:
     if child.computed{"display"} == DISPLAY_TABLE_ROW:
       newchildren.add(child)
     else:
       let wrapper = getTableRowBox(wrappervals)
       wrapper.children.add(child)
+      wrapper.generateTableRowChildWrappers()
       newchildren.add(wrapper)
   box.children = newchildren
 
 proc generateTableRowGroupBox(styledNode: StyledNode, viewport: Viewport): TableRowGroupBoxBuilder =
   let box = getTableRowGroupBox(styledNode.computed)
-  var blockgroup = newBlockGroup(box)
-  var ibox: InlineBoxBuilder = nil
-  for child in styledNode.children:
-    if child.t == STYLED_ELEMENT:
-      generateFromElem(child, blockgroup, viewport, ibox)
-    else:
-      if canGenerateAnonymousInline(blockgroup, box.computed, child.text):
-        if ibox == nil:
-          ibox = getTextBox(styledNode.computed)
-          ibox.node = styledNode
-        ibox.text.add(child.text)
+  var ctx = newInnerBlockContext(styledNode, newBlockGroup(box), viewport)
+  ctx.generateInnerBlockBox()
+  ctx.flush()
   box.generateTableRowGroupChildWrappers()
   return box
 
+proc generateTableCaptionBox(styledNode: StyledNode, viewport: Viewport): TableCaptionBoxBuilder =
+  let box = getTableCaptionBox(styledNode.computed)
+  var ctx = newInnerBlockContext(styledNode, newBlockGroup(box), viewport)
+  ctx.generateInnerBlockBox()
+  ctx.flush()
+  return box
+
 proc generateTableChildWrappers(box: TableBoxBuilder) =
   var newchildren = newSeqOfCap[BoxBuilder](box.children.len)
   var wrappervals = box.computed.inheritProperties()
@@ -1072,25 +1147,10 @@ proc generateTableChildWrappers(box: TableBoxBuilder) =
 
 proc generateTableBox(styledNode: StyledNode, viewport: Viewport): TableBoxBuilder =
   let box = getTableBox(styledNode.computed)
-  var blockgroup = newBlockGroup(box) #TODO this probably shouldn't exist
-  if styledNode.node != nil and styledNode.node.nodeType == ELEMENT_NODE:
-    #TODO put this in dom or something
-    let s = Element(styledNode.node).attr("width")
-    box.width = parseDimensionValues(s)
-  var ibox: InlineBoxBuilder = nil
-  for child in styledNode.children:
-    if child.t == STYLED_ELEMENT:
-      generateFromElem(child, blockgroup, viewport, ibox)
-    else:
-      if canGenerateAnonymousInline(blockgroup, box.computed, child.text):
-        if ibox == nil:
-          ibox = getTextBox(styledNode.computed)
-          ibox.node = styledNode
-        ibox.text.add(child.text)
-  flush_ibox
-  blockgroup.flush()
+  var ctx = newInnerBlockContext(styledNode, newBlockGroup(box), viewport)
+  ctx.generateInnerBlockBox()
+  ctx.flush()
   box.generateTableChildWrappers()
-  #TODO generate missing parents
   return box
 
 proc renderLayout*(viewport: var Viewport, document: Document, root: StyledNode) =
diff --git a/src/render/renderdocument.nim b/src/render/renderdocument.nim
index a039cc71..9eebe61d 100644
--- a/src/render/renderdocument.nim
+++ b/src/render/renderdocument.nim
@@ -160,7 +160,7 @@ proc setSpacing(lines: var FlexibleGrid, spacing: InlineSpacing, x, y: int, wind
 
   lines.setText(linestr, spacing.format, x, y)
 
-proc paintBackground(lines: var FlexibleGrid, color: CSSColor, startx, starty, endx, endy: int, window: WindowAttributes) =
+proc paintBackground(lines: var FlexibleGrid, color: RGBAColor, startx, starty, endx, endy: int, window: WindowAttributes) =
   let color = color.cellColor()
 
   var starty = starty div window.ppl
diff --git a/src/types/color.nim b/src/types/color.nim
index e85a254e..9273c794 100644
--- a/src/types/color.nim
+++ b/src/types/color.nim
@@ -258,24 +258,14 @@ func parseLegacyColor*(s: string): Option[RGBColor] =
         (hexValue(s[1]) * 17 shl 8) or
         (hexValue(s[2]) * 17)
       return some(RGBColor(c))
-  block sane:
-    var c: Option[RGBAColor]
-    for c in s:
-      if hexValue(c) == -1:
-        break sane
-    if s[0] == '#' and s.len == 8:
-      c = parseHexColor(s[1..^1])
-    elif s.len == 8:
-      c = parseHexColor(s)
-    else:
-      break sane
-    if c.isSome:
-      return some(RGBColor(c.get))
   # Seriously, what the hell.
   var s2 = if s[0] == '#':
     s.substr(1)
   else:
     s
+  for i in 0 ..< s2.len:
+    if hexValue(s2[i]) == -1:
+      s2[i] = '0'
   while s2.len == 0 or s2.len mod 3 != 0:
     s2 &= '0'
   var l = s2.len div 3