diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/layout/engine.nim | 370 | ||||
-rw-r--r-- | src/layout/renderdocument.nim | 16 |
2 files changed, 229 insertions, 157 deletions
diff --git a/src/layout/engine.nim b/src/layout/engine.nim index a3c58341..ef3af8da 100644 --- a/src/layout/engine.nim +++ b/src/layout/engine.nim @@ -14,9 +14,33 @@ import utils/twtuni 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%; color: 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. + # + # So we must delay this layout until before the outermost box is + # popped off the stack, and we do this by queuing up absolute boxes in + # the initial pass. + QueuedAbsolute = object + offset: Offset + child: BlockBox + + PositionedItem = ref object + queue: seq[QueuedAbsolute] + LayoutContext = ref object attrsp: ptr WindowAttributes - positioned: seq[AvailableSpace] + positioned: seq[PositionedItem] myRootProperties: CSSComputedValues # placeholder text data imgText: StyledNode @@ -160,14 +184,6 @@ const PositionStaticLike = { PositionStatic, PositionFixed, PositionSticky } -proc pushPositioned(lctx: LayoutContext; box: BlockBox; sizes: ResolvedSizes) = - if box.computed{"position"} notin PositionStaticLike: - lctx.positioned.add(sizes.space) - -proc popPositioned(lctx: LayoutContext; box: BlockBox) = - if box.computed{"position"} notin PositionStaticLike: - lctx.positioned.setLen(lctx.positioned.len - 1) - type BlockContext = object lctx: LayoutContext @@ -597,13 +613,6 @@ proc finishLine(ictx: var InlineContext; state: var InlineState; wrap: bool; ictx.lbstate.availableWidth else: ictx.lbstate.size.w - if state.firstLine: - #TODO padding top - state.fragment.state.startOffset = offset( - x = state.startOffsetTop.x, - y = y + ictx.lbstate.size.h - ) - state.firstLine = false ictx.size.w = max(ictx.size.w, lineWidth) ictx.lbstate = LineBoxState(offsety: y + ictx.lbstate.size.h) ictx.initLine() @@ -636,6 +645,19 @@ proc flushLine(ictx: var InlineContext; state: var InlineState) = ictx.applyLineHeight(ictx.lbstate, state.fragment.computed) ictx.finishLine(state, wrap = false, force = true) +func getBaseline(ictx: InlineContext; iastate: InlineAtomState; + atom: InlineAtom): LayoutUnit = + return case iastate.vertalign.keyword + of VerticalAlignBaseline: + let len = iastate.vertalign.length.px(ictx.lctx, ictx.cellHeight) + iastate.baseline + len + of VerticalAlignTop, VerticalAlignBottom: + atom.size.h + of VerticalAlignMiddle: + atom.size.h div 2 + else: + iastate.baseline + # Add an inline atom atom, with state iastate. # Returns true on newline. proc addAtom(ictx: var InlineContext; state: var InlineState; @@ -657,7 +679,7 @@ proc addAtom(ictx: var InlineContext; state: var InlineState; ictx.finishLine(state, wrap = false, force = true) # Recompute on newline shift = ictx.computeShift(state) - if atom.size.w > 0 and atom.size.h > 0: + if atom.size.w > 0 and atom.size.h > 0 or atom.t == iatInlineBlock: if shift > 0: ictx.addSpacing(shift, state) ictx.root.state.xminwidth = max(ictx.root.state.xminwidth, atom.xminwidth) @@ -675,17 +697,8 @@ proc addAtom(ictx: var InlineContext; state: var InlineState; ictx.lbstate.putAtom(atom, iastate, state.fragment) atom.offset.x += ictx.lbstate.size.w ictx.lbstate.size.w += atom.size.w - let baseline = case iastate.vertalign.keyword - of VerticalAlignBaseline: - let len = iastate.vertalign.length.px(ictx.lctx, ictx.cellHeight) - iastate.baseline + len - of VerticalAlignTop, VerticalAlignBottom: - atom.size.h - of VerticalAlignMiddle: - atom.size.h div 2 - else: - iastate.baseline # store for later use in resizeLine/shiftAtoms + let baseline = ictx.getBaseline(iastate, atom) atom.offset.y = baseline ictx.lbstate.baseline = max(ictx.lbstate.baseline, baseline) @@ -1012,8 +1025,7 @@ proc resolveAbsoluteSize(sizes: var ResolvedSizes; space: AvailableSpace; let cvalSize = computed[CvalSizeMap[dim]].length if cvalSize.auto: if space[dim].isDefinite: - let u = max(space[dim].u - sizes.positioned[dim].sum() - - sizes.margin[dim].sum() - sizes.padding[dim].sum(), 0) + 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: @@ -1063,9 +1075,8 @@ proc resolveBlockSizes(lctx: LayoutContext; space: AvailableSpace; # Calculate and resolve available width & height for absolutely positioned # boxes. -proc resolveAbsoluteSizes(lctx: LayoutContext; computed: CSSComputedValues): - ResolvedSizes = - let space = lctx.positioned[^1] +proc resolveAbsoluteSizes(lctx: LayoutContext; space: AvailableSpace; + computed: CSSComputedValues): ResolvedSizes = var sizes = ResolvedSizes( margin: resolveMargins(space.w, lctx, computed), padding: resolvePadding(space.w, lctx, computed), @@ -1133,7 +1144,7 @@ proc resolveFlexItemSizes(lctx: LayoutContext; space: AvailableSpace; proc resolveSizes(lctx: LayoutContext; space: AvailableSpace; computed: CSSComputedValues): ResolvedSizes = if computed{"position"} == PositionAbsolute: - return lctx.resolveAbsoluteSizes(computed) + return lctx.resolveAbsoluteSizes(space, computed) elif computed{"float"} != FloatNone: return lctx.resolveFloatSizes(space, computed) else: @@ -1257,6 +1268,38 @@ proc clearFloats(offset: var Offset; bctx: var BlockContext; clear: CSSClear) = bctx.clearOffset = y offset.y = y - bctx.bfcOffset.y +proc applyOverflowDimensions(parent: var Overflow; child: BlockBox) = + var childOverflow = child.state.overflow + for dim in DimensionType: + childOverflow[dim] += child.state.offset[dim] + parent[dim].expand(childOverflow[dim]) + +proc applyOverflowDimensions(box, child: BlockBox) = + box.state.overflow.applyOverflowDimensions(child) + +proc pushPositioned(lctx: LayoutContext) = + lctx.positioned.add(PositionedItem()) + +proc popPositioned(lctx: LayoutContext; overflow: var Overflow; + space: AvailableSpace) = + let item = lctx.positioned.pop() + for it in item.queue: + 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 + elif not child.computed{"right"}.auto: + child.state.offset.x = -child.state.positioned.right - + child.state.size.w - child.state.margin.right + if not child.computed{"top"}.auto: + child.state.offset.y = child.state.positioned.top + elif not child.computed{"bottom"}.auto: + child.state.offset.y = -child.state.positioned.bottom - + child.state.size.h - child.state.margin.bottom + overflow.applyOverflowDimensions(child) + type BlockState = object offset: Offset @@ -1341,12 +1384,6 @@ proc positionFloat(bctx: var BlockContext; child: BlockBox; bctx.exclusions.add(ex) bctx.maxFloatHeight = max(bctx.maxFloatHeight, ex.offset.y + ex.size.h) -proc applyOverflowDimensions(box, child: BlockBox) = - var childOverflow = child.state.overflow - for dim in DimensionType: - childOverflow[dim] += child.state.offset[dim] - box.state.overflow[dim].expand(childOverflow[dim]) - proc positionFloats(bctx: var BlockContext) = for f in bctx.unpositionedFloats: bctx.positionFloat(f.box, f.space, f.parentBps.offset) @@ -1396,6 +1433,9 @@ proc layoutListItem(bctx: var BlockContext; box: BlockBox; of ListStylePositionInside: bctx.layoutFlow(box, sizes) +const DisplayOuterInline = { + DisplayInlineBlock, DisplayInlineTableWrapper, DisplayInlineFlex +} proc addInlineBlock(ictx: var InlineContext; state: var InlineState; box: BlockBox) = let lctx = ictx.lctx @@ -1404,38 +1444,45 @@ proc addInlineBlock(ictx: var InlineContext; state: var InlineState; margin: sizes.margin, positioned: sizes.positioned ) - var bctx = BlockContext(lctx: lctx) - bctx.marginTodo.append(sizes.margin.top) - case box.computed{"display"} - of DisplayInlineBlock: bctx.layoutFlow(box, sizes) - of DisplayInlineTableWrapper: bctx.layoutTableWrapper(box, sizes) - of DisplayInlineFlex: bctx.layoutFlex(box, sizes) - else: assert false - assert bctx.unpositionedFloats.len == 0 - bctx.marginTodo.append(sizes.margin.bottom) - let marginTop = box.state.offset.y - let marginBottom = bctx.marginTodo.sum() - # If the highest float edge is higher than the box itself, set that as - # the box height. - box.state.size.h = max(box.state.size.h, bctx.maxFloatHeight - marginBottom) - box.state.offset.y = 0 - # Apply the block box's properties to the atom itself. - let iblock = InlineAtom( - t: iatInlineBlock, - innerbox: box, - offset: offset(x = sizes.margin.left, y = 0), - size: size( - w = box.outerSize(dtHorizontal), - h = box.state.size.h + 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: marginTop, - marginBottom: bctx.marginTodo.sum() - ) - discard ictx.addAtom(state, iastate, iblock) + 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: + # 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 proc addInlineImage(ictx: var InlineContext; state: var InlineState; @@ -1523,15 +1570,17 @@ proc layoutInline(ictx: var InlineContext; fragment: InlineFragment) = size: size(w = padding.start, h = ictx.cellHeight) )) ictx.lbstate.paddingTodo.add((fragment, 0)) - ictx.lbstate.size.w += padding.start - var state = InlineState( - fragment: fragment, - firstLine: true, - startOffsetTop: offset( - x = ictx.lbstate.widthAfterWhitespace, - y = ictx.lbstate.offsety - ) + fragment.state.startOffset = offset( + x = ictx.lbstate.widthAfterWhitespace, + y = ictx.lbstate.offsety ) + if ictx.lbstate.atoms.len > 0: + fragment.state.startOffset.y += ictx.cellHeight + ictx.lbstate.size.w += padding.start + var state = InlineState(fragment: fragment) + if stSplitStart in fragment.splitType and + computed{"position"} notin PositionStaticLike: + lctx.pushPositioned() ictx.applyLineHeight(ictx.lbstate, computed) case fragment.t of iftNewline: ictx.flushLine(state) @@ -1550,18 +1599,25 @@ proc layoutInline(ictx: var InlineContext; fragment: InlineFragment) = if stSplitEnd in fragment.splitType: ictx.lbstate.size.w += padding.send ictx.lbstate.size.w += computed{"margin-right"}.px(lctx, ictx.space.w) - if state.firstLine: - fragment.state.startOffset = offset( - x = state.startOffsetTop.x, - y = ictx.lbstate.offsety - ) - else: - fragment.state.startOffset = offset(x = 0, y = ictx.lbstate.offsety) if fragment.t != iftParent: if not ictx.textFragmentSeen: ictx.textFragmentSeen = true - ictx.root.fragment.state.startOffset = fragment.state.startOffset ictx.lastTextFragment = fragment + if stSplitEnd in fragment.splitType and + computed{"position"} notin PositionStaticLike: + # This is UB in CSS 2.1, I can't find a newer spec about it, + # and Gecko can't even layout it consistently (???) + # + # So I'm trying to follow Blink, though it's still not quite right. + # For one, space should really be the sum of all splits of this + # inline box, but I've wasted enough time on this already so I'm + # gonna stop here and say "good enough". + let space = availableSpace( + w = stretch(0), + h = stretch(ictx.root.state.size.h) + ) + #TODO this overflow calculation looks wrong + lctx.popPositioned(ictx.root.state.overflow, space) proc layoutRootInline0(bctx: var BlockContext; ictx: var InlineContext; root: RootInlineFragment; space: AvailableSpace; @@ -1594,18 +1650,6 @@ proc layoutRootInline(bctx: var BlockContext; root: RootInlineFragment; bctx.layoutRootInline0(ictx, root, space, computed, offset, bfcOffset) ictx.root.state.overflow.finalize(ictx.root.state.size) -proc positionAbsolute(box: BlockBox) = - if not box.computed{"left"}.auto: - box.state.offset.x = box.state.positioned.left + box.state.margin.left - elif not box.computed{"right"}.auto: - box.state.offset.x = -box.state.positioned.right - box.state.size.w - - box.state.margin.right - if not box.computed{"top"}.auto: - box.state.offset.y = box.state.positioned.top + box.state.margin.top - elif not box.computed{"bottom"}.auto: - box.state.offset.y = -box.state.positioned.bottom - box.state.size.h - - box.state.margin.bottom - proc positionRelative(lctx: LayoutContext; parent, box: BlockBox) = if not box.computed{"left"}.auto: box.state.offset.x += box.computed{"left"}.px(lctx, parent.state.size.w) @@ -2109,13 +2153,13 @@ proc postAlignChild(box, child: BlockBox; width: LayoutUnit) = proc layout(bctx: var BlockContext; box: BlockBox; sizes: ResolvedSizes) = case box.computed{"display"} - of DisplayBlock, DisplayFlowRoot, DisplayTableCaption: + of DisplayBlock, DisplayFlowRoot, DisplayTableCaption, DisplayInlineBlock: bctx.layoutFlow(box, sizes) of DisplayListItem: bctx.layoutListItem(box, sizes) - of DisplayTableWrapper: + of DisplayTableWrapper, DisplayInlineTableWrapper: bctx.layoutTableWrapper(box, sizes) - of DisplayFlex: + of DisplayFlex, DisplayInlineFlex: bctx.layoutFlex(box, sizes) else: assert false @@ -2255,7 +2299,8 @@ proc flushMain(fctx: var FlexContext; mctx: var FlexMainContext; proc layoutFlex(bctx: var BlockContext; box: BlockBox; sizes: ResolvedSizes) = assert box.inline == nil let lctx = bctx.lctx - lctx.pushPositioned(box, sizes) + if box.computed{"position"} notin PositionStaticLike: + lctx.pushPositioned() var fctx = FlexContext( lctx: lctx, box: box, @@ -2283,8 +2328,10 @@ proc layoutFlex(bctx: var BlockContext; box: BlockBox; sizes: ResolvedSizes) = lctx.layoutFlexChild(child, childSizes) if child.computed{"position"} == PositionAbsolute: # Absolutely positioned flex children do not participate in flex layout. - # I suspect this is a bit too simplistic, but seems to work? - child.positionAbsolute() + lctx.positioned[^1].queue.add(QueuedAbsolute( + child: child, + offset: offset(x = 0, y = 0) + )) continue if canWrap and (sizes.space[dim].t == scMinContent or sizes.space[dim].isDefinite and @@ -2309,7 +2356,12 @@ proc layoutFlex(bctx: var BlockContext; box: BlockBox; sizes: ResolvedSizes) = lctx.positionRelative(box, child) box.applyOverflowDimensions(child) box.state.overflow.finalize(box.state.size) - lctx.popPositioned(box) + if box.computed{"position"} notin PositionStaticLike: + let space = availableSpace( + w = stretch(box.state.size.w), + h = stretch(box.state.size.h) + ) + lctx.popPositioned(box.state.overflow, space) # Build an outer block box inside an existing block formatting context. proc layoutBlockChild(bctx: var BlockContext; box: BlockBox; @@ -2370,27 +2422,17 @@ func isParentResolved(state: BlockState; bctx: BlockContext): bool = return bctx.marginTarget != state.initialMarginTarget or state.prevParentBps != nil and state.prevParentBps.resolved -# Note: this does not include display types that cannot appear as block -# children. -func establishesBFC(computed: CSSComputedValues): bool = - return computed{"float"} != FloatNone or - computed{"position"} == PositionAbsolute or - computed{"display"} in {DisplayFlowRoot, DisplayTable, DisplayTableWrapper, - DisplayFlex} or - computed{"overflow"} notin {OverflowVisible, OverflowClip} - #TODO contain, grid, multicol, column-span - # Outer layout for block-level children that establish a BFC. # Returns the vertical size used (incl. margins). proc layoutBlockChildBFC(state: var BlockState; bctx: var BlockContext; child: BlockBox): LayoutUnit = + assert child.computed{"position"} != PositionAbsolute var marginBottomOut: LayoutUnit bctx.lctx.layoutRootBlock(child, state.space, state.offset, marginBottomOut) # Do not collapse margins of elements that do not participate in # the flow. - if child.computed{"position"} != PositionAbsolute and - child.computed{"float"} == FloatNone: + if child.computed{"float"} == FloatNone: bctx.marginTodo.append(child.state.margin.top) bctx.flushMargins(child) bctx.positionFloats() @@ -2438,10 +2480,19 @@ proc layoutBlockChildBFC(state: var BlockState; bctx: var BlockContext; # float's initial offset. child.state.offset.y += bctx.marginTodo.sum() # delta y is difference between old and new offsets (margin-top), sum - # of margin todo in bctx2 (margin-bottom) + height. + # of margin todo in bctx (margin-bottom) + height. return child.state.offset.y - state.offset.y + child.state.size.h + marginBottomOut +# Note: this does not include display types that cannot appear as block +# children. +func establishesBFC(computed: CSSComputedValues): bool = + return computed{"float"} != FloatNone or + computed{"display"} in {DisplayFlowRoot, DisplayTable, DisplayTableWrapper, + DisplayFlex} or + computed{"overflow"} notin {OverflowVisible, OverflowClip} + #TODO contain, grid, multicol, column-span + # Layout and place all children in the block box. # Box placement must occur during this pass, since child box layout in the # same block formatting context depends on knowing where the box offset is @@ -2450,6 +2501,21 @@ proc layoutBlockChildren(state: var BlockState; bctx: var BlockContext; parent: BlockBox) = for child in parent.nested: var dy: LayoutUnit = 0 # delta + if child.computed{"position"} == PositionAbsolute: + # Delay this block's layout until its parent's dimensions are + # actually known. + var offset = state.offset + # I *think* this is right, because we want to freeze the box at + # the layout's current position. So even if a next child increased + # the margin todo, that should not change the absolute block's + # position. + #TODO but I'm not sure if this is really what the standard says... + offset.y += bctx.marginTodo.sum() + bctx.lctx.positioned[^1].queue.add(QueuedAbsolute( + child: child, + offset: offset + )) + continue if child.computed.establishesBFC(): dy = state.layoutBlockChildBFC(bctx, child) else: @@ -2461,25 +2527,23 @@ proc layoutBlockChildren(state: var BlockState; bctx: var BlockContext; let childWidth = child.outerSize(dtHorizontal) state.xminwidth = max(state.xminwidth, child.state.xminwidth) let isfloat = child.computed{"float"} != FloatNone - if child.computed{"position"} != PositionAbsolute and not isfloat: - # Not absolute, and not a float. + if not isfloat: state.maxChildWidth = max(state.maxChildWidth, childWidth) state.offset.y += dy - elif isfloat: - if state.space.w.t == scFitContent: - # Float position depends on the available width, but in this case - # the parent width is not known. - # - # Set the "re-layout" flag, and skip this box. - # (If child boxes with fit-content have floats, those will be - # re-layouted too first, so we do not have to consider those here.) - state.needsReLayout = true - # Since we emulate max-content here, the float will not contribute to - # maxChildWidth in this iteration; instead, its outer width will be - # summed up in totalFloatWidth and added to maxChildWidth in - # initReLayout. - state.totalFloatWidth += childWidth - continue + elif state.space.w.t == scFitContent: + # Float position depends on the available width, but in this case + # the parent width is not known. + # + # Set the "re-layout" flag, and skip this box. + # (If child boxes with fit-content have floats, those will be + # re-layouted too first, so we do not have to consider those here.) + state.needsReLayout = true + # Since we emulate max-content here, the float will not contribute to + # maxChildWidth in this iteration; instead, its outer width will be + # summed up in totalFloatWidth and added to maxChildWidth in + # initReLayout. + state.totalFloatWidth += childWidth + else: state.maxChildWidth = max(state.maxChildWidth, childWidth) # Two cases exist: # a) The float cannot be positioned, because `box' has not resolved @@ -2550,20 +2614,16 @@ proc initReLayout(state: var BlockState; bctx: var BlockContext; # so we cannot do this in the first pass. proc repositionChildren(state: BlockState; box: BlockBox; lctx: LayoutContext) = for child in box.nested: - if child.computed{"position"} != PositionAbsolute: - box.postAlignChild(child, box.state.size.w) - case child.computed{"position"} - of PositionRelative: + box.postAlignChild(child, box.state.size.w) + if child.computed{"position"} == PositionRelative: lctx.positionRelative(box, child) - of PositionAbsolute: - child.positionAbsolute() - else: discard #TODO # Set overflow here, after the child has been positioned. box.applyOverflowDimensions(child) proc layoutBlock(bctx: var BlockContext; box: BlockBox; sizes: ResolvedSizes) = let lctx = bctx.lctx - lctx.pushPositioned(box, sizes) + if box.computed{"position"} notin PositionStaticLike: + lctx.pushPositioned() var state = BlockState( offset: offset(x = sizes.padding.left, y = sizes.padding.top), space: sizes.space, @@ -2599,7 +2659,12 @@ proc layoutBlock(bctx: var BlockContext; box: BlockBox; sizes: ResolvedSizes) = box.state.overflow.finalize(box.state.size) # Reset parentBps to the previous node. bctx.parentBps = state.prevParentBps - lctx.popPositioned(box) + if box.computed{"position"} notin PositionStaticLike: + let space = availableSpace( + w = stretch(box.state.size.w), + h = stretch(box.state.size.h) + ) + lctx.popPositioned(box.state.overflow, space) # 1st pass: build tree @@ -2666,6 +2731,8 @@ proc buildTableCaption(parent: var InnerBlockContext; styledNode: StyledNode; proc newInnerBlockContext(styledNode: StyledNode; box: BlockBox; lctx: LayoutContext; parent: ptr InnerBlockContext): InnerBlockContext proc pushInline(ctx: var InnerBlockContext; fragment: InlineFragment) +proc pushInlineBlock(ctx: var InnerBlockContext; styledNode: StyledNode; + computed: CSSComputedValues) func toTableWrapper(display: CSSDisplay): CSSDisplay = if display == DisplayTable: @@ -2806,10 +2873,14 @@ proc buildSomeBlock(ctx: var InnerBlockContext; styledNode: StyledNode; # Note: these also pop proc pushBlock(ctx: var InnerBlockContext; styledNode: StyledNode; computed: CSSComputedValues) = - ctx.iflush() - ctx.flush() - let box = ctx.buildSomeBlock(styledNode, computed) - ctx.outer.nested.add(box) + if computed{"position"} == PositionAbsolute and + (ctx.inline != nil or ctx.inlineStack.len > 0): + ctx.pushInlineBlock(styledNode, computed) + else: + ctx.iflush() + ctx.flush() + let box = ctx.buildSomeBlock(styledNode, computed) + ctx.outer.nested.add(box) proc pushInline(ctx: var InnerBlockContext; fragment: InlineFragment) = if ctx.inlineStack.len == 0: @@ -3233,7 +3304,7 @@ proc layout*(root: StyledNode; attrsp: ptr WindowAttributes): BlockBox = ) let lctx = LayoutContext( attrsp: attrsp, - positioned: @[space], + positioned: @[PositionedItem()], myRootProperties: rootProperties(), imgText: newStyledText("[img]"), videoText: newStyledText("[video]"), @@ -3244,4 +3315,5 @@ proc layout*(root: StyledNode; attrsp: ptr WindowAttributes): BlockBox = ctx.buildBlock() var marginBottomOut: LayoutUnit lctx.layoutRootBlock(box, space, offset(x = 0, y = 0), marginBottomOut) + lctx.popPositioned(box.state.overflow, space) return box diff --git a/src/layout/renderdocument.nim b/src/layout/renderdocument.nim index 93af649a..5ea01b8a 100644 --- a/src/layout/renderdocument.nim +++ b/src/layout/renderdocument.nim @@ -335,6 +335,12 @@ proc renderInlineFragment(grid: var FlexibleGrid; state: var RenderState; if bgcolor0.a > 0: grid.paintInlineFragment(state, fragment, offset, bgcolor0.rgb.cellColor()) + if fragment.computed{"position"} != PositionStatic: + if stSplitStart in fragment.splitType: + state.absolutePos.add(AbsolutePos( + offset: offset + fragment.state.startOffset, + # looks like it's OK to set size to 0 here + )) if fragment.t == iftParent: for child in fragment.children: grid.renderInlineFragment(state, child, offset, bgcolor0) @@ -362,14 +368,8 @@ proc renderInlineFragment(grid: var FlexibleGrid; state: var RenderState; bmp: atom.bmp )) if fragment.computed{"position"} != PositionStatic: - if fragment.splitType != {stSplitStart, stSplitEnd}: - if stSplitStart in fragment.splitType: - state.absolutePos.add(AbsolutePos( - offset: offset + fragment.state.startOffset, - # looks like it's OK to set size to 0 here - )) - if stSplitEnd in fragment.splitType: - discard state.absolutePos.pop() + if stSplitEnd in fragment.splitType: + discard state.absolutePos.pop() proc renderRootInlineFragment(grid: var FlexibleGrid; state: var RenderState; root: RootInlineFragment; offset: Offset) = |