diff options
author | bptato <nincsnevem662@gmail.com> | 2024-05-29 18:41:08 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-05-29 18:53:13 +0200 |
commit | efbbe82f2387368ec771ab1b58ba735b63f9c7e3 (patch) | |
tree | 4302b2db1c8c05a4db408021f43c4fda34f74ce8 | |
parent | 6f6b6e3d88f16c80fd5b7a6f8e7882628900294f (diff) | |
download | chawan-efbbe82f2387368ec771ab1b58ba735b63f9c7e3.tar.gz |
layout: misc refactorings
-rw-r--r-- | src/layout/box.nim | 30 | ||||
-rw-r--r-- | src/layout/engine.nim | 365 | ||||
-rw-r--r-- | src/layout/renderdocument.nim | 3 |
3 files changed, 205 insertions, 193 deletions
diff --git a/src/layout/box.nim b/src/layout/box.nim index d607ea68..ff11c906 100644 --- a/src/layout/box.nim +++ b/src/layout/box.nim @@ -27,11 +27,9 @@ type of iatImage: bmp*: Bitmap - RootInlineFragment* = ref object + RootInlineFragmentState* = object # offset relative to parent offset*: Offset - # root fragment - fragment*: InlineFragment # baseline of the first line box firstBaseline*: LayoutUnit # baseline of the last line box @@ -40,6 +38,10 @@ type xminwidth*: LayoutUnit size*: Size + RootInlineFragment* = ref object + fragment*: InlineFragment # root fragment + state*: RootInlineFragmentState + SplitType* = enum stSplitStart, stSplitEnd @@ -63,11 +65,11 @@ type bmp*: Bitmap box*: BlockBox - RelativeRect* = object - top*: LayoutUnit - bottom*: LayoutUnit - left*: LayoutUnit - right*: LayoutUnit + Span* = object + start*: LayoutUnit + send*: LayoutUnit + + RelativeRect* = array[DimensionType, Span] BlockBoxLayoutState* = object offset*: Offset @@ -141,3 +143,15 @@ func `-`*(a, b: Offset): Offset = proc `+=`*(a: var Offset; b: Offset) = a.x += b.x a.y += b.y + +func left*(s: RelativeRect): LayoutUnit = + return s[dtHorizontal].start + +func right*(s: RelativeRect): LayoutUnit = + return s[dtHorizontal].send + +func top*(s: RelativeRect): LayoutUnit = + return s[dtVertical].start + +func bottom*(s: RelativeRect): LayoutUnit = + return s[dtVertical].send diff --git a/src/layout/engine.nim b/src/layout/engine.nim index e10c8890..5cc32dd1 100644 --- a/src/layout/engine.nim +++ b/src/layout/engine.nim @@ -36,40 +36,29 @@ type AvailableSpace = array[DimensionType, SizeConstraint] - MinMaxSpan = object - min: LayoutUnit - max: LayoutUnit - ResolvedSizes = object margin: RelativeRect padding: RelativeRect positioned: RelativeRect space: AvailableSpace - minMaxSizes: array[DimensionType, MinMaxSpan] + minMaxSizes: array[DimensionType, Span] -const DefaultSpan = MinMaxSpan(min: 0, max: LayoutUnit.high) +const DefaultSpan = Span(start: 0, send: LayoutUnit.high) proc `minWidth=`(sizes: var ResolvedSizes; w: LayoutUnit) = - sizes.minMaxSizes[dtHorizontal].min = w + sizes.minMaxSizes[dtHorizontal].start = w proc `maxWidth=`(sizes: var ResolvedSizes; w: LayoutUnit) = - sizes.minMaxSizes[dtHorizontal].max = w + sizes.minMaxSizes[dtHorizontal].send = w proc `minHeight=`(sizes: var ResolvedSizes; h: LayoutUnit) = - sizes.minMaxSizes[dtVertical].min = h + sizes.minMaxSizes[dtVertical].start = h proc `maxHeight=`(sizes: var ResolvedSizes; h: LayoutUnit) = - sizes.minMaxSizes[dtVertical].max = h + sizes.minMaxSizes[dtVertical].send = h func dimSum(rect: RelativeRect; dim: DimensionType): LayoutUnit = - case dim - of dtHorizontal: return rect.left + rect.right - of dtVertical: return rect.top + rect.bottom - -func dimStart(rect: RelativeRect; dim: DimensionType): LayoutUnit = - case dim - of dtHorizontal: return rect.left - of dtVertical: return rect.top + return rect[dim].start + rect[dim].send func opposite(dim: DimensionType): DimensionType = case dim @@ -208,7 +197,7 @@ type LineBoxState = object atomstates: seq[InlineAtomState] baseline: LayoutUnit - lineheight: LayoutUnit + lineHeight: LayoutUnit paddingTop: LayoutUnit paddingBottom: LayoutUnit line: LineBox @@ -241,7 +230,6 @@ type hasshy: bool lctx: LayoutContext lines: seq[LineBox] - minwidth: LayoutUnit space: AvailableSpace whitespacenum: int whitespaceIsLF: bool @@ -271,16 +259,16 @@ func whitespacepre(computed: CSSComputedValues): bool = func nowrap(computed: CSSComputedValues): bool = computed{"white-space"} in {WhitespaceNowrap, WhitespacePre} -func cellwidth(lctx: LayoutContext): int = +func cellWidth(lctx: LayoutContext): int = lctx.attrs.ppc -func cellwidth(ictx: InlineContext): int = - ictx.lctx.cellwidth +func cellWidth(ictx: InlineContext): int = + ictx.lctx.cellWidth -func cellheight(lctx: LayoutContext): int = +func cellHeight(lctx: LayoutContext): int = lctx.attrs.ppl -func cellheight(ictx: InlineContext): int = +func cellHeight(ictx: InlineContext): int = ictx.lctx.attrs.ppl template atoms(state: LineBoxState): untyped = @@ -293,7 +281,7 @@ template offsety(state: LineBoxState): untyped = state.line.offsety func size(ictx: var InlineContext): var Size = - ictx.root.size + ictx.root.state.size # Whitespace between words func computeShift(ictx: InlineContext; state: InlineState): LayoutUnit = @@ -306,31 +294,31 @@ func computeShift(ictx: InlineContext; state: InlineState): LayoutUnit = if ictx.currentLine.atoms.len == 0 or ictx.currentLine.atoms[^1].t == iatSpacing: return 0 - return ictx.cellwidth * ictx.whitespacenum + return ictx.cellWidth * ictx.whitespacenum proc applyLineHeight(ictx: InlineContext; state: var LineBoxState; computed: CSSComputedValues) = let lctx = ictx.lctx #TODO this should be computed during cascading. - let lineheight = if computed{"line-height"}.auto: # ergo normal - lctx.cellheight.toLayoutUnit + let lineHeight = if computed{"line-height"}.auto: # ergo normal + lctx.cellHeight.toLayoutUnit else: # Percentage: refers to the font size of the element itself. - computed{"line-height"}.px(lctx, lctx.cellheight) + computed{"line-height"}.px(lctx, lctx.cellHeight) let paddingTop = computed{"padding-top"}.px(lctx, ictx.space.w) let paddingBottom = computed{"padding-bottom"}.px(lctx, ictx.space.w) state.paddingTop = max(paddingTop, state.paddingTop) state.paddingBottom = max(paddingBottom, state.paddingBottom) - state.lineheight = max(lineheight, state.lineheight) + state.lineHeight = max(lineHeight, state.lineHeight) proc newWord(ictx: var InlineContext; state: var InlineState) = ictx.word = InlineAtom( t: iatWord, - size: size(w = 0, h = ictx.cellheight) + size: size(w = 0, h = ictx.cellHeight) ) ictx.wordstate = InlineAtomState( vertalign: state.computed{"vertical-align"}, - baseline: ictx.cellheight + baseline: ictx.cellHeight ) ictx.wrappos = -1 ictx.hasshy = false @@ -363,23 +351,15 @@ proc horizontalAlignLines(ictx: var InlineContext; align: CSSTextAlign) = atom.offset.x += x ictx.size.w = max(atom.offset.x + atom.size.w, ictx.size.w) -# Align atoms (inline boxes, text, etc.) vertically (i.e. along the inline -# axis) inside the line. -proc verticalAlignLine(ictx: var InlineContext) = - # Start with line-height as the baseline and line height. - let lineheight = ictx.currentLine.lineheight - ictx.currentLine.size.h = lineheight - var baseline = lineheight - - # Calculate the line's baseline based on atoms' baseline. - # Also, collect the maximum vertical margins of inline blocks. - var marginTop: LayoutUnit = 0 - var bottomEdge = baseline - for i, atom in ictx.currentLine.atoms: - let iastate = ictx.currentLine.atomstates[i] +# Calculate the line's baseline based on its atoms' baseline. +func calcBaseline(currentLine: LineBoxState; lctx: LayoutContext): LayoutUnit = + let lineHeight = currentLine.lineHeight + var baseline = lineHeight + for i, atom in currentLine.atoms: + let iastate = currentLine.atomstates[i] case iastate.vertalign.keyword of VerticalAlignBaseline: - let len = iastate.vertalign.length.px(ictx.lctx, lineheight) + let len = iastate.vertalign.length.px(lctx, lineHeight) baseline = max(baseline, iastate.baseline + len) of VerticalAlignTop, VerticalAlignBottom: baseline = max(baseline, atom.size.h) @@ -387,45 +367,49 @@ proc verticalAlignLine(ictx: var InlineContext) = baseline = max(baseline, atom.size.h div 2) else: baseline = max(baseline, iastate.baseline) - - let ch = ictx.cellheight - baseline = baseline.round(ch) - - # Resize the line's height based on atoms' height and baseline. - # The line height should be at least as high as the highest baseline used by - # an atom plus that atom's height. - for i, atom in ictx.currentLine.atoms: - let iastate = ictx.currentLine.atomstates[i] + return baseline + +# Resize the line's height based on atoms' height and baseline. +# The line height should be at least as high as the highest baseline used by +# an atom plus that atom's height. +func resizeLine(currentLine: LineBoxState; lctx: LayoutContext): LayoutUnit = + let baseline = currentLine.baseline + let lineHeight = currentLine.lineHeight + var h = currentLine.size.h + for i, atom in currentLine.atoms: + let iastate = currentLine.atomstates[i] # In all cases, the line's height must at least equal the atom's height. # (Where the atom is actually placed is irrelevant here.) - ictx.currentLine.size.h = max(ictx.currentLine.size.h, atom.size.h) + h = max(h, atom.size.h) case iastate.vertalign.keyword of VerticalAlignBaseline: # Line height must be at least as high as # (line baseline) - (atom baseline) + (atom height) + (extra height). - let len = iastate.vertalign.length.px(ictx.lctx, lineheight) - ictx.currentLine.size.h = max(baseline - iastate.baseline + - atom.size.h + len, ictx.currentLine.size.h) + let len = iastate.vertalign.length.px(lctx, lineHeight) + h = max(baseline - iastate.baseline + atom.size.h + len, h) of VerticalAlignMiddle: # Line height must be at least # (line baseline) + (atom height / 2). - ictx.currentLine.size.h = max(baseline + atom.size.h div 2, - ictx.currentLine.size.h) + h = max(baseline + atom.size.h div 2, h) of VerticalAlignTop, VerticalAlignBottom: # Line height must be at least atom height (already ensured above.) discard else: # See baseline (with len = 0). - ictx.currentLine.size.h = max(baseline - iastate.baseline + - atom.size.h, ictx.currentLine.size.h) + h = max(baseline - iastate.baseline + atom.size.h, h) + return h - # Now we can calculate the actual position of atoms inside the line. - for i, atom in ictx.currentLine.atoms: - let iastate = ictx.currentLine.atomstates[i] +# returns marginTop +proc positionAtoms(currentLine: LineBoxState; lctx: LayoutContext): LayoutUnit = + let lineHeight = currentLine.lineHeight + let baseline = currentLine.baseline + var marginTop: LayoutUnit = 0 + for i, atom in currentLine.atoms: + let iastate = currentLine.atomstates[i] case iastate.vertalign.keyword of VerticalAlignBaseline: # Atom is placed at (line baseline) - (atom baseline) - len - let len = iastate.vertalign.length.px(ictx.lctx, lineheight) + let len = iastate.vertalign.length.px(lctx, lineHeight) atom.offset.y = baseline - iastate.baseline - len of VerticalAlignMiddle: # Atom is placed at (line baseline) - ((atom height) / 2) @@ -435,40 +419,56 @@ proc verticalAlignLine(ictx: var InlineContext) = atom.offset.y = 0 of VerticalAlignBottom: # Atom is placed at the bottom of the line. - atom.offset.y = ictx.currentLine.size.h - atom.size.h + atom.offset.y = currentLine.size.h - atom.size.h else: # See baseline (with len = 0). atom.offset.y = baseline - iastate.baseline - # Find the best top margin and bottom edge of all atoms. - # In fact, we are looking for the lowest top edge and the highest bottom - # edge of the line, so we have to do this after we know where the atoms - # will be placed. + # Find the best top margin of all atoms. + # We are looking for the lowest top edge of the line, so we have to do this + # after we know where the atoms will be placed. + # Note: we used to calculate the bottom edge based on margins too, but this + # generated pointless empty lines so I removed it. marginTop = max(iastate.marginTop - atom.offset.y, marginTop) - bottomEdge = max(atom.offset.y + atom.size.h + iastate.marginBottom, - bottomEdge) - + return marginTop + +proc shiftAtoms(currentLine: var LineBoxState; marginTop: LayoutUnit; + cellHeight: int) = + let paddingTop = currentLine.paddingTop + let offsety = currentLine.offsety + for atom in currentLine.atoms: + let atomy = atom.offset.y + atom.offset.y = (atomy + marginTop + paddingTop + offsety).round(cellHeight) + currentLine.minHeight = max(currentLine.minHeight, + atomy - offsety + atom.size.h) + +# Align atoms (inline boxes, text, etc.) vertically (i.e. along the block/y +# axis) inside the line. +proc verticalAlignLine(ictx: var InlineContext) = + # Start with line-height as the baseline and line height. + let lineHeight = ictx.currentLine.lineHeight + ictx.currentLine.size.h = lineHeight + let ch = ictx.cellHeight + # Find baseline. + ictx.currentLine.baseline = ictx.currentLine.calcBaseline(ictx.lctx).round(ch) + # Resize according to the baseline and atom sizes. + ictx.currentLine.size.h = ictx.currentLine.resizeLine(ictx.lctx) + # Now we can calculate the actual position of atoms inside the line. + let marginTop = ictx.currentLine.positionAtoms(ictx.lctx) # Finally, offset all atoms' y position by the largest top margin and the # line box's top padding. - let paddingTop = ictx.currentLine.paddingTop - let offsety = ictx.currentLine.offsety - for atom in ictx.currentLine.atoms: - atom.offset.y = (atom.offset.y + marginTop + paddingTop + offsety).round(ch) - ictx.currentLine.minHeight = max(ictx.currentLine.minHeight, - atom.offset.y - offsety + atom.size.h) - - ictx.currentLine.baseline = baseline + ictx.currentLine.shiftAtoms(marginTop, ch) #TODO this does not really work with rounding :/ ictx.currentLine.baseline += ictx.currentLine.paddingTop # Ensure that the line is exactly as high as its highest atom demands, # rounded up to the next line. # (This is almost the same as completely ignoring line height. However, there - # is a difference because line height is still taken into account when + # *is* a difference, because line height is still taken into account when # positioning the atoms.) ictx.currentLine.size.h = ictx.currentLine.minHeight.ceilTo(ch) # Now, if we got a height that is lower than cell height *and* line height, # then set it back to the cell height. (This is to avoid the situation where # we would swallow hard line breaks with <br>.) - if lineheight >= ch and ictx.currentLine.size.h < ch: + if lineHeight >= ch and ictx.currentLine.size.h < ch: ictx.currentLine.size.h = ch # Set the line height to size.h. ictx.currentLine.line.height = ictx.currentLine.size.h @@ -499,7 +499,7 @@ proc flushWhitespace(ictx: var InlineContext; state: InlineState; ictx.currentLine.charwidth += ictx.whitespacenum ictx.whitespacenum = 0 if shift > 0: - ictx.addSpacing(shift, ictx.cellheight, state, hang) + ictx.addSpacing(shift, ictx.cellHeight, state, hang) # Prepare the next line's initial width and available width. # (If space on the left is excluded by floats, set the initial width to @@ -540,8 +540,8 @@ proc finishLine(ictx: var InlineContext; state: var InlineState; wrap: bool; # * set first baseline if this is the first line box # * always set last baseline (so the baseline of the last line box remains) if ictx.lines.len == 0: - ictx.root.firstBaseline = y + ictx.currentLine.baseline - ictx.root.baseline = y + ictx.currentLine.baseline + ictx.root.state.firstBaseline = y + ictx.currentLine.baseline + ictx.root.state.baseline = y + ictx.currentLine.baseline ictx.size.h += ictx.currentLine.size.h let lineWidth = if wrap: ictx.currentLine.availableWidth @@ -645,7 +645,7 @@ proc addBackgroundAreas(ictx: var InlineContext; rootFragment: InlineFragment) = )) lineSkipped = false -func minwidth(atom: InlineAtom): LayoutUnit = +func xminwidth(atom: InlineAtom): LayoutUnit = if atom.t == iatInlineBlock: return atom.innerbox.state.xminwidth return atom.size.w @@ -688,15 +688,15 @@ proc addAtom(ictx: var InlineContext; state: var InlineState; #TODO this is inefficient while ictx.shouldWrap2(atom.size.w + shift): ictx.applyLineHeight(ictx.currentLine, state.computed) - ictx.currentLine.lineheight = max(ictx.currentLine.lineheight, - ictx.cellheight) + ictx.currentLine.lineHeight = max(ictx.currentLine.lineHeight, + ictx.cellHeight) 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 shift > 0: - ictx.addSpacing(shift, ictx.cellheight, state) - ictx.minwidth = max(ictx.minwidth, atom.minwidth) + ictx.addSpacing(shift, ictx.cellHeight, state) + ictx.root.state.xminwidth = max(ictx.root.state.xminwidth, atom.xminwidth) ictx.applyLineHeight(ictx.currentLine, state.computed) if atom.t != iatWord: ictx.currentLine.charwidth = 0 @@ -723,7 +723,7 @@ proc addWordEOL(ictx: var InlineContext; state: var InlineState): bool = ictx.hasshy = false result = ictx.addWord(state) ictx.word.str = leftstr - ictx.word.size.w = leftstr.width() * ictx.cellwidth + ictx.word.size.w = leftstr.width() * ictx.cellWidth else: result = ictx.addWord(state) @@ -742,19 +742,19 @@ proc checkWrap(ictx: var InlineContext; state: var InlineState; r: Rune) = case state.computed{"word-break"} of WordBreakNormal: if rw == 2 or ictx.wrappos != -1: # break on cjk and wrap opportunities - let plusWidth = ictx.word.size.w + shift + rw * ictx.cellwidth + let plusWidth = ictx.word.size.w + shift + rw * ictx.cellWidth if ictx.shouldWrap(plusWidth, nil): if not ictx.addWordEOL(state): # no line wrapping occured in addAtom ictx.finishLine(state, wrap = true) ictx.whitespacenum = 0 of WordBreakBreakAll: - let plusWidth = ictx.word.size.w + shift + rw * ictx.cellwidth + let plusWidth = ictx.word.size.w + shift + rw * ictx.cellWidth if ictx.shouldWrap(plusWidth, nil): if not ictx.addWordEOL(state): # no line wrapping occured in addAtom ictx.finishLine(state, wrap = true) ictx.whitespacenum = 0 of WordBreakKeepAll: - let plusWidth = ictx.word.size.w + shift + rw * ictx.cellwidth + let plusWidth = ictx.word.size.w + shift + rw * ictx.cellWidth if ictx.shouldWrap(plusWidth, nil): ictx.finishLine(state, wrap = true) ictx.whitespacenum = 0 @@ -823,7 +823,7 @@ proc layoutTextLoop(ictx: var InlineContext; state: var InlineState; ictx.checkWrap(state, r) ictx.word.str &= c let w = r.width() - ictx.word.size.w += w * ictx.cellwidth + ictx.word.size.w += w * ictx.cellWidth ictx.currentLine.charwidth += w if c == '-': # ascii dash ictx.wrappos = ictx.word.str.len @@ -839,7 +839,7 @@ proc layoutTextLoop(ictx: var InlineContext; state: var InlineState; else: ictx.word.str &= r let w = r.width() - ictx.word.size.w += w * ictx.cellwidth + ictx.word.size.w += w * ictx.cellWidth ictx.currentLine.charwidth += w discard ictx.addWord(state) let shift = ictx.computeShift(state) @@ -891,61 +891,73 @@ proc resolveContentWidth(sizes: var ResolvedSizes; widthpx: LayoutUnit; if not sizes.space.w.isDefinite(): # width is indefinite, so no conflicts can be resolved here. return - let total = widthpx + sizes.margin.left + sizes.margin.right + - sizes.padding.left + sizes.padding.right + let total = widthpx + sizes.margin.dimSum(dtHorizontal) + + sizes.padding.dimSum(dtHorizontal) let underflow = containingWidth.u - total if isauto or sizes.space.w.t == scFitContent: if underflow >= 0: sizes.space.w = SizeConstraint(t: sizes.space.w.t, u: underflow) else: - sizes.margin.right += underflow + sizes.margin[dtHorizontal].send += underflow elif underflow > 0: if not computed{"margin-left"}.auto and not computed{"margin-right"}.auto: - sizes.margin.right += underflow + sizes.margin[dtHorizontal].send += underflow elif not computed{"margin-left"}.auto and computed{"margin-right"}.auto: - sizes.margin.right = underflow + sizes.margin[dtHorizontal].send = underflow elif computed{"margin-left"}.auto and not computed{"margin-right"}.auto: - sizes.margin.left = underflow + sizes.margin[dtHorizontal].start = underflow else: - sizes.margin.left = underflow div 2 - sizes.margin.right = underflow div 2 + sizes.margin[dtHorizontal].start = underflow div 2 + sizes.margin[dtHorizontal].send = underflow div 2 proc resolveMargins(availableWidth: SizeConstraint; lctx: LayoutContext; computed: CSSComputedValues): RelativeRect = # Note: we use availableWidth for percentage resolution intentionally. - return RelativeRect( - top: computed{"margin-top"}.px(lctx, availableWidth), - bottom: computed{"margin-bottom"}.px(lctx, availableWidth), - left: computed{"margin-left"}.px(lctx, availableWidth), - right: computed{"margin-right"}.px(lctx, availableWidth) - ) + return [ + dtHorizontal: Span( + start: computed{"margin-left"}.px(lctx, availableWidth), + send: computed{"margin-right"}.px(lctx, availableWidth), + ), + dtVertical: Span( + start: computed{"margin-top"}.px(lctx, availableWidth), + send: computed{"margin-bottom"}.px(lctx, availableWidth), + ) + ] proc resolvePadding(availableWidth: SizeConstraint; lctx: LayoutContext; computed: CSSComputedValues): RelativeRect = # Note: we use availableWidth for percentage resolution intentionally. - return RelativeRect( - top: computed{"padding-top"}.px(lctx, availableWidth), - bottom: computed{"padding-bottom"}.px(lctx, availableWidth), - left: computed{"padding-left"}.px(lctx, availableWidth), - right: computed{"padding-right"}.px(lctx, availableWidth) - ) + return [ + dtHorizontal: Span( + start: computed{"padding-left"}.px(lctx, availableWidth), + send: computed{"padding-right"}.px(lctx, availableWidth) + ), + dtVertical: Span( + start: computed{"padding-top"}.px(lctx, availableWidth), + send: computed{"padding-bottom"}.px(lctx, availableWidth), + ) + ] func resolvePositioned(space: AvailableSpace; lctx: LayoutContext; computed: CSSComputedValues): RelativeRect = # As per standard, vertical percentages refer to the *height*, not the width # (unlike with margin/padding) - return RelativeRect( - top: computed{"top"}.px(lctx, space.h), - bottom: computed{"bottom"}.px(lctx, space.h), - left: computed{"left"}.px(lctx, space.w), - right: computed{"right"}.px(lctx, space.w) - ) + return [ + dtHorizontal: Span( + start: computed{"left"}.px(lctx, space.w), + send: computed{"right"}.px(lctx, space.w) + ), + dtVertical: Span( + start: computed{"top"}.px(lctx, space.h), + send: computed{"bottom"}.px(lctx, space.h), + ) + ] proc resolveBlockWidth(sizes: var ResolvedSizes; containingWidth: SizeConstraint; computed: CSSComputedValues; lctx: LayoutContext) = let width = computed{"width"} - let padding = sizes.padding.left + sizes.padding.right + let padding = sizes.padding.dimSum(dtHorizontal) var widthpx: LayoutUnit = 0 if width.canpx(containingWidth): widthpx = width.spx(lctx, containingWidth, computed, padding) @@ -1084,8 +1096,8 @@ proc resolveFloatSizes(lctx: LayoutContext; space: AvailableSpace; h = space.h ) let padding = resolvePadding(space.w, lctx, computed) - let inlinePadding = padding.left + padding.right - let blockPadding = padding.top + padding.bottom + let inlinePadding = padding.dimSum(dtHorizontal) + let blockPadding = padding.dimSum(dtVertical) let minWidth: LayoutUnit = if not computed{"min-width"}.auto: computed{"min-width"}.spx(lctx, space.w, computed, inlinePadding) else: @@ -1120,14 +1132,13 @@ proc resolveFloatSizes(lctx: LayoutContext; space: AvailableSpace; padding: padding, space: space, minMaxSizes: [ - dtHorizontal: MinMaxSpan(min: minWidth, max: maxWidth), - dtVertical: MinMaxSpan(min: minHeight, max: maxHeight) + dtHorizontal: Span(start: minWidth, send: maxWidth), + dtVertical: Span(start: minHeight, send: maxHeight) ] ) # Calculate and resolve available width & height for box children. -# containingWidth: width of the containing box -# containingHeight: ditto; but with height. +# space: width/height of the containing box # Note that this is not the final content size, just the amount of space # available for content. # The percentage width/height is generally @@ -1172,8 +1183,8 @@ proc applySize(box: BlockBox; sizes: ResolvedSizes; # Make the box as small/large as the content's width or specified width. box.state.size[dim] = maxChildSize.applySizeConstraint(space[dim]) # Then, clamp it to minWidth and maxWidth (if applicable). - let minMax = sizes.minMaxSizes[dim] - box.state.size[dim] = clamp(box.state.size[dim], minMax.min, minMax.max) + let span = sizes.minMaxSizes[dim] + box.state.size[dim] = clamp(box.state.size[dim], span.start, span.send) proc applyWidth(box: BlockBox; sizes: ResolvedSizes; maxChildWidth: LayoutUnit; space: AvailableSpace) = @@ -1203,13 +1214,14 @@ proc layoutInline(bctx: var BlockContext; box: BlockBox; sizes: ResolvedSizes) = bfcOffset.y += box.state.offset.y + offset.y bctx.layoutRootInline(box.inline, sizes.space, box.computed, offset, bfcOffset) - box.state.xminwidth = max(box.state.xminwidth, box.inline.xminwidth) - box.state.size.w = box.inline.size.w + sizes.padding.dimSum(dtHorizontal) - box.applyWidth(sizes, box.inline.size.w) - box.applyHeight(sizes, box.inline.size.h) + box.state.xminwidth = max(box.state.xminwidth, box.inline.state.xminwidth) + box.state.size.w = box.inline.state.size.w + + sizes.padding.dimSum(dtHorizontal) + box.applyWidth(sizes, box.inline.state.size.w) + box.applyHeight(sizes, box.inline.state.size.h) box.applyPadding(sizes.padding) - box.state.baseline = offset.y + box.inline.baseline - box.state.firstBaseline = offset.y + box.inline.firstBaseline + box.state.baseline = offset.y + box.inline.state.baseline + box.state.firstBaseline = offset.y + box.inline.state.firstBaseline const DisplayBlockLike = {DisplayBlock, DisplayListItem, DisplayInlineBlock} @@ -1344,10 +1356,6 @@ proc positionFloats(bctx: var BlockContext) = bctx.positionFloat(f.box, f.space, f.parentBps.offset) bctx.unpositionedFloats.setLen(0) -const RowGroupBox = { - DisplayTableRowGroup, DisplayTableHeaderGroup, DisplayTableFooterGroup -} - proc layoutFlow(bctx: var BlockContext; box: BlockBox; sizes: ResolvedSizes) = if box.canFlushMargins(sizes): bctx.flushMargins(box) @@ -1385,17 +1393,17 @@ proc layoutListItem(bctx: var BlockContext; box: BlockBox; # take inner box min width etc. box.state = content.state content.state.offset = offset(x = 0, y = 0) - content.state.margin = RelativeRect() - content.state.positioned = RelativeRect() + content.state.margin = [Span(), Span()] + content.state.positioned = [Span(), Span()] of ListStylePositionInside: bctx.layoutFlow(box, sizes) -# parentWidth, parentHeight: width/height of the containing block. +# space: width/height of the containing block. proc addInlineBlock(ictx: var InlineContext; state: var InlineState; - box: BlockBox; parentWidth, parentHeight: SizeConstraint) = + box: BlockBox; space: AvailableSpace) = let lctx = ictx.lctx - let percHeight = parentHeight.toPercSize() - let space = availableSpace(w = parentWidth, h = maxContent()) + let percHeight = space.h.toPercSize() + let space = availableSpace(w = space.w, h = maxContent()) let sizes = lctx.resolveFloatSizes(space, percHeight, box.computed) box.state = BlockBoxLayoutState( margin: sizes.margin, @@ -1404,14 +1412,10 @@ proc addInlineBlock(ictx: var InlineContext; state: var InlineState; 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 + 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 @@ -1453,9 +1457,8 @@ proc layoutChildren(ictx: var InlineContext; state: var InlineState; node: child.node, fragment: child ) - let w = fitContent(ictx.space.w) - let h = ictx.space.h - ictx.addInlineBlock(state, child.box, w, h) + let space = availableSpace(w = fitContent(ictx.space.w), h = ictx.space.h) + ictx.addInlineBlock(state, child.box, space) else: assert false @@ -1487,7 +1490,7 @@ proc layoutInline(ictx: var InlineContext; fragment: InlineFragment) = ictx.firstTextFragment = fragment ictx.lastTextFragment = fragment if fragment.bmp != nil: - let h = int(fragment.bmp.height).toLayoutUnit().ceilTo(ictx.cellheight) + let h = int(fragment.bmp.height).toLayoutUnit().ceilTo(ictx.cellHeight) let iastate = InlineAtomState( vertalign: state.computed{"vertical-align"}, baseline: h @@ -1518,12 +1521,7 @@ proc layoutInline(ictx: var InlineContext; fragment: InlineFragment) = proc layoutRootInline(bctx: var BlockContext; root: RootInlineFragment; space: AvailableSpace; computed: CSSComputedValues; offset, bfcOffset: Offset) = - root.offset = offset - root.size = size(w = 0, h = 0) - root.firstBaseline = 0 - root.baseline = 0 - root.xminwidth = 0 - root.size = size(w = 0, h = 0) + root.state = RootInlineFragmentState(offset: offset) var ictx = bctx.initInlineContext(space, bfcOffset, root) ictx.layoutInline(root.fragment) if ictx.firstTextFragment != nil: @@ -1533,7 +1531,6 @@ proc layoutRootInline(bctx: var BlockContext; root: RootInlineFragment; ictx.finishLine(state, wrap = false) ictx.horizontalAlignLines(computed{"text-align"}) ictx.addBackgroundAreas(root.fragment) - root.xminwidth = ictx.minwidth proc positionAbsolute(lctx: LayoutContext; box: BlockBox; margin: RelativeRect) = @@ -1564,6 +1561,9 @@ proc positionRelative(parent, box: BlockBox) = box.state.size.h # Note: caption is not included here +const RowGroupBox = { + DisplayTableRowGroup, DisplayTableHeaderGroup, DisplayTableFooterGroup +} const ProperTableChild = RowGroupBox + { DisplayTableRow, DisplayTableColumn, DisplayTableColumnGroup } @@ -2089,23 +2089,21 @@ type lctx: LayoutContext pending: seq[FlexPendingItem] -const FlexReverse = {FlexDirectionRowReverse, FlexDirectionColumnReverse} const FlexRow = {FlexDirectionRow, FlexDirectionRowReverse} func outerSize(box: BlockBox; dim: DimensionType): LayoutUnit = return box.state.margin.dimSum(dim) + box.state.size[dim] proc updateMaxSizes(mctx: var FlexMainContext; child: BlockBox) = - mctx.maxSize.w = max(mctx.maxSize.w, child.state.size.w) - mctx.maxSize.h = max(mctx.maxSize.h, child.state.size.h) - mctx.maxMargin.left = max(mctx.maxMargin.left, child.state.margin.left) - mctx.maxMargin.right = max(mctx.maxMargin.right, child.state.margin.right) - mctx.maxMargin.top = max(mctx.maxMargin.top, child.state.margin.top) - mctx.maxMargin.bottom = max(mctx.maxMargin.bottom, child.state.margin.bottom) + for dim in DimensionType: + mctx.maxSize[dim] = max(mctx.maxSize[dim], child.state.size[dim]) + mctx.maxMargin[dim].start = max(mctx.maxMargin[dim].start, + child.state.margin[dim].start) + mctx.maxMargin[dim].send = max(mctx.maxMargin[dim].send, + child.state.margin[dim].send) proc redistributeMainSize(mctx: var FlexMainContext; sizes: ResolvedSizes; dim: DimensionType) = - #TODO actually use flex-basis let lctx = mctx.lctx let odim = dim.opposite if sizes.space[dim].isDefinite: @@ -2128,7 +2126,7 @@ proc redistributeMainSize(mctx: var FlexMainContext; sizes: ResolvedSizes; var u = it.child.state.size[dim] + (unit * it.weights[wt]).toLayoutUnit() # check for min/max violation - var minu = it.sizes.minMaxSizes[dim].min + var minu = it.sizes.minMaxSizes[dim].start if dim == dtHorizontal: minu = max(it.child.state.xminwidth, minu) if minu > u: @@ -2137,7 +2135,7 @@ proc redistributeMainSize(mctx: var FlexMainContext; sizes: ResolvedSizes; diff += u - minu it.weights[wt] = 0 u = minu - let maxu = it.sizes.minMaxSizes[dim].max + let maxu = it.sizes.minMaxSizes[dim].send if maxu < u: # max violation if wt == fwtGrow: # freeze @@ -2168,7 +2166,7 @@ proc flushMain(mctx: var FlexMainContext; box: BlockBox; sizes: ResolvedSizes; it.child.state.offset[dim] += offset[dim] # margins are added here, since they belong to the flex item. it.child.state.offset[odim] += offset[odim] + - it.child.state.margin.dimStart(odim) + it.child.state.margin[odim].start offset[dim] += it.child.state.size[dim] totalMaxSize[dim] = max(totalMaxSize[dim], offset[dim]) mctx = FlexMainContext( @@ -2183,7 +2181,7 @@ proc layoutFlex(bctx: var BlockContext; box: BlockBox; sizes: ResolvedSizes) = var i = 0 var mctx = FlexMainContext(lctx: lctx) let flexDir = box.computed{"flex-direction"} - var totalMaxSize = size(w = 0, h = 0) #TODO find a better name for this + var totalMaxSize = size(w = 0, h = 0) let canWrap = box.computed{"flex-wrap"} != FlexWrapNowrap let percHeight = sizes.space.h.toPercSize() let dim = if flexDir in FlexRow: dtHorizontal else: dtVertical @@ -2974,6 +2972,7 @@ proc buildFlex(styledNode: StyledNode; lctx: LayoutContext; ctx.flushInherit() ctx.blockgroup.flush() assert box.inline == nil + const FlexReverse = {FlexDirectionRowReverse, FlexDirectionColumnReverse} if box.computed{"flex-direction"} in FlexReverse: box.nested.reverse() return box diff --git a/src/layout/renderdocument.nim b/src/layout/renderdocument.nim index 45bcb64f..1e987c41 100644 --- a/src/layout/renderdocument.nim +++ b/src/layout/renderdocument.nim @@ -383,7 +383,7 @@ proc renderInlineFragment(grid: var FlexibleGrid; state: var RenderState; proc renderRootInlineFragment(grid: var FlexibleGrid; state: var RenderState; root: RootInlineFragment; offset: Offset) = - grid.renderInlineFragment(state, root.fragment, root.offset + offset) + grid.renderInlineFragment(state, root.fragment, root.state.offset + offset) proc renderBlockBox(grid: var FlexibleGrid; state: var RenderState; box: BlockBox; offset: Offset) = @@ -392,7 +392,6 @@ proc renderBlockBox(grid: var FlexibleGrid; state: var RenderState; offset: Offset ]](100) stack.add((box, offset)) - while stack.len > 0: var (box, offset) = stack.pop() if box == nil: # positioned marker |