about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/layout/engine.nim180
1 files changed, 120 insertions, 60 deletions
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index ef3af8da..adf5b3ce 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -252,6 +252,11 @@ type
     marginBottom: LayoutUnit
     fragment: InlineFragment
 
+  InlineUnpositionedFloat = object
+    parent: InlineFragment
+    box: BlockBox
+    space: AvailableSpace
+
   InlineContext = object
     root: RootInlineFragment
     computed: CSSComputedValues
@@ -269,10 +274,11 @@ type
     textFragmentSeen: bool
     lastTextFragment: InlineFragment
     firstBaselineSet: bool
+    unpositionedFloats: seq[InlineUnpositionedFloat]
+    secondPass: bool
 
   InlineState = object
     fragment: InlineFragment
-    firstLine: bool
     startOffsetTop: Offset
     # we do not want to collapse newlines over tag boundaries, so these are
     # in state
@@ -804,7 +810,7 @@ proc processWhitespace(ictx: var InlineContext; state: var InlineState;
 func initInlineContext(bctx: var BlockContext; space: AvailableSpace;
     bfcOffset: Offset; root: RootInlineFragment;
     computed: CSSComputedValues): InlineContext =
-  var ictx = InlineContext(
+  return InlineContext(
     bctx: addr bctx,
     lctx: bctx.lctx,
     bfcOffset: bfcOffset,
@@ -812,8 +818,6 @@ func initInlineContext(bctx: var BlockContext; space: AvailableSpace;
     root: root,
     computed: computed
   )
-  ictx.initLine()
-  return ictx
 
 proc layoutTextLoop(ictx: var InlineContext; state: var InlineState;
     str: string) =
@@ -1024,19 +1028,19 @@ proc resolveAbsoluteSize(sizes: var ResolvedSizes; space: AvailableSpace;
     dim: DimensionType; computed: CSSComputedValues; lctx: LayoutContext) =
   let cvalSize = computed[CvalSizeMap[dim]].length
   if cvalSize.auto:
-    if space[dim].isDefinite:
-      let u = max(space[dim].u - sizes.positioned[dim].sum(), 0)
-      let cvalStart = computed[CvalStartMap[dim]].length
-      let cvalEnd = computed[CvalEndMap[dim]].length
-      if not cvalStart.auto and not cvalEnd.auto:
-        # width is auto and left & right are not auto.
-        # Solve for width.
-        sizes.space[dim] = stretch(u)
-      else:
-        # Return shrink to fit and solve for left/right.
-        sizes.space[dim] = fitContent(u)
+    # available space is always known here, because unlike regular
+    # boxes, we layout absolute boxes in a bottom-up order.
+    assert space[dim].isDefinite
+    let u = max(space[dim].u - sizes.positioned[dim].sum(), 0)
+    let cvalStart = computed[CvalStartMap[dim]].length
+    let cvalEnd = computed[CvalEndMap[dim]].length
+    if not cvalStart.auto and not cvalEnd.auto:
+      # width is auto and left & right are not auto.
+      # Solve for width.
+      sizes.space[dim] = stretch(u)
     else:
-      sizes.space[dim] = space[dim]
+      # Return shrink to fit and solve for left/right.
+      sizes.space[dim] = fitContent(u)
   else:
     let padding = sizes.padding[dim].sum()
     let sizepx = cvalSize.spx(lctx, space[dim], computed, padding)
@@ -1287,17 +1291,21 @@ proc popPositioned(lctx: LayoutContext; overflow: var Overflow;
     let child = it.child
     var marginBottomOut: LayoutUnit
     lctx.layoutRootBlock(child, space, it.offset, marginBottomOut)
-    child.state.offset.y += child.state.margin.top
     if not child.computed{"left"}.auto:
-      child.state.offset.x = child.state.positioned.left
+      child.state.offset.x = child.state.positioned.left +
+        child.state.margin.left
     elif not child.computed{"right"}.auto:
       child.state.offset.x = -child.state.positioned.right -
         child.state.size.w - child.state.margin.right
+    # margin.left is added in layoutRootBlock
     if not child.computed{"top"}.auto:
-      child.state.offset.y = child.state.positioned.top
+      child.state.offset.y = child.state.positioned.top +
+        child.state.margin.top
     elif not child.computed{"bottom"}.auto:
       child.state.offset.y = -child.state.positioned.bottom -
         child.state.size.h - child.state.margin.bottom
+    else:
+      child.state.offset.y += child.state.margin.top
     overflow.applyOverflowDimensions(child)
 
 type
@@ -1433,9 +1441,51 @@ proc layoutListItem(bctx: var BlockContext; box: BlockBox;
   of ListStylePositionInside:
     bctx.layoutFlow(box, sizes)
 
+proc addInlineFloat(ictx: var InlineContext; state: var InlineState;
+    box: BlockBox) =
+  let lctx = ictx.lctx
+  let sizes = lctx.resolveFloatSizes(ictx.space, box.computed)
+  box.state = BlockBoxLayoutState(
+    margin: sizes.margin,
+    positioned: sizes.positioned
+  )
+  var marginBottom: LayoutUnit = 0
+  let offset = offset(
+    x = 0,
+    y = ictx.lbstate.offsety + sizes.margin.top
+  )
+  lctx.layoutRootBlock(box, sizes.space, offset, marginBottom)
+  ictx.lbstate.size.w += box.state.size.w
+  # Note that by now, the top y offset is always resolved.
+  ictx.unpositionedFloats.add(InlineUnpositionedFloat(
+    parent: state.fragment,
+    box: box,
+    space: sizes.space
+  ))
+
 const DisplayOuterInline = {
   DisplayInlineBlock, DisplayInlineTableWrapper, DisplayInlineFlex
 }
+
+proc addInlineAbsolute(ictx: var InlineContext; state: var InlineState;
+    box: BlockBox) =
+  let lctx = ictx.lctx
+  state.fragment.state.atoms.add(InlineAtom(
+    t: iatInlineBlock,
+    innerbox: box
+  ))
+  var offset = offset(x = 0, y = ictx.lbstate.offsety)
+  if box.computed{"display"} in DisplayOuterInline:
+    # inline-block or similar. put it on the current line.
+    # (I don't add pending spacing because other browsers don't add
+    # it either.)
+    offset.x += ictx.lbstate.size.w
+  elif ictx.lbstate.atoms.len > 0:
+    # flush if there is already something on the line *and* our outer
+    # display is block.
+    offset.y += ictx.cellHeight
+  lctx.positioned[^1].queue.add(QueuedAbsolute(child: box, offset: offset))
+
 proc addInlineBlock(ictx: var InlineContext; state: var InlineState;
     box: BlockBox) =
   let lctx = ictx.lctx
@@ -1444,46 +1494,38 @@ proc addInlineBlock(ictx: var InlineContext; state: var InlineState;
     margin: sizes.margin,
     positioned: sizes.positioned
   )
-  if box.computed{"position"} != PositionAbsolute:
-    assert box.computed{"display"} in {DisplayInlineBlock,
-      DisplayInlineTableWrapper, DisplayInlineFlex}
-    var marginBottom: LayoutUnit = 0
-    lctx.layoutRootBlock(box, sizes.space, offset(x = 0, y = 0), marginBottom)
-    # Apply the block box's properties to the atom itself.
-    let iblock = InlineAtom(
-      t: iatInlineBlock,
-      innerbox: box,
-      offset: offset(x = 0, y = 0),
-      size: size(w = box.outerSize(dtHorizontal), h = box.state.size.h)
-    )
-    let iastate = InlineAtomState(
-      baseline: box.state.baseline,
-      vertalign: box.computed{"vertical-align"},
-      marginTop: sizes.margin.top,
-      marginBottom: marginBottom
-    )
-    discard ictx.addAtom(state, iastate, iblock)
-  else:
+  var marginBottom: LayoutUnit = 0
+  lctx.layoutRootBlock(box, sizes.space, offset(x = 0, y = 0), marginBottom)
+  # Apply the block box's properties to the atom itself.
+  let iblock = InlineAtom(
+    t: iatInlineBlock,
+    innerbox: box,
+    offset: offset(x = 0, y = 0),
+    size: size(w = box.outerSize(dtHorizontal), h = box.state.size.h)
+  )
+  let iastate = InlineAtomState(
+    baseline: box.state.baseline,
+    vertalign: box.computed{"vertical-align"},
+    marginTop: sizes.margin.top,
+    marginBottom: marginBottom
+  )
+  discard ictx.addAtom(state, iastate, iblock)
+  ictx.whitespacenum = 0
+
+proc addBox(ictx: var InlineContext; state: var InlineState;
+    box: BlockBox) =
+  if box.computed{"float"} != FloatNone:
+    # This will trigger a re-layout for this inline root.
+    if not ictx.secondPass:
+      ictx.addInlineFloat(state, box)
+  elif box.computed{"position"} == PositionAbsolute:
     # This doesn't really have to be an inline block. I just want to
     # handle its positioning here.
-    #TODO figure out how to do this for fully inline boxes too... (maybe
-    # just skip positioning for those?)
-    state.fragment.state.atoms.add(InlineAtom(
-      t: iatInlineBlock,
-      innerbox: box
-    ))
-    var offset = offset(x = 0, y = ictx.lbstate.offsety)
-    if box.computed{"display"} in DisplayOuterInline:
-      # inline-block or similar. put it on the current line.
-      # (I don't add pending spacing because other browsers don't add
-      # it either.)
-      offset.x += ictx.lbstate.size.w
-    elif ictx.lbstate.atoms.len > 0:
-      # flush if there is already something on the line *and* our outer
-      # display is block.
-      offset.y += ictx.cellHeight
-    lctx.positioned[^1].queue.add(QueuedAbsolute(child: box, offset: offset))
-  ictx.whitespacenum = 0
+    ictx.addInlineAbsolute(state, box)
+  else:
+    # This is an inline block.
+    assert box.computed{"display"} in DisplayOuterInline
+    ictx.addInlineBlock(state, box)
 
 proc addInlineImage(ictx: var InlineContext; state: var InlineState;
     bmp: NetworkBitmap; padding: LayoutUnit) =
@@ -1584,7 +1626,7 @@ proc layoutInline(ictx: var InlineContext; fragment: InlineFragment) =
   ictx.applyLineHeight(ictx.lbstate, computed)
   case fragment.t
   of iftNewline: ictx.flushLine(state)
-  of iftBox: ictx.addInlineBlock(state, fragment.box)
+  of iftBox: ictx.addBox(state, fragment.box)
   of iftBitmap: ictx.addInlineImage(state, fragment.bmp, padding.sum())
   of iftText: ictx.layoutText(state, fragment.text.textData)
   of iftParent:
@@ -1633,21 +1675,38 @@ proc layoutRootInline(bctx: var BlockContext; root: RootInlineFragment;
     space: AvailableSpace; computed: CSSComputedValues;
     offset, bfcOffset: Offset) =
   var ictx = bctx.initInlineContext(space, bfcOffset, root, computed)
+  ictx.initLine()
   bctx.layoutRootInline0(ictx, root, space, computed, offset, bfcOffset)
-  if ictx.space.w.t == scFitContent and
+  if ictx.unpositionedFloats.len > 0 or
+      ictx.space.w.t == scFitContent and
       ictx.computed{"text-align"} notin TextAlignNone and
       ictx.size.w != ictx.space.w.u:
     # fit-content initial guess didn't work out; re-layout, with width stretched
     # to the actual text width.
+    #
     # Since we guess fit-content width to be the same width but stretched, this
     # should only run for cases where the text is shorter than the place it has,
     # or when some word overflows the place available.
+    #
     # In the first case, we know that the text is relatively short, so it
     # affects performance little. As for the latter case... just pray it happens
     # rarely enough.
+    let floats = move(ictx.unpositionedFloats)
     let space = availableSpace(w = stretch(ictx.size.w), h = space.h)
     ictx = bctx.initInlineContext(space, bfcOffset, root, computed)
+    for it in floats:
+      var offset = offset(x = 0, y = 0)
+      if bctx.parentBps != nil:
+        offset = bctx.parentBps.offset
+      bctx.positionFloat(it.box, space, offset)
+    ictx.initLine()
+    ictx.secondPass = true
     bctx.layoutRootInline0(ictx, root, space, computed, offset, bfcOffset)
+    for it in floats:
+      it.parent.state.atoms.add(InlineAtom(
+        t: iatInlineBlock,
+        innerbox: it.box
+      ))
   ictx.root.state.overflow.finalize(ictx.root.state.size)
 
 proc positionRelative(lctx: LayoutContext; parent, box: BlockBox) =
@@ -2873,7 +2932,8 @@ proc buildSomeBlock(ctx: var InnerBlockContext; styledNode: StyledNode;
 # Note: these also pop
 proc pushBlock(ctx: var InnerBlockContext; styledNode: StyledNode;
     computed: CSSComputedValues) =
-  if computed{"position"} == PositionAbsolute and
+  if (computed{"position"} == PositionAbsolute or
+        computed{"float"} != FloatNone) and
       (ctx.inline != nil or ctx.inlineStack.len > 0):
     ctx.pushInlineBlock(styledNode, computed)
   else: