about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/css/values.nim196
-rw-r--r--src/layout/engine.nim504
2 files changed, 537 insertions, 163 deletions
diff --git a/src/css/values.nim b/src/css/values.nim
index e1c54c04..5bceed5b 100644
--- a/src/css/values.nim
+++ b/src/css/values.nim
@@ -23,6 +23,7 @@ type
     cstPadding = "padding"
     cstBackground = "background"
     cstListStyle = "list-style"
+    cstFlex = "flex"
 
   CSSUnit* = enum
     UNIT_CM, UNIT_MM, UNIT_IN, UNIT_PX, UNIT_PT, UNIT_PC, UNIT_EM, UNIT_EX,
@@ -79,6 +80,11 @@ type
     cptClear = "clear"
     cptTextTransform = "text-transform"
     cptBgcolorIsCanvas = "-cha-bgcolor-is-canvas"
+    cptFlexDirection = "flex-direction"
+    cptFlexWrap = "flex-wrap"
+    cptFlexGrow = "flex-grow"
+    cptFlexShrink = "flex-shrink"
+    cptFlexBasis = "flex-basis"
 
   CSSValueType* = enum
     cvtNone = ""
@@ -108,6 +114,9 @@ type
     cvtClear = "clear"
     cvtTextTransform = "texttransform"
     cvtBgcolorIsCanvas = "bgcoloriscanvas"
+    cvtFlexDirection = "flexdirection"
+    cvtFlexWrap = "flexwrap"
+    cvtNumber = "number"
 
   CSSGlobalValueType* = enum
     cvtNoglobal, cvtInitial, cvtInherit, cvtRevert, cvtUnset
@@ -118,7 +127,7 @@ type
     DISPLAY_TABLE_ROW_GROUP, DISPLAY_TABLE_HEADER_GROUP,
     DISPLAY_TABLE_FOOTER_GROUP, DISPLAY_TABLE_COLUMN_GROUP, DISPLAY_TABLE_ROW,
     DISPLAY_TABLE_COLUMN, DISPLAY_TABLE_CELL, DISPLAY_TABLE_CAPTION,
-    DISPLAY_FLOW_ROOT
+    DISPLAY_FLOW_ROOT, DISPLAY_FLEX, DISPLAY_INLINE_FLEX
 
   CSSWhitespace* = enum
     WHITESPACE_NORMAL, WHITESPACE_NOWRAP, WHITESPACE_PRE, WHITESPACE_PRE_LINE,
@@ -193,15 +202,28 @@ type
     TEXT_TRANSFORM_LOWERCASE, TEXT_TRANSFORM_FULL_WIDTH,
     TEXT_TRANSFORM_FULL_SIZE_KANA, TEXT_TRANSFORM_CHA_HALF_WIDTH
 
-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
+  CSSFlexDirection* = enum
+    FLEX_DIRECTION_ROW, FLEX_DIRECTION_ROW_REVERSE, FLEX_DIRECTION_COLUMN,
+    FLEX_DIRECTION_COLUMN_REVERSE
+
+  CSSFlexWrap* = enum
+    FLEX_WRAP_NOWRAP, FLEX_WRAP_WRAP, FLEX_WRAP_WRAP_REVERSE
+
+const RowGroupBox* = {
+  DISPLAY_TABLE_ROW_GROUP, DISPLAY_TABLE_HEADER_GROUP,
+  DISPLAY_TABLE_FOOTER_GROUP
+}
+const ProperTableChild* = RowGroupBox + {
+  DISPLAY_TABLE_ROW, DISPLAY_TABLE_COLUMN, DISPLAY_TABLE_COLUMN_GROUP,
+  DISPLAY_TABLE_CAPTION
+}
+const ProperTableRowParent* = RowGroupBox + {
+  DISPLAY_TABLE, DISPLAY_INLINE_TABLE
+}
+const InternalTableBox* = RowGroupBox + {
+  DISPLAY_TABLE_CELL, DISPLAY_TABLE_ROW, DISPLAY_TABLE_COLUMN,
+  DISPLAY_TABLE_COLUMN_GROUP
+}
 const TabularContainer* = {DISPLAY_TABLE_ROW} + ProperTableRowParent
 
 type
@@ -245,6 +267,8 @@ type
       whitespace*: CSSWhitespace
     of cvtInteger:
       integer*: int
+    of cvtNumber:
+      number*: float64
     of cvtTextDecoration:
       textdecoration*: set[CSSTextDecoration]
     of cvtWordBreak:
@@ -281,6 +305,10 @@ type
       texttransform*: CSSTextTransform
     of cvtBgcolorIsCanvas:
       bgcoloriscanvas*: bool
+    of cvtFlexDirection:
+      flexdirection*: CSSFlexDirection
+    of cvtFlexWrap:
+      flexwrap*: CSSFlexWrap
     of cvtNone: discard
 
   CSSComputedValues* = ref array[CSSPropertyType, CSSComputedValue]
@@ -303,13 +331,12 @@ type
     importantProperties: array[CSSOrigin, CSSComputedEntries]
     preshints*: CSSComputedValues
 
-const ShorthandNames = {
-  "all": cstAll,
-  "margin": cstMargin,
-  "padding": cstPadding,
-  "background": cstBackground,
-  "list-style": cstListStyle
-}.toTable()
+const ShorthandNames = block:
+  var tab = initTable[string, CSSShorthandType]()
+  for t in CSSShorthandType:
+    if $t != "":
+      tab[$t] = t
+  tab
 
 const PropertyNames = block:
   var tab = initTable[string, CSSPropertyType]()
@@ -318,7 +345,7 @@ const PropertyNames = block:
       tab[$t] = t
   tab
 
-const ValueTypes* = [
+const ValueTypes = [
   cptNone: cvtNone,
   cptColor: cvtColor,
   cptMarginTop: cvtLength,
@@ -367,7 +394,12 @@ const ValueTypes* = [
   cptBoxSizing: cvtBoxSizing,
   cptClear: cvtClear,
   cptTextTransform: cvtTextTransform,
-  cptBgcolorIsCanvas: cvtBgcolorIsCanvas
+  cptBgcolorIsCanvas: cvtBgcolorIsCanvas,
+  cptFlexDirection: cvtFlexDirection,
+  cptFlexWrap: cvtFlexWrap,
+  cptFlexGrow: cvtNumber,
+  cptFlexShrink: cvtNumber,
+  cptFlexBasis: cvtLength
 ]
 
 const InheritedProperties = {
@@ -475,6 +507,22 @@ func px*(l: CSSLength, window: WindowAttributes, p: LayoutUnit): LayoutUnit
   of UNIT_VMAX:
     toLayoutUnit(max(window.width_px, window.width_px) / 100 * l.num)
 
+func blockify*(display: CSSDisplay): CSSDisplay =
+  case display
+  of DISPLAY_BLOCK, DISPLAY_TABLE, DISPLAY_LIST_ITEM, DISPLAY_NONE,
+      DISPLAY_FLOW_ROOT, DISPLAY_FLEX:
+     #TODO grid
+    return display
+  of DISPLAY_INLINE, DISPLAY_INLINE_BLOCK, DISPLAY_TABLE_ROW,
+      DISPLAY_TABLE_ROW_GROUP, DISPLAY_TABLE_COLUMN,
+      DISPLAY_TABLE_COLUMN_GROUP, DISPLAY_TABLE_CELL, DISPLAY_TABLE_CAPTION,
+      DISPLAY_TABLE_HEADER_GROUP, DISPLAY_TABLE_FOOTER_GROUP:
+    return DISPLAY_BLOCK
+  of DISPLAY_INLINE_TABLE:
+    return DISPLAY_TABLE
+  of DISPLAY_INLINE_FLEX:
+    return DISPLAY_FLEX
+
 const UpperAlphaMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toRunes()
 const LowerAlphaMap = "abcdefghijklmnopqrstuvwxyz".toRunes()
 const LowerGreekMap = "αβγδεζηθικλμνξοπρστυφχψω".toRunes()
@@ -710,9 +758,11 @@ func cssColor*(val: CSSComponentValue): Opt[CellColor] =
       return parseANSI(f.value)
   return err()
 
-func isToken(cval: CSSComponentValue): bool {.inline.} = cval of CSSToken
+func isToken(cval: CSSComponentValue): bool {.inline.} =
+  cval of CSSToken
 
-func getToken(cval: CSSComponentValue): CSSToken = (CSSToken)cval
+func getToken(cval: CSSComponentValue): CSSToken {.inline.} =
+  CSSToken(cval)
 
 func cssIdent[T](map: static openArray[(string, T)], cval: CSSComponentValue):
     Opt[T] =
@@ -869,6 +919,8 @@ func cssDisplay(cval: CSSComponentValue): Opt[CSSDisplay] =
     "table-footer-group": DISPLAY_TABLE_FOOTER_GROUP,
     "table-caption": DISPLAY_TABLE_CAPTION,
     "flow-root": DISPLAY_FLOW_ROOT,
+    "flex": DISPLAY_FLEX,
+    "inline-flex": DISPLAY_INLINE_FLEX,
     "none": DISPLAY_NONE
   }
   return cssIdent(DisplayMap, cval)
@@ -1169,6 +1221,31 @@ func cssTextTransform(cval: CSSComponentValue): Opt[CSSTextTransform] =
   }
   return cssIdent(TextTransformMap, cval)
 
+func cssFlexDirection(cval: CSSComponentValue): Opt[CSSFlexDirection] =
+  const FlexDirectionMap = {
+    "row": FLEX_DIRECTION_ROW,
+    "row-reverse": FLEX_DIRECTION_ROW_REVERSE,
+    "column": FLEX_DIRECTION_COLUMN,
+    "column-reverse": FLEX_DIRECTION_COLUMN_REVERSE,
+  }
+  return cssIdent(FlexDirectionMap, cval)
+
+func cssNumber(cval: CSSComponentValue; positive: bool): Opt[float64] =
+  if isToken(cval):
+    let tok = getToken(cval)
+    if tok.tokenType == CSS_NUMBER_TOKEN:
+      if not positive or tok.nvalue >= 0:
+        return ok(tok.nvalue)
+  return err()
+
+func cssFlexWrap(cval: CSSComponentValue): Opt[CSSFlexWrap] =
+  const FlexWrapMap = {
+    "nowrap": FLEX_WRAP_NOWRAP,
+    "wrap": FLEX_WRAP_WRAP,
+    "wrap-reverse": FLEX_WRAP_WRAP_REVERSE
+  }
+  return cssIdent(FlexWrapMap, cval)
+
 proc getValueFromDecl(val: CSSComputedValue, d: CSSDeclaration,
     vtype: CSSValueType, ptype: CSSPropertyType): Err[void] =
   var i = 0
@@ -1192,6 +1269,7 @@ proc getValueFromDecl(val: CSSComputedValue, d: CSSDeclaration,
     of cptPaddingLeft, cptPaddingRight, cptPaddingTop,
        cptPaddingBottom:
       val.length = ?cssLength(cval, has_auto = false)
+    #TODO content for flex-basis
     else:
       val.length = ?cssLength(cval)
   of cvtFontStyle:
@@ -1253,6 +1331,13 @@ proc getValueFromDecl(val: CSSComputedValue, d: CSSDeclaration,
     val.texttransform = ?cssTextTransform(cval)
   of cvtBgcolorIsCanvas:
     return err() # internal value
+  of cvtFlexDirection:
+    val.flexdirection = ?cssFlexDirection(cval)
+  of cvtFlexWrap:
+    val.flexwrap = ?cssFlexWrap(cval)
+  of cvtNumber:
+    const NeedsPositive = {cptFlexGrow}
+    val.number = ?cssNumber(cval, ptype in NeedsPositive)
   of cvtNone:
     discard
   return ok()
@@ -1264,10 +1349,9 @@ func getInitialColor(t: CSSPropertyType): CellColor =
 
 func getInitialLength(t: CSSPropertyType): CSSLength =
   case t
-  of cptWidth, cptHeight, cptWordSpacing,
-     cptLineHeight, cptLeft, cptRight, cptTop,
-     cptBottom, cptMaxWidth, cptMaxHeight,
-     cptMinWidth, cptMinHeight:
+  of cptWidth, cptHeight, cptWordSpacing, cptLineHeight, cptLeft, cptRight,
+      cptTop, cptBottom, cptMaxWidth, cptMaxHeight, cptMinWidth, cptMinHeight,
+      cptFlexBasis:
     return CSSLengthAuto
   else:
     return CSSLength(auto: false, unit: UNIT_PX, num: 0)
@@ -1281,6 +1365,11 @@ func getInitialInteger(t: CSSPropertyType): int =
   else:
     return 0
 
+func getInitialNumber(t: CSSPropertyType): float64 =
+  if t == cptFlexShrink:
+    return 1
+  return 0
+
 func calcInitial(t: CSSPropertyType): CSSComputedValue =
   let v = valueType(t)
   var nv: CSSComputedValue
@@ -1297,6 +1386,8 @@ func calcInitial(t: CSSPropertyType): CSSComputedValue =
     nv = CSSComputedValue(v: v, integer: getInitialInteger(t))
   of cvtQuotes:
     nv = CSSComputedValue(v: v, quotes: CSSQuotes(auto: true))
+  of cvtNumber:
+    nv = CSSComputedValue(v: v, number: getInitialNumber(t))
   else:
     nv = CSSComputedValue(v: v)
   return nv
@@ -1421,6 +1512,8 @@ proc getComputedValues0(res: var seq[CSSComputedEntry], d: CSSDeclaration):
     var valid = true
     if global == cvtNoglobal:
       for tok in d.value:
+        if tok == CSS_WHITESPACE_TOKEN:
+          continue
         if (let r = cssListStylePosition(tok); r.isOk):
           positionVal = CSSComputedValue(
             v: cvtListStylePosition,
@@ -1438,6 +1531,42 @@ proc getComputedValues0(res: var seq[CSSComputedEntry], d: CSSDeclaration):
     if valid:
       res.add((cptListStylePosition, positionVal, global))
       res.add((cptListStyleType, typeVal, global))
+  of cstFlex:
+    let global = cssGlobal(d)
+    if global == cvtNoglobal:
+      var i = 0
+      d.value.skipWhitespace(i)
+      if i >= d.value.len:
+        return err()
+      if (let r = cssNumber(d.value[i], positive = true); r.isSome):
+        # flex-grow
+        let val = CSSComputedValue(v: cvtNumber, number: r.get)
+        res.add((cptFlexGrow, val, global))
+        d.value.skipWhitespace(i)
+        if i < d.value.len:
+          if not d.value[i].isToken:
+            return err()
+          if (let r = cssNumber(d.value[i], positive = true); r.isSome):
+            # flex-shrink
+            let val = CSSComputedValue(v: cvtNumber, number: r.get)
+            res.add((cptFlexShrink, val, global))
+            d.value.skipWhitespace(i)
+      if res.len < 1: # flex-grow omitted, default to 1
+        let val = CSSComputedValue(v: cvtNumber, number: 1)
+        res.add((cptFlexGrow, val, global))
+      if res.len < 2: # flex-shrink omitted, default to 1
+        let val = CSSComputedValue(v: cvtNumber, number: 1)
+        res.add((cptFlexShrink, val, global))
+      if i < d.value.len:
+        # flex-basis
+        let val = CSSComputedValue(v: cvtLength, length: ?cssLength(d.value[i]))
+        res.add((cptFlexBasis, val, global))
+      else: # omitted, default to 0px
+        let val = CSSComputedValue(
+          v: cvtLength,
+          length: CSSLength(unit: UNIT_PX, num: 0)
+        )
+        res.add((cptFlexGrow, val, global))
   return ok()
 
 proc getComputedValues(d: CSSDeclaration): seq[CSSComputedEntry] =
@@ -1570,16 +1699,7 @@ func buildComputedValues*(builder: CSSComputedValuesBuilder):
       else:
         result[prop] = getDefault(prop)
   if result{"float"} != FLOAT_NONE:
-    case result{"display"}
-    of DISPLAY_BLOCK, DISPLAY_TABLE, DISPLAY_LIST_ITEM, DISPLAY_NONE,
-        DISPLAY_FLOW_ROOT:
-       #TODO flex, grid
-      discard
-      {.linearScanEnd.}
-    of DISPLAY_INLINE, DISPLAY_INLINE_BLOCK, DISPLAY_TABLE_ROW,
-        DISPLAY_TABLE_ROW_GROUP, DISPLAY_TABLE_COLUMN,
-        DISPLAY_TABLE_COLUMN_GROUP, DISPLAY_TABLE_CELL, DISPLAY_TABLE_CAPTION,
-        DISPLAY_TABLE_HEADER_GROUP, DISPLAY_TABLE_FOOTER_GROUP:
-      result{"display"} = DISPLAY_BLOCK
-    of DISPLAY_INLINE_TABLE:
-      result{"display"} = DISPLAY_TABLE
+    #TODO it may be better to handle this in layout
+    let display = result{"display"}.blockify()
+    if display != result{"display"}:
+      result{"display"} = display
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index ca5e506f..a32decf0 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -88,18 +88,10 @@ type
 
   TableCaptionBoxBuilder = ref object of BlockBoxBuilder
 
-#TODO ?
-func stretch(sc: SizeConstraint): SizeConstraint =
-  case sc.t
-  of MIN_CONTENT, MAX_CONTENT:
-    return sc
-  of STRETCH, FIT_CONTENT:
-    return SizeConstraint(t: STRETCH, u: sc.u)
-
 func fitContent(sc: SizeConstraint): SizeConstraint =
   case sc.t
   of MIN_CONTENT, MAX_CONTENT:
-    return SizeConstraint(t: sc.t)
+    return sc
   of STRETCH, FIT_CONTENT:
     return SizeConstraint(t: FIT_CONTENT, u: sc.u)
 
@@ -1133,12 +1125,12 @@ proc resolveFloatSizes(lctx: LayoutState, containingWidth,
 # differs for the root height (TODO: and all heights in quirks mode) in that
 # it uses the lctx height. Therefore we pass percHeight as a separate
 # parameter. (TODO surely there is a better solution to this?)
-proc resolveSizes(lctx: LayoutState, containingWidth,
-    containingHeight: SizeConstraint, percHeight: Option[LayoutUnit],
-    computed: CSSComputedValues): ResolvedSizes =
+proc resolveSizes(lctx: LayoutState; containingWidth,
+    containingHeight: SizeConstraint; percHeight: Option[LayoutUnit];
+    computed: CSSComputedValues; flexItem = false): ResolvedSizes =
   if computed{"position"} == POSITION_ABSOLUTE:
     return lctx.resolveAbsoluteSizes(computed)
-  elif computed{"float"} != FLOAT_NONE:
+  elif computed{"float"} != FLOAT_NONE or flexItem:
     return lctx.resolveFloatSizes(containingWidth, containingHeight,
       percHeight, computed)
   else:
@@ -1159,12 +1151,14 @@ proc append(a: var Strut, b: LayoutUnit) =
 func sum(a: Strut): LayoutUnit =
   return a.pos + a.neg
 
-proc layoutRootInline(bctx: var BlockContext, inlines: seq[BoxBuilder],
-  space: AvailableSpace, computed: CSSComputedValues, offset,
-  bfcOffset: Offset): RootInlineFragment
-proc layoutBlock(bctx: var BlockContext, box: BlockBox,
-  builder: BlockBoxBuilder, sizes: ResolvedSizes)
-proc layoutTable(lctx: LayoutState, table: BlockBox, builder: TableBoxBuilder,
+proc layoutRootInline(bctx: var BlockContext; inlines: seq[BoxBuilder];
+  space: AvailableSpace; computed: CSSComputedValues;
+  offset, bfcOffset: Offset): RootInlineFragment
+proc layoutBlock(bctx: var BlockContext; box: BlockBox;
+  builder: BlockBoxBuilder; sizes: ResolvedSizes)
+proc layoutTable(lctx: LayoutState; table: BlockBox; builder: TableBoxBuilder;
+  sizes: ResolvedSizes)
+proc layoutFlex(bctx: var BlockContext; box: BlockBox; builder: BlockBoxBuilder;
   sizes: ResolvedSizes)
 
 # Note: padding must still be applied after this.
@@ -1335,14 +1329,11 @@ func establishesBFC(computed: CSSComputedValues): bool =
   return computed{"float"} != FLOAT_NONE or
     computed{"position"} == POSITION_ABSOLUTE or
     computed{"display"} in {DISPLAY_INLINE_BLOCK, DISPLAY_FLOW_ROOT} +
-      InternalTableBox
-    #TODO overflow, contain, flex, grid, multicol, column-span
+      InternalTableBox + {DISPLAY_FLEX, DISPLAY_INLINE_FLEX}
+    #TODO overflow, contain, grid, multicol, column-span
 
-proc layoutFlow(bctx: var BlockContext, box: BlockBox, builder: BlockBoxBuilder,
+proc layoutFlow(bctx: var BlockContext; box: BlockBox; builder: BlockBoxBuilder;
     sizes: ResolvedSizes) =
-  let isBfc = builder.computed.establishesBFC()
-  if not isBfc:
-    bctx.marginTodo.append(sizes.margin.top)
   if builder.canFlushMargins(sizes):
     bctx.flushMargins(box)
     bctx.positionFloats()
@@ -1354,8 +1345,6 @@ proc layoutFlow(bctx: var BlockContext, box: BlockBox, builder: BlockBoxBuilder,
   else:
     # Builder only contains block boxes.
     bctx.layoutBlock(box, builder, sizes)
-  if not isBfc:
-    bctx.marginTodo.append(sizes.margin.bottom)
 
 func toperc100(sc: SizeConstraint): Option[LayoutUnit] =
   if sc.isDefinite():
@@ -1381,6 +1370,8 @@ proc addInlineBlock(ictx: var InlineContext, state: var InlineState,
     bctx.layoutFlow(box, builder, sizes)
   of DISPLAY_INLINE_TABLE:
     lctx.layoutTable(box, TableBoxBuilder(builder), sizes)
+  of DISPLAY_INLINE_FLEX:
+    bctx.layoutFlex(box, builder, sizes)
   else:
     assert false, $builder.computed{"display"}
   bctx.positionFloats()
@@ -1451,7 +1442,7 @@ proc layoutInline(ictx: var InlineContext; box: InlineBoxBuilder):
     of DISPLAY_INLINE:
       let child = ictx.layoutInline(InlineBoxBuilder(child))
       state.fragment.children.add(child)
-    of DISPLAY_INLINE_BLOCK, DISPLAY_INLINE_TABLE:
+    of DISPLAY_INLINE_BLOCK, DISPLAY_INLINE_TABLE, DISPLAY_INLINE_FLEX:
       # Note: we do not need a separate inline fragment here, because the tree
       # generator already does an iflush() before adding inline blocks.
       let w = fitContent(ictx.space.w)
@@ -1519,28 +1510,9 @@ proc layoutRootInline(bctx: var BlockContext, inlines: seq[BoxBuilder],
   root.xminwidth = ictx.minwidth
   return root
 
-proc buildMarker(builder: MarkerBoxBuilder, space: AvailableSpace,
-    lctx: LayoutState): RootInlineFragment =
-  let space = AvailableSpace(
-    w: fitContent(space.w),
-    h: space.h
-  )
-  #TODO we should put markers right before the first atom of the parent
-  # list item or something...
-  var bctx = BlockContext(lctx: lctx)
-  let children = @[BoxBuilder(builder)]
-  return bctx.layoutRootInline(children, space, builder.computed, Offset(),
-    Offset())
-
 # Build a block box without establishing a new block formatting context.
-proc buildBlock(bctx: var BlockContext, builder: BlockBoxBuilder,
-    space: AvailableSpace, offset: Offset): BlockBox =
-  let lctx = bctx.lctx
-  let availableWidth = space.w
-  let availableHeight = maxContent() #TODO fit-content when clip
-  let percHeight = space.h.toPercSize()
-  let sizes = lctx.resolveSizes(availableWidth, availableHeight, percHeight,
-    builder.computed)
+proc buildBlock(bctx: var BlockContext; builder: BlockBoxBuilder;
+    sizes: ResolvedSizes; offset: Offset): BlockBox =
   let box = BlockBox(
     computed: builder.computed,
     node: builder.node,
@@ -1550,14 +1522,8 @@ proc buildBlock(bctx: var BlockContext, builder: BlockBoxBuilder,
   bctx.layoutFlow(box, builder, sizes)
   return box
 
-proc buildListItem(bctx: var BlockContext, builder: ListItemBoxBuilder,
-    space: AvailableSpace, offset: Offset): ListItemBox =
-  let availableWidth = stretch(space.w)
-  let availableHeight = maxContent() #TODO fit-content when clip
-  let percHeight = space.h.toPercSize()
-  let lctx = bctx.lctx
-  let sizes = lctx.resolveSizes(availableWidth, availableHeight, percHeight,
-    builder.computed)
+proc buildListItem(bctx: var BlockContext; builder: ListItemBoxBuilder;
+    sizes: ResolvedSizes; offset: Offset): ListItemBox =
   let box = ListItemBox(
     computed: builder.computed,
     node: builder.node,
@@ -1565,31 +1531,25 @@ proc buildListItem(bctx: var BlockContext, builder: ListItemBoxBuilder,
     margin: sizes.margin
   )
   if builder.marker != nil:
-    box.marker = buildMarker(builder.marker, sizes.space, lctx)
+    #TODO we should put markers right before the first atom of the parent
+    # list item or something...
+    var bctx = BlockContext(lctx: bctx.lctx)
+    let children = @[BoxBuilder(builder.marker)]
+    let space = AvailableSpace(w: fitContent(sizes.space.w), h: sizes.space.h)
+    box.marker = bctx.layoutRootInline(children, space, builder.marker.computed,
+      Offset(), Offset())
   bctx.layoutFlow(box, builder.content, sizes)
   return box
 
-proc buildTable(bctx: var BlockContext, builder: TableBoxBuilder,
-    space: AvailableSpace, offset: Offset): BlockBox =
-  let availableWidth = fitContent(space.w)
-  let availableHeight = maxContent() #TODO fit-content when clip
-  let percHeight = space.h.toPercSize()
-  let lctx = bctx.lctx
-  let sizes = lctx.resolveSizes(availableWidth, availableHeight, percHeight,
-    builder.computed)
+proc buildTable(bctx: var BlockContext; builder: TableBoxBuilder;
+    sizes: ResolvedSizes; offset: Offset): BlockBox =
   let box = BlockBox(
     computed: builder.computed,
     node: builder.node,
     offset: Offset(x: offset.x + sizes.margin.left, y: offset.y),
     margin: sizes.margin
   )
-  let isBfc = builder.computed.establishesBFC()
-  if not isBfc:
-    bctx.marginTodo.append(sizes.margin.top)
-  bctx.flushMargins(box)
-  lctx.layoutTable(box, builder, sizes)
-  if not isBfc:
-    bctx.marginTodo.append(sizes.margin.bottom)
+  bctx.lctx.layoutTable(box, builder, sizes)
   return box
 
 proc positionAbsolute(lctx: LayoutState, box: BlockBox, margin: RelativeRect) =
@@ -1974,9 +1934,8 @@ proc calcUnspecifiedColIndices(ctx: var TableContext, W: var LayoutUnit,
     weight: var float64): seq[int] =
   # Spacing for each column:
   var avail = newSeqUninitialized[int](ctx.cols.len)
-  var i = 0
   var j = 0
-  while i < ctx.cols.len:
+  for i in 0 ..< ctx.cols.len:
     if not ctx.cols[i].wspecified:
       avail[j] = i
       let colw = ctx.cols[i].width
@@ -1990,7 +1949,6 @@ proc calcUnspecifiedColIndices(ctx: var TableContext, W: var LayoutUnit,
     else:
       W -= ctx.cols[i].width
       avail.del(j)
-    inc i
   return avail
 
 func needsRedistribution(ctx: TableContext, computed: CSSComputedValues): bool =
@@ -2121,36 +2079,295 @@ proc postAlignChild(box, child: BlockBox, width: LayoutUnit) =
   else:
     discard
 
-# Build an outer block box inside an existing block formatting context.
-proc layoutBlockChild(bctx: var BlockContext, builder: BoxBuilder,
-    space: AvailableSpace, offset: Offset): BlockBox =
-  let child = case builder.computed{"display"}
+proc buildFlex(bctx: var BlockContext; builder: BlockBoxBuilder;
+    sizes: ResolvedSizes; offset: Offset): BlockBox =
+  let box = BlockBox(
+    computed: builder.computed,
+    node: builder.node,
+    offset: Offset(x: offset.x + sizes.margin.left, y: offset.y),
+    margin: sizes.margin
+  )
+  bctx.layoutFlex(box, builder, sizes)
+  return box
+
+proc layoutFlexChild(lctx: LayoutState; builder: BoxBuilder;
+    sizes: ResolvedSizes): BlockBox =
+  var bctx = BlockContext(lctx: lctx)
+  # note: we do not append margins here, since those belong to the flex item,
+  # not its inner BFC.
+  var offset = Offset()
+  let box = case builder.computed{"display"}
   of DISPLAY_BLOCK, DISPLAY_FLOW_ROOT:
-    bctx.buildBlock(BlockBoxBuilder(builder), space, offset)
+    bctx.buildBlock(BlockBoxBuilder(builder), sizes, offset)
   of DISPLAY_LIST_ITEM:
-    bctx.buildListItem(ListItemBoxBuilder(builder), space, offset)
+    bctx.buildListItem(ListItemBoxBuilder(builder), sizes, offset)
   of DISPLAY_TABLE:
-    bctx.buildTable(TableBoxBuilder(builder), space, offset)
+    bctx.buildTable(TableBoxBuilder(builder), sizes, offset)
+  of DISPLAY_FLEX:
+    bctx.buildFlex(BlockBoxBuilder(builder), sizes, offset)
   else:
     assert false, "builder.t is " & $builder.computed{"display"}
     BlockBox(nil)
-  return child
+  return box
 
-# Establish a new block formatting context and build a block box.
-proc layoutRootBlock(lctx: LayoutState, builder: BoxBuilder,
-    space: AvailableSpace, offset: Offset, marginBottomOut: var LayoutUnit):
-    BlockBox =
-  var bctx = BlockContext(lctx: lctx)
+type
+  FlexWeightType = enum
+    fwtGrow, fwtShrink
+
+  FlexPendingItem = object
+    child: BlockBox
+    builder: BoxBuilder
+    weights: array[FlexWeightType, float64]
+    space: AvailableSpace
+    sizes: ResolvedSizes
+
+  FlexMainContext = object
+    offset: Offset
+    totalSize: Size
+    maxSize: Size
+    totalWeight: array[FlexWeightType, float64]
+    lctx: LayoutState
+    pending: seq[FlexPendingItem]
+
+const FlexReverse = {FLEX_DIRECTION_ROW_REVERSE, FLEX_DIRECTION_COLUMN_REVERSE}
+const FlexRow = {FLEX_DIRECTION_ROW, FLEX_DIRECTION_ROW_REVERSE}
+
+proc redistributeWidth(mctx: var FlexMainContext; sizes: ResolvedSizes) =
+  #TODO actually use flex-basis
+  let lctx = mctx.lctx
+  if sizes.space.w.isDefinite:
+    var diff = sizes.space.w.u - mctx.totalSize.w
+    let wt = if diff > 0: fwtGrow else: fwtShrink
+    var totalWeight = mctx.totalWeight[wt]
+    while (wt == fwtGrow and diff > 0 or wt == fwtShrink and diff < 0) and
+        totalWeight > 0:
+      mctx.maxSize.h = 0 # redo maxSize calculation; we only need height here
+      let unit = diff / totalWeight
+      # reset total weight & available diff for the next iteration (if there is one)
+      totalWeight = 0
+      diff = 0
+      for it in mctx.pending.mitems:
+        let builder = it.builder
+        if it.weights[wt] == 0:
+          mctx.maxSize.h = max(mctx.maxSize.h, it.child.size.h)
+          continue
+        var w = it.child.size.w + unit * it.weights[wt]
+        # check for min/max violation
+        let minw = max(it.child.xminwidth, it.sizes.minWidth)
+        if minw > w:
+          # min violation
+          if wt == fwtShrink: # freeze
+            diff += w - minw
+            it.weights[wt] = 0
+          w = minw
+        let maxw = it.sizes.maxWidth
+        if maxw < w:
+          # max violation
+          if wt == fwtGrow: # freeze
+            diff += w - maxw
+            it.weights[wt] = 0
+          w = maxw
+        it.space.w = stretch(w)
+        it.sizes = lctx.resolveSizes(it.space.w, it.space.h,
+          it.space.h.toPercSize(), builder.computed)
+        totalWeight += it.weights[wt]
+        #TODO we should call this only on freeze, and then put another loop to
+        # the end for non-freezed items
+        it.child = lctx.layoutFlexChild(builder, it.sizes)
+        mctx.maxSize.h = max(mctx.maxSize.h, it.child.size.h)
+
+proc redistributeHeight(mctx: var FlexMainContext; sizes: ResolvedSizes) =
+  let lctx = mctx.lctx
+  if sizes.space.h.isDefinite and mctx.totalSize.h != sizes.space.h.u:
+    var diff = sizes.space.h.u - mctx.totalSize.h
+    let wt = if diff > 0: fwtGrow else: fwtShrink
+    var totalWeight = mctx.totalWeight[wt]
+    while (wt == fwtGrow and diff > 0 or wt == fwtShrink and diff < 0) and
+        totalWeight > 0:
+      mctx.maxSize.w = 0 # redo maxSize calculation; we only need height here
+      let unit = diff / totalWeight
+      # reset total weight & available diff for the next iteration (if there is one)
+      totalWeight = 0
+      diff = 0
+      for it in mctx.pending.mitems:
+        let builder = it.builder
+        if it.weights[wt] == 0:
+          mctx.maxSize.w = max(mctx.maxSize.w, it.child.size.w)
+          continue
+        var h = max(it.child.size.h + unit * it.weights[wt], 0)
+        # check for min/max violation
+        let minh = it.sizes.minHeight
+        if minh > h:
+          # min violation
+          if wt == fwtShrink: # freeze
+            diff += h - minh
+            it.weights[wt] = 0
+          h = minh
+        let maxh = it.sizes.maxHeight
+        if maxh < h:
+          # max violation
+          if wt == fwtGrow: # freeze
+            diff += h - maxh
+            it.weights[wt] = 0
+          h = maxh
+        it.space.h = stretch(h)
+        it.sizes = lctx.resolveSizes(it.space.w, it.space.h,
+          it.space.h.toPercSize(), builder.computed)
+        totalWeight += it.weights[wt]
+        it.child = lctx.layoutFlexChild(builder, it.sizes)
+        mctx.maxSize.h = max(mctx.maxSize.h, it.child.size.h)
+
+proc flushRow(mctx: var FlexMainContext; box: BlockBox; sizes: ResolvedSizes;
+    totalMaxSize: var Size) =
+  let lctx = mctx.lctx
+  mctx.redistributeWidth(sizes)
+  let h = stretch(mctx.maxSize.h)
+  var offset = mctx.offset
+  for it in mctx.pending.mitems:
+    if it.child.size.h < mctx.maxSize.h and not it.sizes.space.h.isDefinite:
+      # if the max height is greater than our height, then take max height
+      # instead. (if the box's available height is definite, then this will
+      # change nothing, so we skip it as an optimization.)
+      it.sizes.space.h = h
+      it.child = lctx.layoutFlexChild(it.builder, it.sizes)
+    it.child.offset = Offset(
+      x: it.child.offset.x + offset.x,
+      # margins are added here, since they belong to the flex item.
+      y: it.child.offset.y + offset.y + it.child.margin.top +
+        it.child.margin.bottom
+    )
+    offset.x += it.child.size.w
+    box.nested.add(it.child)
+  totalMaxSize.w = max(totalMaxSize.w, offset.x)
+  mctx = FlexMainContext(
+    lctx: mctx.lctx,
+    offset: Offset(
+      x: mctx.offset.x,
+      y: mctx.offset.y + mctx.maxSize.h
+    )
+  )
+
+proc flushColumn(mctx: var FlexMainContext; box: BlockBox;
+    sizes: ResolvedSizes; totalMaxSize: var Size) =
+  let lctx = mctx.lctx
+  mctx.redistributeHeight(sizes)
+  let w = stretch(mctx.maxSize.w)
+  var offset = mctx.offset
+  for it in mctx.pending.mitems:
+    if it.child.size.w < mctx.maxSize.w and not it.sizes.space.w.isDefinite:
+      # see above.
+      it.sizes.space.w = w
+      it.child = lctx.layoutFlexChild(it.builder, it.sizes)
+    # margins belong to the flex item, and influence its positioning
+    offset.y += it.child.margin.top
+    it.child.offset = Offset(
+      x: it.child.offset.x + offset.x,
+      y: it.child.offset.y + offset.y
+    )
+    offset.y += it.child.margin.bottom
+    offset.y += it.child.size.h
+    box.nested.add(it.child)
+  totalMaxSize.h = max(totalMaxSize.h, offset.y)
+  mctx = FlexMainContext(
+    lctx: lctx,
+    offset: Offset(
+      x: mctx.offset.x + mctx.maxSize.w,
+      y: mctx.offset.y
+    )
+  )
+
+proc layoutFlex(bctx: var BlockContext; box: BlockBox; builder: BlockBoxBuilder;
+    sizes: ResolvedSizes) =
+  assert not builder.inlinelayout
+  let lctx = bctx.lctx
+  var i = 0
+  var mctx = FlexMainContext(lctx: lctx)
+  let flexDir = builder.computed{"flex-direction"}
+  let children = if builder.computed{"flex-direction"} in FlexReverse:
+    builder.children.reversed()
+  else:
+    builder.children
+  var totalMaxSize = Size() #TODO find a better name for this
+  let canWrap = box.computed{"flex-wrap"} != FLEX_WRAP_NOWRAP
+  let percHeight = sizes.space.h.toPercSize()
+  while i < children.len:
+    let builder = children[i]
+    let childSizes = lctx.resolveFloatSizes(sizes.space.w, sizes.space.h,
+      percHeight, builder.computed)
+    let child = lctx.layoutFlexChild(builder, childSizes)
+    if flexDir in FlexRow:
+      if canWrap and (sizes.space.w.t == MIN_CONTENT or
+          sizes.space.w.isDefinite and
+          mctx.totalSize.w + child.size.w > sizes.space.w.u):
+        mctx.flushRow(box, sizes, totalMaxSize)
+      mctx.totalSize.w += child.size.w
+    else:
+      if canWrap and (sizes.space.h.t == MIN_CONTENT or
+          sizes.space.h.isDefinite and
+          mctx.totalSize.h + child.size.h > sizes.space.h.u):
+        mctx.flushRow(box, sizes, totalMaxSize)
+      mctx.totalSize.h += child.size.h
+    mctx.maxSize.w = max(mctx.maxSize.w, child.size.w)
+    mctx.maxSize.h = max(mctx.maxSize.h, child.size.h)
+    let grow = builder.computed{"flex-grow"}
+    let shrink = builder.computed{"flex-shrink"}
+    mctx.totalWeight[fwtGrow] += grow
+    mctx.totalWeight[fwtShrink] += shrink
+    mctx.pending.add(FlexPendingItem(
+      child: child,
+      builder: builder,
+      weights: [grow, shrink],
+      space: sizes.space,
+      sizes: childSizes
+    ))
+    inc i # need to increment index here for needsGrow
+  if flexDir in FlexRow:
+    if mctx.pending.len > 0:
+      mctx.flushRow(box, sizes, totalMaxSize)
+    box.applyWidth(sizes, totalMaxSize.w)
+    box.applyHeight(sizes, mctx.offset.y)
+  else:
+    if mctx.pending.len > 0:
+      mctx.flushColumn(box, sizes, totalMaxSize)
+    box.applyWidth(sizes, mctx.offset.x)
+    box.applyHeight(sizes, totalMaxSize.h)
+
+# Build an outer block box inside an existing block formatting context.
+proc layoutBlockChild(bctx: var BlockContext; builder: BoxBuilder;
+    space: AvailableSpace; offset: Offset; appendMargins: bool): BlockBox =
+  let availHeight = maxContent() #TODO also fit-content when clip
+  let availWidth = if builder.computed{"display"} == DISPLAY_TABLE:
+    fitContent(space.w)
+  else:
+    space.w
+  let sizes = bctx.lctx.resolveSizes(availWidth, availHeight,
+    space.h.toPercSize(), builder.computed)
+  if appendMargins:
+    # for nested blocks that do not establish their own BFC, and thus take part
+    # in margin collapsing.
+    bctx.marginTodo.append(sizes.margin.top)
   let box = case builder.computed{"display"}
   of DISPLAY_BLOCK, DISPLAY_FLOW_ROOT:
-    bctx.buildBlock(BlockBoxBuilder(builder), space, offset)
+    bctx.buildBlock(BlockBoxBuilder(builder), sizes, offset)
   of DISPLAY_LIST_ITEM:
-    bctx.buildListItem(ListItemBoxBuilder(builder), space, offset)
+    bctx.buildListItem(ListItemBoxBuilder(builder), sizes, offset)
   of DISPLAY_TABLE:
-    bctx.buildTable(TableBoxBuilder(builder), space, offset)
+    bctx.buildTable(TableBoxBuilder(builder), sizes, offset)
+  of DISPLAY_FLEX:
+    bctx.buildFlex(BlockBoxBuilder(builder), sizes, offset)
   else:
     assert false, "builder.t is " & $builder.computed{"display"}
     BlockBox(nil)
+  if appendMargins:
+    bctx.marginTodo.append(sizes.margin.bottom)
+  return box
+
+# Establish a new block formatting context and build a block box.
+proc layoutRootBlock(lctx: LayoutState; builder: BoxBuilder;
+    space: AvailableSpace; offset: Offset; marginBottomOut: var LayoutUnit):
+    BlockBox =
+  var bctx = BlockContext(lctx: lctx)
+  let box = bctx.layoutBlockChild(builder, space, offset, appendMargins = false)
   bctx.positionFloats()
   marginBottomOut = bctx.marginTodo.sum()
   # If the highest float edge is higher than the box itself, set that as
@@ -2227,7 +2444,8 @@ proc layoutBlockChildren(state: var BlockState, bctx: var BlockContext,
       # of margin todo in bctx2 (margin-bottom) + height.
       dy = child.offset.y - state.offset.y + child.size.h + marginBottomOut
     else:
-      child = bctx.layoutBlockChild(builder, state.space, state.offset)
+      child = bctx.layoutBlockChild(builder, state.space, state.offset,
+        appendMargins = true)
       # delta y is difference between old and new offsets (margin-top),
       # plus height.
       dy = child.offset.y - state.offset.y + child.size.h
@@ -2406,7 +2624,7 @@ proc add(blockgroup: var BlockGroup, box: BoxBuilder) {.inline.} =
     DISPLAY_INLINE_BLOCK}, $box.computed{"display"}
   blockgroup.boxes.add(box)
 
-proc flush(blockgroup: var BlockGroup) {.inline.} =
+proc flush(blockgroup: var BlockGroup) =
   if blockgroup.boxes.len > 0:
     assert blockgroup.parent.computed{"display"} != DISPLAY_INLINE
     let computed = blockgroup.parent.computed.inheritProperties()
@@ -2426,7 +2644,7 @@ func canGenerateAnonymousInline(blockgroup: BlockGroup,
 
 proc newBlockGroup(parent: BlockBoxBuilder): BlockGroup =
   assert parent.computed{"display"} != DISPLAY_INLINE
-  result.parent = parent
+  return BlockGroup(parent: parent)
 
 proc generateTableBox(styledNode: StyledNode, lctx: LayoutState,
   parent: var InnerBlockContext): TableBoxBuilder
@@ -2441,12 +2659,18 @@ proc generateTableCaptionBox(styledNode: StyledNode, lctx: LayoutState,
 proc generateBlockBox(styledNode: StyledNode, lctx: LayoutState,
   marker = none(MarkerBoxBuilder), parent: ptr InnerBlockContext = nil):
   BlockBoxBuilder
+proc generateFlexBox(styledNode: StyledNode; lctx: LayoutState;
+  parent: ptr InnerBlockContext = nil): BlockBoxBuilder
 proc generateInlineBoxes(ctx: var InnerBlockContext, styledNode: StyledNode)
 
-proc generateBlockBox(pctx: var InnerBlockContext, styledNode: StyledNode,
+proc generateBlockBox(pctx: var InnerBlockContext; styledNode: StyledNode;
     marker = none(MarkerBoxBuilder)): BlockBoxBuilder =
   return generateBlockBox(styledNode, pctx.lctx, marker, addr pctx)
 
+proc generateFlexBox(pctx: var InnerBlockContext; styledNode: StyledNode):
+    BlockBoxBuilder =
+  return generateFlexBox(styledNode, pctx.lctx, addr pctx)
+
 proc flushTableRow(ctx: var InnerBlockContext) =
   if ctx.anonRow != nil:
     if ctx.blockgroup.parent.computed{"display"} == DISPLAY_TABLE_ROW:
@@ -2502,15 +2726,19 @@ proc reconstructInlineParents(ctx: var InnerBlockContext): InlineBoxBuilder =
     parent = nbox
   return parent
 
-proc generateFromElem(ctx: var InnerBlockContext, styledNode: StyledNode) =
+proc generateFromElem(ctx: var InnerBlockContext; styledNode: StyledNode) =
   let box = ctx.blockgroup.parent
-
   case styledNode.computed{"display"}
   of DISPLAY_BLOCK, DISPLAY_FLOW_ROOT:
     ctx.iflush()
     ctx.flush()
     let childbox = ctx.generateBlockBox(styledNode)
     box.children.add(childbox)
+  of DISPLAY_FLEX:
+    ctx.iflush()
+    ctx.flush()
+    let childbox = ctx.generateFlexBox(styledNode)
+    box.children.add(childbox)
   of DISPLAY_LIST_ITEM:
     ctx.flush()
     inc ctx.listItemCounter
@@ -2528,7 +2756,7 @@ proc generateFromElem(ctx: var InnerBlockContext, styledNode: StyledNode) =
     box.children.add(childbox)
   of DISPLAY_INLINE:
     ctx.generateInlineBoxes(styledNode)
-  of DISPLAY_INLINE_BLOCK:
+  of DISPLAY_INLINE_BLOCK, DISPLAY_INLINE_TABLE, DISPLAY_INLINE_FLEX:
     # create a new inline box that we can safely put our inline block into
     ctx.iflush()
     let computed = styledNode.computed.inheritProperties()
@@ -2539,7 +2767,14 @@ proc generateFromElem(ctx: var InnerBlockContext, styledNode: StyledNode) =
       ctx.iroot = iparent
     else:
       ctx.iroot = ctx.ibox
-    let childbox = ctx.generateBlockBox(styledNode)
+    var childbox: BoxBuilder
+    if styledNode.computed{"display"} == DISPLAY_INLINE_BLOCK:
+      childbox = ctx.generateBlockBox(styledNode)
+    elif styledNode.computed{"display"} == DISPLAY_INLINE_TABLE:
+      childbox = styledNode.generateTableBox(ctx.lctx, ctx)
+    else:
+      assert styledNode.computed{"display"} == DISPLAY_INLINE_FLEX
+      childbox = ctx.generateFlexBox(styledNode)
     ctx.ibox.children.add(childbox)
     ctx.iflush()
   of DISPLAY_TABLE:
@@ -2584,20 +2819,6 @@ proc generateFromElem(ctx: var InnerBlockContext, styledNode: StyledNode) =
         wrappervals{"display"} = DISPLAY_TABLE_ROW
         ctx.anonRow = TableRowBoxBuilder(computed: wrappervals)
       ctx.anonRow.children.add(childbox)
-  of DISPLAY_INLINE_TABLE:
-    # create a new inline box that we can safely put our inline block into
-    ctx.iflush()
-    let computed = styledNode.computed.inheritProperties()
-    ctx.ibox = InlineBoxBuilder(computed: computed, node: styledNode)
-    if ctx.inlineStack.len > 0:
-      let iparent = ctx.reconstructInlineParents()
-      iparent.children.add(ctx.ibox)
-      ctx.iroot = iparent
-    else:
-      ctx.iroot = ctx.ibox
-    let childbox = styledNode.generateTableBox(ctx.lctx, ctx)
-    ctx.ibox.children.add(childbox)
-    ctx.iflush()
   of DISPLAY_TABLE_CAPTION:
     ctx.bflush()
     ctx.flushTableRow()
@@ -2703,19 +2924,20 @@ proc generateInlineBoxes(ctx: var InnerBlockContext, styledNode: StyledNode) =
 
 proc newInnerBlockContext(styledNode: StyledNode, box: BlockBoxBuilder,
     lctx: LayoutState, parent: ptr InnerBlockContext): InnerBlockContext =
-  result = InnerBlockContext(
+  var ctx = InnerBlockContext(
     styledNode: styledNode,
     blockgroup: newBlockGroup(box),
     lctx: lctx,
     parent: parent
   )
   if parent != nil:
-    result.listItemCounter = parent[].listItemCounter
-    result.quoteLevel = parent[].quoteLevel
+    ctx.listItemCounter = parent[].listItemCounter
+    ctx.quoteLevel = parent[].quoteLevel
   for reset in styledNode.computed{"counter-reset"}:
     if reset.name == "list-item":
-      result.listItemCounter = reset.num
-      result.listItemReset = true
+      ctx.listItemCounter = reset.num
+      ctx.listItemReset = true
+  return ctx
 
 proc generateInnerBlockBox(ctx: var InnerBlockContext) =
   let box = ctx.blockgroup.parent
@@ -2732,25 +2954,21 @@ proc generateInnerBlockBox(ctx: var InnerBlockContext) =
       ctx.generateReplacement(child, ctx.styledNode)
   ctx.iflush()
 
-proc generateBlockBox(styledNode: StyledNode, lctx: LayoutState,
-    marker = none(MarkerBoxBuilder), parent: ptr InnerBlockContext = nil):
+proc generateBlockBox(styledNode: StyledNode; lctx: LayoutState;
+    marker = none(MarkerBoxBuilder); parent: ptr InnerBlockContext = nil):
     BlockBoxBuilder =
   let box = BlockBoxBuilder(computed: styledNode.computed)
   box.node = styledNode
   var ctx = newInnerBlockContext(styledNode, box, lctx, parent)
-
   if marker.isSome:
     ctx.ibox = marker.get
     ctx.iflush()
-
   ctx.generateInnerBlockBox()
-
   # Flush anonymous tables here, to avoid setting inline layout with tables.
   ctx.flushTableRow()
   ctx.flushTable()
   # (flush here, because why not)
   ctx.flushInherit()
-
   # Avoid unnecessary anonymous block boxes. This also helps set our layout to
   # inline even if no inner anonymous block was generated.
   if box.children.len == 0:
@@ -2760,6 +2978,42 @@ proc generateBlockBox(styledNode: StyledNode, lctx: LayoutState,
   ctx.blockgroup.flush()
   return box
 
+proc generateFlexBox(styledNode: StyledNode; lctx: LayoutState;
+    parent: ptr InnerBlockContext = nil): BlockBoxBuilder =
+  let box = BlockBoxBuilder(computed: styledNode.computed, node: styledNode)
+  var ctx = newInnerBlockContext(styledNode, box, lctx, parent)
+  assert box.computed{"display"} != DISPLAY_INLINE
+  for child in ctx.styledNode.children:
+    case child.t
+    of STYLED_ELEMENT:
+      ctx.iflush()
+      let display = child.computed{"display"}.blockify()
+      if display != child.computed{"display"}:
+        #TODO this is a hack.
+        # it exists because passing down a different `computed' would need
+        # changes in way too many procedures, which I am not ready to make yet.
+        let newChild = StyledNode()
+        newChild[] = child[]
+        newChild.computed = child.computed.copyProperties()
+        newChild.computed{"display"} = display
+        ctx.generateFromElem(newChild)
+      else:
+        ctx.generateFromElem(child)
+    of STYLED_TEXT:
+      if ctx.blockgroup.canGenerateAnonymousInline(box.computed, child.text):
+        ctx.generateAnonymousInlineText(child.text, ctx.styledNode)
+    of STYLED_REPLACEMENT:
+      ctx.generateReplacement(child, ctx.styledNode)
+  ctx.iflush()
+  # Flush anonymous tables here, to avoid setting inline layout with tables.
+  ctx.flushTableRow()
+  ctx.flushTable()
+  # (flush here, because why not)
+  ctx.flushInherit()
+  ctx.blockgroup.flush()
+  assert not box.inlinelayout
+  return box
+
 proc generateTableCellBox(styledNode: StyledNode, lctx: LayoutState,
     parent: var InnerBlockContext): TableCellBoxBuilder =
   let box = TableCellBoxBuilder(computed: styledNode.computed)