about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-01-29 15:33:34 +0100
committerbptato <nincsnevem662@gmail.com>2024-01-29 15:58:58 +0100
commit69ab68942e73458a1b0105d16bc5cb1210895f29 (patch)
treeffff7084502156a60f814967b038786dd9ef7dfe /src
parent9318ad33229d3c2f96dd766b38b8748f77487c5a (diff)
downloadchawan-69ab68942e73458a1b0105d16bc5cb1210895f29.tar.gz
layout: consider inline positioning for absolute blocks
When an absolute block is a descendant of an inline box, its positioned
ancestor must be set to that box.
Diffstat (limited to 'src')
-rw-r--r--src/layout/box.nim7
-rw-r--r--src/layout/engine.nim27
-rw-r--r--src/render/renderdocument.nim146
3 files changed, 102 insertions, 78 deletions
diff --git a/src/layout/box.nim b/src/layout/box.nim
index 6763846a..b4b4e030 100644
--- a/src/layout/box.nim
+++ b/src/layout/box.nim
@@ -43,6 +43,9 @@ type
     xminwidth*: LayoutUnit
     size*: Size
 
+  SplitType* = enum
+    stSplitStart, stSplitEnd
+
   InlineFragment* = ref object
     # Say we have the following inline box:
     #   abcd
@@ -64,6 +67,7 @@ type
     atoms*: seq[InlineAtom]
     computed*: CSSComputedValues
     node*: StyledNode
+    splitType*: set[SplitType]
 
   RelativeRect* = object
     top*: LayoutUnit
@@ -79,9 +83,6 @@ type
     offset*: Offset
     size*: Size # padding size
     margin*: RelativeRect #TODO get rid of this?
-    positioned*: bool
-    x_positioned*: bool
-    y_positioned*: bool
     # very bad name. basically the minimum content width after the contents
     # have been positioned (usually the width of the shortest word.) used
     # in table cells.
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index f6762881..63a3efdf 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -62,8 +62,7 @@ type
   InlineBoxBuilder = ref object of BoxBuilder
     text: seq[string]
     newline: bool
-    splitstart: bool
-    splitend: bool
+    splitType: set[SplitType]
 
   BlockBoxBuilder = ref object of BoxBuilder
     inlinelayout: bool
@@ -1307,7 +1306,6 @@ proc addInlineBlock(ictx: var InlineContext, state: var InlineState,
   let box = BlockBox(
     computed: builder.computed,
     node: builder.node,
-    positioned: builder.computed{"position"} != POSITION_STATIC,
     margin: sizes.margin
   )
   var bctx = BlockContext(lctx: lctx)
@@ -1350,8 +1348,12 @@ proc addInlineBlock(ictx: var InlineContext, state: var InlineState,
 proc layoutInline(ictx: var InlineContext, box: InlineBoxBuilder):
     InlineFragment =
   let lctx = ictx.lctx
-  let fragment = InlineFragment(computed: box.computed, node: box.node)
-  if box.splitstart:
+  let fragment = InlineFragment(
+    computed: box.computed,
+    node: box.node,
+    splitType: box.splitType
+  )
+  if stSplitStart in box.splitType:
     let marginLeft = box.computed{"margin-left"}.px(lctx, ictx.space.w)
     ictx.currentLine.size.w += marginLeft
   var state = InlineState(
@@ -1366,7 +1368,7 @@ proc layoutInline(ictx: var InlineContext, box: InlineBoxBuilder):
   )
   if box.newline:
     ictx.flushLine(state)
-  if box.splitstart:
+  if stSplitStart in box.splitType:
     let paddingLeft = box.computed{"padding-left"}.px(lctx, ictx.space.w)
     ictx.currentLine.size.w += paddingLeft
 
@@ -1391,7 +1393,7 @@ proc layoutInline(ictx: var InlineContext, box: InlineBoxBuilder):
     else:
       assert false, "child.t is " & $child.computed{"display"}
 
-  if box.splitend:
+  if stSplitEnd in box.splitType:
     let paddingRight = box.computed{"padding-right"}.px(lctx, ictx.space.w)
     ictx.currentLine.size.w += paddingRight
     let marginRight = box.computed{"margin-right"}.px(lctx, ictx.space.w)
@@ -1492,7 +1494,6 @@ proc buildBlock(bctx: var BlockContext, builder: BlockBoxBuilder,
     builder.computed)
   let box = BlockBox(
     computed: builder.computed,
-    positioned: builder.computed{"position"} != POSITION_STATIC,
     node: builder.node,
     offset: Offset(x: offset.x + sizes.margin.left, y: offset.y),
     margin: sizes.margin
@@ -1510,7 +1511,6 @@ proc buildListItem(bctx: var BlockContext, builder: ListItemBoxBuilder,
     builder.computed)
   let box = ListItemBox(
     computed: builder.computed,
-    positioned: builder.computed{"position"} != POSITION_STATIC,
     node: builder.node,
     offset: Offset(x: offset.x + sizes.margin.left, y: offset.y),
     margin: sizes.margin
@@ -1530,7 +1530,6 @@ proc buildTable(bctx: var BlockContext, builder: TableBoxBuilder,
     builder.computed)
   let box = BlockBox(
     computed: builder.computed,
-    positioned: builder.computed{"position"} != POSITION_STATIC,
     node: builder.node,
     offset: Offset(x: offset.x + sizes.margin.left, y: offset.y),
     margin: sizes.margin
@@ -1552,8 +1551,6 @@ proc positionAbsolute(lctx: LayoutState, box: BlockBox, margin: RelativeRect) =
   let bottom = box.computed{"bottom"}
   let parentWidth = applySizeConstraint(lctx.attrs.width_px, last.w)
   let parentHeight = applySizeConstraint(lctx.attrs.height_px, last.h)
-  box.x_positioned = not (left.auto and right.auto)
-  box.y_positioned = not (top.auto and bottom.auto)
   if not left.auto:
     box.offset.x = left.px(lctx, parentWidth)
     box.offset.x += margin.left
@@ -1629,7 +1626,6 @@ proc buildTableCaption(lctx: LayoutState, builder: TableCaptionBoxBuilder,
   let box = BlockBox(
     computed: builder.computed,
     node: builder.node,
-    positioned: builder.computed{"position"} != POSITION_STATIC,
     margin: sizes.margin
   )
   var bctx = BlockContext(lctx: lctx)
@@ -1813,7 +1809,6 @@ proc buildTableRow(pctx: TableContext, ctx: RowContext, parent: BlockBox,
   var n = 0
   let row = BlockBox(
     computed: builder.computed,
-    positioned: builder.computed{"position"} != POSITION_STATIC,
     node: builder.node
   )
   var baseline: LayoutUnit = 0
@@ -2665,7 +2660,7 @@ proc generateInlineBoxes(ctx: var InnerBlockContext, styledNode: StyledNode) =
   ctx.iflush()
   ctx.inlineStack.add(styledNode)
   var lbox = ctx.reconstructInlineParents()
-  lbox.splitstart = true
+  lbox.splitType.incl(stSplitStart)
   ctx.ibox = lbox
   for child in styledNode.children:
     case child.t
@@ -2683,7 +2678,7 @@ proc generateInlineBoxes(ctx: var InnerBlockContext, styledNode: StyledNode) =
     ctx.iflush()
     lbox = ctx.reconstructInlineParents()
     ctx.ibox = lbox
-  lbox.splitend = true
+  lbox.splitType.incl(stSplitEnd)
   ctx.inlineStack.setLen(ctx.inlineStack.len - 1)
   ctx.iflush()
 
diff --git a/src/render/renderdocument.nim b/src/render/renderdocument.nim
index f86f276a..2a5ddccd 100644
--- a/src/render/renderdocument.nim
+++ b/src/render/renderdocument.nim
@@ -33,8 +33,8 @@ func toFormat(computed: CSSComputedValues): Format =
     flags: flags
   )
 
-proc setText(grid: var FlexibleGrid, linestr: string, x, y: int,
-    format: Format, node: StyledNode) {.inline.} =
+proc setText(grid: var FlexibleGrid; linestr: string; x, y: int;
+    format: Format; node: StyledNode) {.inline.} =
   assert linestr.len != 0
   var i = 0
   var r: Rune
@@ -169,13 +169,13 @@ proc setText(grid: var FlexibleGrid, linestr: string, x, y: int,
   assert grid[y].formats[fi].pos <= nx
   # That's it!
 
-proc setRowWord(grid: var FlexibleGrid, word: InlineAtom, x, y: LayoutUnit,
-    attrs: WindowAttributes, format: Format, node: StyledNode) =
-  let y = toInt((y + word.offset.y) div attrs.ppl) # y cell
+proc setRowWord(grid: var FlexibleGrid; word: InlineAtom; offset: Offset;
+    attrs: WindowAttributes; format: Format; node: StyledNode) =
+  let y = toInt((offset.y + word.offset.y) div attrs.ppl) # y cell
   if y < 0:
     # y is outside the canvas, no need to draw
     return
-  var x = toInt((x + word.offset.x) div attrs.ppc) # x cell
+  var x = toInt((offset.x + word.offset.x) div attrs.ppc) # x cell
   var i = 0
   var r: Rune
   while x < 0 and i < word.str.len:
@@ -188,13 +188,14 @@ proc setRowWord(grid: var FlexibleGrid, word: InlineAtom, x, y: LayoutUnit,
     let linestr = word.str.substr(i)
     grid.setText(linestr, x, y, format, node)
 
-proc setSpacing(grid: var FlexibleGrid, spacing: InlineAtom, x, y: LayoutUnit,
-    attrs: WindowAttributes, format: Format, node: StyledNode) =
-  let y = toInt((y + spacing.offset.y) div attrs.ppl) # y cell
+proc setSpacing(grid: var FlexibleGrid; spacing: InlineAtom; offset: Offset;
+    attrs: WindowAttributes; format: Format; node: StyledNode) =
+  let y = toInt((offset.y + spacing.offset.y) div attrs.ppl) # y cell
   if y < 0: return # y is outside the canvas, no need to draw
-  var x = toInt((x + spacing.offset.x) div attrs.ppc) # x cell
+  var x = toInt((offset.x + spacing.offset.x) div attrs.ppc) # x cell
   let width = toInt(spacing.size.w div attrs.ppc) # cell width
-  if x + width < 0: return # highest x is outside the canvas, no need to draw
+  if x + width < 0:
+    return # highest x is outside the canvas, no need to draw
   var i = 0
   if x < 0:
     i -= x
@@ -203,8 +204,8 @@ proc setSpacing(grid: var FlexibleGrid, spacing: InlineAtom, x, y: LayoutUnit,
     let linestr = ' '.repeat(width - i)
     grid.setText(linestr, x, y, format, node)
 
-proc paintBackground(grid: var FlexibleGrid, color: CellColor, startx,
-    starty, endx, endy: int, node: StyledNode, attrs: WindowAttributes) =
+proc paintBackground(grid: var FlexibleGrid; color: CellColor; startx,
+    starty, endx, endy: int; node: StyledNode; attrs: WindowAttributes) =
   var starty = starty div attrs.ppl
   var endy = endy div attrs.ppl
 
@@ -278,11 +279,18 @@ proc paintBackground(grid: var FlexibleGrid, color: CellColor, startx,
         grid[y].formats[fi].format.bgcolor = color
         grid[y].formats[fi].node = node
 
-proc renderBlockBox(grid: var FlexibleGrid, box: BlockBox, x, y: LayoutUnit,
-  attrs: WindowAttributes, posx: LayoutUnit = 0, posy: LayoutUnit = 0)
+type RenderState = object
+  # Position of the absolute positioning containing block:
+  # https://drafts.csswg.org/css-position/#absolute-positioning-containing-block
+  absolutePos: seq[Offset]
 
-proc paintInlineFragment(grid: var FlexibleGrid, fragment: InlineFragment,
-    x, y: LayoutUnit, bgcolor: CellColor, attrs: WindowAttributes) =
+proc renderBlockBox(grid: var FlexibleGrid; state: var RenderState;
+  box: BlockBox; offset: Offset; attrs: WindowAttributes)
+
+proc paintInlineFragment(grid: var FlexibleGrid; fragment: InlineFragment;
+    offset: Offset; bgcolor: CellColor; attrs: WindowAttributes) =
+  let x = offset.x
+  let y = offset.y
   let node = fragment.node
   if fragment.startOffset.y - fragment.size.h == fragment.endOffset.y:
     let x0 = toInt(x + fragment.startOffset.x)
@@ -307,61 +315,75 @@ proc paintInlineFragment(grid: var FlexibleGrid, fragment: InlineFragment,
     let y5 = toInt(y + fragment.size.h)
     grid.paintBackground(bgcolor, x4, y4, x5, y5, node, attrs)
 
-proc renderInlineFragment(grid: var FlexibleGrid, fragment: InlineFragment,
-    x, y: LayoutUnit, attrs: WindowAttributes, posx: LayoutUnit = 0,
-    posy: LayoutUnit = 0) =
+proc renderInlineFragment(grid: var FlexibleGrid; state: var RenderState,
+    fragment: InlineFragment; offset: Offset; attrs: WindowAttributes) =
   assert fragment.atoms.len == 0 or fragment.children.len == 0
   if fragment.computed{"background-color"}.a > 0: # TODO color blending
     let bgcolor = fragment.computed{"background-color"}.cellColor()
-    grid.paintInlineFragment(fragment, x, y, bgcolor, attrs)
+    grid.paintInlineFragment(fragment, offset, bgcolor, attrs)
   if fragment.atoms.len > 0:
     let format = fragment.computed.toFormat()
     for atom in fragment.atoms:
       case atom.t
       of INLINE_BLOCK:
-        let x = x + atom.offset.x
-        let y = y + atom.offset.y
-        grid.renderBlockBox(atom.innerbox, x, y, attrs, posx, posy)
+        let offset = Offset(
+          x: offset.x + atom.offset.x,
+          y: offset.y + atom.offset.y
+        )
+        grid.renderBlockBox(state, atom.innerbox, offset, attrs)
       of INLINE_WORD:
-        grid.setRowWord(atom, x, y, attrs, format, fragment.node)
+        grid.setRowWord(atom, offset, attrs, format, fragment.node)
       of INLINE_SPACING:
-        grid.setSpacing(atom, x, y, attrs, format, fragment.node)
+        grid.setSpacing(atom, offset, attrs, format, fragment.node)
+  if fragment.computed{"position"} != POSITION_STATIC:
+    if fragment.splitType != {stSplitStart, stSplitEnd}:
+      if stSplitStart in fragment.splitType:
+        state.absolutePos.add(Offset(
+          x: offset.x + fragment.startOffset.x,
+          y: offset.y + fragment.endOffset.y
+        ))
+      if stSplitEnd in fragment.splitType:
+        discard state.absolutePos.pop()
   for child in fragment.children:
-    grid.renderInlineFragment(child, x, y, attrs, posx, posy)
+    grid.renderInlineFragment(state, child, offset, attrs)
 
-proc renderRootInlineFragment(grid: var FlexibleGrid, root: RootInlineFragment,
-    x, y: LayoutUnit, attrs: WindowAttributes, posx: LayoutUnit = 0,
-    posy: LayoutUnit = 0) =
-  let x = x + root.offset.x
-  let y = y + root.offset.y
-  grid.renderInlineFragment(root.fragment, x, y, attrs, posx, posy)
+proc renderRootInlineFragment(grid: var FlexibleGrid; state: var RenderState;
+    root: RootInlineFragment; offset: Offset; attrs: WindowAttributes) =
+  let offset = Offset(
+    x: offset.x + root.offset.x,
+    y: offset.y + root.offset.y
+  )
+  grid.renderInlineFragment(state, root.fragment, offset, attrs)
 
-proc renderBlockBox(grid: var FlexibleGrid, box: BlockBox, x, y: LayoutUnit,
-    attrs: WindowAttributes, posx: LayoutUnit = 0, posy: LayoutUnit = 0) =
+proc renderBlockBox(grid: var FlexibleGrid; state: var RenderState;
+    box: BlockBox; offset: Offset; attrs: WindowAttributes) =
   var stack = newSeqOfCap[tuple[
     box: BlockBox,
-    x, y, posx, posy: LayoutUnit
+    offset: Offset
   ]](100)
-  stack.add((box, x, y, posx, posy))
+  stack.add((box, offset))
 
   while stack.len > 0:
-    var (box, x, y, posx, posy) = stack.pop()
-    if box.x_positioned:
-      x = posx
-    if box.y_positioned:
-      y = posy
-    x += box.offset.x
-    y += box.offset.y
-    if box.positioned:
-      posx = x
-      posy = y
+    var (box, offset) = stack.pop()
+    if box == nil: # positioned marker
+      discard state.absolutePos.pop()
+      continue
+    if not box.computed{"left"}.auto or not box.computed{"right"}.auto:
+      offset.x = state.absolutePos[^1].x
+    if not box.computed{"top"}.auto or not box.computed{"bottom"}.auto:
+      offset.y = state.absolutePos[^1].y
+    offset.x += box.offset.x
+    offset.y += box.offset.y
+    if box.computed{"position"} != POSITION_STATIC:
+      state.absolutePos.add(offset)
+      stack.add((nil, Offset(x: -1, y: -1)))
 
     if box.computed{"visibility"} == VISIBILITY_VISIBLE:
       if box.computed{"background-color"}.a != 0: #TODO color blending
-        let ix = toInt(x)
-        let iy = toInt(y)
-        let iex = toInt(x + box.size.w)
-        let iey = toInt(y + box.size.h)
+        let ix = toInt(offset.x)
+        let iy = toInt(offset.y)
+        let iex = toInt(offset.x + box.size.w)
+        let iey = toInt(offset.y + box.size.h)
         let color = box.computed{"background-color"}.cellColor()
         grid.paintBackground(color, ix, iy, iex, iey, box.node, attrs)
       if box.computed{"background-image"}.t == CONTENT_IMAGE and
@@ -369,35 +391,41 @@ proc renderBlockBox(grid: var FlexibleGrid, box: BlockBox, x, y: LayoutUnit,
         # ugly hack for background-image display... TODO actually display images
         let s = "[img]"
         let w = s.len * attrs.ppc
-        var ix = x
+        var ix = offset.x
         if box.size.w < w:
           # text is larger than image; center it to minimize error
           ix -= w div 2
           ix += box.size.w div 2
         let x = toInt(ix div attrs.ppc)
-        let y = toInt(y div attrs.ppl)
+        let y = toInt(offset.y div attrs.ppl)
         if y >= 0 and x + w >= 0:
           grid.setText(s, x, y, box.computed.toFormat(), box.node)
 
       if box of ListItemBox:
         let box = ListItemBox(box)
         if box.marker != nil:
-          let x = x - box.marker.size.w
-          grid.renderRootInlineFragment(box.marker, x, y, attrs)
+          let offset = Offset(
+            x: offset.x - box.marker.size.w,
+            y: offset.y
+          )
+          grid.renderRootInlineFragment(state, box.marker, offset, attrs)
 
     if box.inline != nil:
       assert box.nested.len == 0
       if box.computed{"visibility"} == VISIBILITY_VISIBLE:
-        grid.renderRootInlineFragment(box.inline, x, y, attrs)
+        grid.renderRootInlineFragment(state, box.inline, offset, attrs)
     else:
       for i in countdown(box.nested.high, 0):
-        stack.add((box.nested[i], x, y, posx, posy))
+        stack.add((box.nested[i], offset))
 
-proc renderDocument*(styledRoot: StyledNode, attrs: WindowAttributes):
+proc renderDocument*(styledRoot: StyledNode; attrs: WindowAttributes):
     FlexibleGrid =
   var grid: FlexibleGrid
+  var state = RenderState(
+    absolutePos: @[Offset(x: 0, y: 0)]
+  )
   let rootBox = renderLayout(styledRoot, attrs)
-  grid.renderBlockBox(rootBox, 0, 0, attrs)
+  grid.renderBlockBox(state, rootBox, Offset(x: 0, y: 0), attrs)
   if grid.len == 0:
     grid.addLine()
   return grid