diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/css/box.nim | 1 | ||||
-rw-r--r-- | src/css/csstree.nim | 8 | ||||
-rw-r--r-- | src/css/layout.nim | 98 | ||||
-rw-r--r-- | src/css/render.nim | 31 |
4 files changed, 71 insertions, 67 deletions
diff --git a/src/css/box.nim b/src/css/box.nim index f177078e..3d237e1a 100644 --- a/src/css/box.nim +++ b/src/css/box.nim @@ -78,6 +78,7 @@ type bounds*: Bounds CSSBox* = ref object of RootObj + parent* {.cursor.}: CSSBox render*: BoxRenderState # render output computed*: CSSValues element*: Element diff --git a/src/css/csstree.nim b/src/css/csstree.nim index ad7bb1a3..0de914c9 100644 --- a/src/css/csstree.nim +++ b/src/css/csstree.nim @@ -481,13 +481,17 @@ proc buildBox(ctx: var TreeContext; frame: TreeFrame; cached: CSSBox): CSSBox = else: BlockBox(computed: frame.computed, element: frame.parent) for child in frame.children: - box.children.add(ctx.build(nil, child)) + let childBox = ctx.build(nil, child) + childBox.parent = box + box.children.add(childBox) if display in DisplayInlineBlockLike: - return InlineBlockBox( + let wrapper = InlineBlockBox( computed: ctx.rootProperties, element: frame.parent, children: @[box] ) + box.parent = wrapper + return wrapper return box proc applyCounters(ctx: var TreeContext; styledNode: StyledNode) = diff --git a/src/css/layout.nim b/src/css/layout.nim index c2facdd3..5366c5e2 100644 --- a/src/css/layout.nim +++ b/src/css/layout.nim @@ -12,33 +12,22 @@ import utils/twtstr import utils/widthconv type - # position: absolute is annoying in that its layout depends on its - # containing box's size, which of course is rarely its parent box. - # e.g. in - # <div style="position: relative; display: inline-block"> - # <div> - # <div style="position: absolute; width: 100%; background: red"> - # blah - # </div> - # <div> - # blah blah - # </div> - # the width of the absolute box is the same as "blah blah", but we - # only know that after the outermost box has been layouted. + # position: absolute is annoying in that its position depends on its + # containing box's size and position, which of course is rarely its + # parent box. # - # So we must delay this layout until before the outermost box is + # So we must delay its positioning until before the outermost box is # popped off the stack, and we do this by queuing up absolute boxes in # the initial pass. # - #TODO: welp, turns out this is also true without position: absolute, - # so I think we could skip this entirely... especially now that - # we can cache sub-layouts. + # (Technically, its layout could be done earlier, but we aren't sure + # of the parent's size before its layout is finished, so this way we + # can avoid pointless sub-layout passes.) QueuedAbsolute = object offset: Offset child: BlockBox - PositionedItem = ref object - queue: seq[QueuedAbsolute] + PositionedItem = seq[QueuedAbsolute] LayoutContext = ref object attrsp: ptr WindowAttributes @@ -1401,12 +1390,38 @@ proc applyIntr(box: BlockBox; sizes: ResolvedSizes; intr: Size) = box.state.size[dim] = max(box.state.size[dim], intr[dim]) proc pushPositioned(lctx: LayoutContext) = - lctx.positioned.add(PositionedItem()) + lctx.positioned.add(@[]) + +# Offset the child by the offset of its actual parent to its nearest +# positioned ancestor (`parent'). +# This is only necessary (for the respective axes) if either top, +# bottom, left or right is specified. +proc realignAbsolutePosition(child: BlockBox; parent: CSSBox; + dims: set[DimensionType]) = + var it {.cursor.} = child.parent + while it != parent: + let offset = if it of BlockBox: + BlockBox(it).state.offset + else: + InlineBox(it).state.startOffset + if dtHorizontal in dims: + child.state.offset.x -= offset.x + if dtVertical in dims: + child.state.offset.y -= offset.y + it = it.parent + if parent of InlineBox: + # The renderer does not adjust position for inline parents, so we + # must do it here. + let offset = InlineBox(parent).state.startOffset + if dtHorizontal in dims: + child.state.offset.x += offset.x + if dtVertical in dims: + child.state.offset.y += offset.y # size is the parent's size -proc popPositioned(lctx: LayoutContext; size: Size) = - let item = lctx.positioned.pop() - for it in item.queue: +proc popPositioned(lctx: LayoutContext; parent: CSSBox; size: Size; + skipStatic = true) = + for it in lctx.positioned.pop(): let child = it.child var size = size #TODO this is very ugly. @@ -1426,26 +1441,33 @@ proc popPositioned(lctx: LayoutContext; size: Size) = sizes.space.w = stretch(child.state.intr.w) lctx.layoutRootBlock(child, offset, sizes) #TODO what happens with marginBottom? + var dims: set[DimensionType] = {} if child.computed{"left"}.u != clAuto: child.state.offset.x = positioned.left + sizes.margin.left + dims.incl(dtHorizontal) elif child.computed{"right"}.u != clAuto: child.state.offset.x = size.w - positioned.right - child.state.size.w - sizes.margin.right + dims.incl(dtHorizontal) # margin.left is added in layoutRootBlock if child.computed{"top"}.u != clAuto: child.state.offset.y = positioned.top + sizes.margin.top + dims.incl(dtVertical) elif child.computed{"bottom"}.u != clAuto: child.state.offset.y = size.h - positioned.bottom - child.state.size.h - sizes.margin.bottom + dims.incl(dtVertical) else: child.state.offset.y += sizes.margin.top + if dims != {}: + child.realignAbsolutePosition(parent, dims) proc queueAbsolute(lctx: LayoutContext; box: BlockBox; offset: Offset) = case box.computed{"position"} of PositionAbsolute: - lctx.positioned[^1].queue.add(QueuedAbsolute(child: box, offset: offset)) + lctx.positioned[^1].add(QueuedAbsolute(child: box, offset: offset)) of PositionFixed: - lctx.positioned[0].queue.add(QueuedAbsolute(child: box, offset: offset)) + lctx.positioned[0].add(QueuedAbsolute(child: box, offset: offset)) else: assert false proc positionRelative(lctx: LayoutContext; space: AvailableSpace; @@ -1623,6 +1645,9 @@ proc layoutOuterBlock(fstate: var FlowState; child: BlockBox; # Here our job is much easier in the unresolved case: subsequent # children's layout doesn't depend on our position; so we can just # defer margin resolution to the parent. + if fstate.space.w.t == scFitContent: + # Do not queue in the first pass. + return let lctx = fstate.lctx var offset = fstate.offset fstate.initLine(flag = ilfAbsolute) @@ -1815,12 +1840,7 @@ proc layoutImage(fstate: var FlowState; ibox: InlineImageBox; padding: LUnit) = proc layoutInline(fstate: var FlowState; ibox: InlineBox) = let lctx = fstate.lctx let computed = ibox.computed - ibox.state = InlineBoxState( - startOffset: offset( - x = fstate.lbstate.widthAfterWhitespace, - y = fstate.offset.y - ) - ) + ibox.state = InlineBoxState() let padding = Span( start: computed{"padding-left"}.px(fstate.space.w), send: computed{"padding-right"}.px(fstate.space.w) @@ -1846,6 +1866,10 @@ proc layoutInline(fstate: var FlowState; ibox: InlineBox) = fstate.layoutImage(ibox, padding.sum()) fstate.lastTextBox = ibox else: + ibox.state.startOffset = offset( + x = fstate.lbstate.widthAfterWhitespace, + y = fstate.offset.y + ) let w = computed{"margin-left"}.px(fstate.space.w) if w != 0: fstate.initLine() @@ -1894,7 +1918,7 @@ proc layoutInline(fstate: var FlowState; ibox: InlineBox) = # since it uses cellHeight instead of the actual line height for the # last line. # Well, it seems good enough. - lctx.popPositioned(size( + lctx.popPositioned(ibox, size( w = 0, h = fstate.offset.y + fstate.cellHeight - ibox.state.startOffset.y )) @@ -2061,7 +2085,7 @@ proc layoutFlow(bctx: var BlockContext; box: BlockBox; sizes: ResolvedSizes; # I'll just replicate what layoutRootBlock is doing until I find a # better solution... size.h = max(size.h, bctx.maxFloatHeight) - bctx.lctx.popPositioned(size) + bctx.lctx.popPositioned(box, size) proc layoutListItem(bctx: var BlockContext; box: BlockBox; sizes: ResolvedSizes) = @@ -2827,7 +2851,7 @@ proc layoutFlex(bctx: var BlockContext; box: BlockBox; sizes: ResolvedSizes) = for child in fctx.relativeChildren: lctx.positionRelative(sizes.space, child) if box.computed{"position"} != PositionStatic: - lctx.popPositioned(box.state.size) + lctx.popPositioned(box, box.state.size) # Inner layout for boxes that establish a new block formatting context. # Returns the bottom margin for the box, collapsed with the appropriate @@ -2861,7 +2885,7 @@ proc layout*(box: BlockBox; attrsp: ptr WindowAttributes) = let lctx = LayoutContext( attrsp: attrsp, cellSize: size(w = attrsp.ppc, h = attrsp.ppl), - positioned: @[PositionedItem(), PositionedItem()], + positioned: @[@[], @[]], luctx: LUContext() ) let sizes = lctx.resolveBlockSizes(space, box.computed) @@ -2869,7 +2893,7 @@ proc layout*(box: BlockBox; attrsp: ptr WindowAttributes) = lctx.layoutRootBlock(box, sizes.margin.topLeft, sizes) var size = size(w = attrsp[].widthPx, h = attrsp[].heightPx) # Last absolute layer. - lctx.popPositioned(size) + lctx.popPositioned(box, size) # Fixed containing block. # The idea is to move fixed boxes to the real edges of the page, # so that they do not overlap with other boxes *and* we don't have @@ -2878,4 +2902,4 @@ proc layout*(box: BlockBox; attrsp: ptr WindowAttributes) = # slow down the renderer to a crawl.) size.w = max(size.w, box.state.size.w) size.h = max(size.h, box.state.size.h) - lctx.popPositioned(size) + lctx.popPositioned(box, size, skipStatic = false) diff --git a/src/css/render.nim b/src/css/render.nim index 02f19a63..7a859baf 100644 --- a/src/css/render.nim +++ b/src/css/render.nim @@ -50,9 +50,6 @@ type index: int RenderState = object - # Position of the absolute positioning containing block: - # https://drafts.csswg.org/css-position/#absolute-positioning-containing-block - absolutePos: seq[Offset] clipBoxes: seq[ClipBox] bgcolor: CellColor attrsp: ptr WindowAttributes @@ -362,7 +359,6 @@ proc paintInlineBox(grid: var FlexibleGrid; state: var RenderState; proc renderInlineBox(grid: var FlexibleGrid; state: var RenderState; ibox: InlineBox; offset: Offset; bgcolor0: ARGBColor; pass2 = false) = - let position = ibox.computed{"position"} #TODO stacking contexts let bgcolor = ibox.computed{"background-color"} var bgcolor0 = bgcolor0 @@ -375,8 +371,7 @@ proc renderInlineBox(grid: var FlexibleGrid; state: var RenderState; if bgcolor0.a > 0: grid.paintInlineBox(state, ibox, offset, bgcolor0.rgb.cellColor(), bgcolor0.a) - let startOffset = offset + ibox.state.startOffset - ibox.render.offset = startOffset + ibox.render.offset = offset + ibox.state.startOffset if ibox of InlineTextBox: let ibox = InlineTextBox(ibox) let format = ibox.computed.toFormat() @@ -415,18 +410,12 @@ proc renderInlineBox(grid: var FlexibleGrid; state: var RenderState; height: ibox.imgstate.size.h.toInt, bmp: ibox.bmp )) - elif ibox of InlineNewLineBox: - discard - else: - if position != PositionStatic: - state.absolutePos.add(startOffset) + else: # InlineNewLineBox does not have children, so we handle it here for child in ibox.children: if child of InlineBox: grid.renderInlineBox(state, InlineBox(child), offset, bgcolor0) else: grid.renderBlockBox(state, BlockBox(child), offset) - if position != PositionStatic: - discard state.absolutePos.pop() proc renderBlockBox(grid: var FlexibleGrid; state: var RenderState; box: BlockBox; offset: Offset; pass2 = false) = @@ -437,21 +426,12 @@ proc renderBlockBox(grid: var FlexibleGrid; state: var RenderState; state.nstack.add(StackItem( box: box, offset: offset, - apos: state.absolutePos[^1], clipBox: state.clipBox, index: zindex )) return - var offset = offset - if position in PositionAbsoluteFixed: - if box.computed{"left"}.u != clAuto or box.computed{"right"}.u != clAuto: - offset.x = state.absolutePos[^1].x - if box.computed{"top"}.u != clAuto or box.computed{"bottom"}.u != clAuto: - offset.y = state.absolutePos[^1].y - offset += box.state.offset + let offset = offset + box.state.offset box.render.offset = offset - if position != PositionStatic: - state.absolutePos.add(offset) let overflowX = box.computed{"overflow-x"} let overflowY = box.computed{"overflow-y"} let hasClipBox = overflowX != OverflowVisible or overflowY != OverflowVisible @@ -506,8 +486,6 @@ proc renderBlockBox(grid: var FlexibleGrid; state: var RenderState; grid.renderBlockBox(state, BlockBox(child), offset) if hasClipBox: discard state.clipBoxes.pop() - if position != PositionStatic: - discard state.absolutePos.pop() proc renderDocument*(grid: var FlexibleGrid; bgcolor: var CellColor; rootBox: BlockBox; attrsp: ptr WindowAttributes; @@ -517,7 +495,6 @@ proc renderDocument*(grid: var FlexibleGrid; bgcolor: var CellColor; # no HTML element when we run cascade; just clear all lines. return var state = RenderState( - absolutePos: @[offset(0, 0)], clipBoxes: @[ClipBox(send: offset(LUnit.high, LUnit.high))], attrsp: attrsp, bgcolor: defaultColor @@ -525,11 +502,9 @@ proc renderDocument*(grid: var FlexibleGrid; bgcolor: var CellColor; var stack = @[StackItem(box: rootBox, clipBox: state.clipBox)] while stack.len > 0: for it in stack: - state.absolutePos.add(it.apos) state.clipBoxes.add(it.clipBox) grid.renderBlockBox(state, it.box, it.offset, true) discard state.clipBoxes.pop() - discard state.absolutePos.pop() stack = move(state.nstack) stack.sort(proc(x, y: StackItem): int = cmp(x.index, y.index)) state.nstack = @[] |