diff options
author | bptato <nincsnevem662@gmail.com> | 2024-01-29 15:33:34 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-01-29 15:58:58 +0100 |
commit | 69ab68942e73458a1b0105d16bc5cb1210895f29 (patch) | |
tree | ffff7084502156a60f814967b038786dd9ef7dfe /src | |
parent | 9318ad33229d3c2f96dd766b38b8748f77487c5a (diff) | |
download | chawan-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.nim | 7 | ||||
-rw-r--r-- | src/layout/engine.nim | 27 | ||||
-rw-r--r-- | src/render/renderdocument.nim | 146 |
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 |