about summary refs log tree commit diff stats
path: root/src/layout
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2022-11-25 23:15:49 +0100
committerbptato <nincsnevem662@gmail.com>2022-11-25 23:16:39 +0100
commitd54e0258fd794ad54b42acc51ec65d9c7297d908 (patch)
tree028ec9ce0dc9ba77d23bea2e3f86e4aabb527b4b /src/layout
parent7ab7f28fdefe503fdde53ba9e253e308cb06b44f (diff)
downloadchawan-d54e0258fd794ad54b42acc51ec65d9c7297d908.tar.gz
Fix some table layout issues
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/box.nim7
-rw-r--r--src/layout/engine.nim404
2 files changed, 238 insertions, 173 deletions
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) =