diff options
author | bptato <nincsnevem662@gmail.com> | 2022-01-28 18:58:45 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2022-01-28 18:58:45 +0100 |
commit | a42f0b169f5f612b3b14026618bcb26a2afeedca (patch) | |
tree | c5cf1ee21ad32444ef74b4e06ed5c4bd16c09abc /src/layout/engine.nim | |
parent | 876f5e9c64992ffef8630cd2bafa8c5ba9431f01 (diff) | |
download | chawan-a42f0b169f5f612b3b14026618bcb26a2afeedca.tar.gz |
Implement line-height, vertical-align
Diffstat (limited to 'src/layout/engine.nim')
-rw-r--r-- | src/layout/engine.nim | 163 |
1 files changed, 114 insertions, 49 deletions
diff --git a/src/layout/engine.nim b/src/layout/engine.nim index b5876b5c..986d6723 100644 --- a/src/layout/engine.nim +++ b/src/layout/engine.nim @@ -1,5 +1,6 @@ -import unicode +import math import options +import unicode import layout/box import html/tags @@ -34,11 +35,17 @@ type InlineState = object func whitespacepre(specified: CSSSpecifiedValues): bool {.inline.} = specified{"white-space"} in {WHITESPACE_PRE, WHITESPACE_PRE_WRAP} +func cellwidth(viewport: Viewport): int {.inline.} = + viewport.term.ppc + func cellwidth(ictx: InlineContext): int {.inline.} = - ictx.viewport.term.ppc + ictx.viewport.cellwidth + +func cellheight(viewport: Viewport): int {.inline.} = + viewport.term.ppl func cellheight(ictx: InlineContext): int {.inline.} = - ictx.viewport.term.ppl + ictx.viewport.cellheight # Whitespace between words func computeShift(ictx: InlineContext, specified: CSSSpecifiedValues): int = @@ -51,6 +58,11 @@ func computeShift(ictx: InlineContext, specified: CSSSpecifiedValues): int = return spacing.px(ictx.viewport) return 0 +func computeLineHeight(viewport: Viewport, specified: CSSSpecifiedValues): int = + if specified{"line-height"}.auto: + return viewport.cellheight + return specified{"line-height"}.px(viewport, viewport.cellheight) + proc newWord(state: var InlineState) = let word = InlineWord() word.format = ComputedFormat() @@ -62,9 +74,47 @@ proc newWord(state: var InlineState) = word.format.node = state.node state.word = word -proc finishRow(ictx: InlineContext) = - if ictx.thisrow.height != 0: +proc finishRow(ictx: InlineContext, specified: CSSSpecifiedValues, force = false) = + if ictx.thisrow.atoms.len != 0 or force: let oldrow = ictx.thisrow + + var baseline = if oldrow.height < oldrow.lineheight: + let lines = oldrow.lineheight div ictx.cellheight + ceilDiv(lines, 2) * ictx.cellheight + else: + 0 + + # line-height is the minimum line height + oldrow.height = max(oldrow.height, oldrow.lineheight) + + for atom in oldrow.atoms: + case atom.vertalign.keyword + of VERTICAL_ALIGN_BASELINE: + let len = atom.vertalign.length.px(ictx.viewport, oldrow.lineheight) + baseline = max(baseline, atom.height + len) + of VERTICAL_ALIGN_TOP, VERTICAL_ALIGN_BOTTOM: + oldrow.height = max(atom.height, oldrow.height) + of VERTICAL_ALIGN_MIDDLE: + baseline = max(baseline, atom.height div 2) + else: + baseline = max(baseline, atom.height) + oldrow.height = max(baseline, oldrow.height) + + for atom in oldrow.atoms: + let diff = case atom.vertalign.keyword + of VERTICAL_ALIGN_BASELINE: + let len = atom.vertalign.length.px(ictx.viewport, oldrow.lineheight) + baseline - atom.height - len + of VERTICAL_ALIGN_MIDDLE: + baseline - atom.height div 2 + of VERTICAL_ALIGN_TOP: + 0 + of VERTICAL_ALIGN_BOTTOM: + oldrow.height - atom.height + else: + baseline - atom.height + atom.rely += diff + ictx.rows.add(oldrow) ictx.height += oldrow.height ictx.width = max(ictx.width, oldrow.width) @@ -82,20 +132,23 @@ proc addAtom(ictx: InlineContext, atom: InlineAtom, maxwidth: int, specified: CS # Line wrapping if specified{"white-space"} notin {WHITESPACE_NOWRAP, WHITESPACE_PRE}: if ictx.thisrow.width + atom.width + shift > maxwidth: - ictx.finishRow() + ictx.finishRow(specified) # Recompute on newline shift = ictx.computeShift(specified) ictx.whitespace = false if atom.width > 0 and atom.height > 0: + atom.vertalign = specified{"vertical-align"} + if shift > 0: let format = if atom of InlineWord: InlineWord(atom).format else: nil - ictx.thisrow.addSpacing(shift, atom.height, format) + ictx.thisrow.addSpacing(shift, ictx.cellheight, format) atom.relx += ictx.thisrow.width + ictx.thisrow.lineheight = max(ictx.thisrow.lineheight, computeLineHeight(ictx.viewport, specified)) ictx.thisrow.width += atom.width ictx.thisrow.height = max(ictx.thisrow.height, atom.height) ictx.thisrow.atoms.add(atom) @@ -108,9 +161,9 @@ proc addWord(state: var InlineState) = state.newWord() # Start a new line, even if the previous one is empty -proc flushLine(ictx: InlineContext) = - ictx.thisrow.height = max(ictx.thisrow.height, ictx.cellheight) - ictx.finishRow() +proc flushLine(ictx: InlineContext, specified: CSSSpecifiedValues) = + ictx.thisrow.lineheight = computeLineHeight(ictx.viewport, specified) + ictx.finishRow(specified, true) proc checkWrap(state: var InlineState, r: Rune) = if state.specified{"white-space"} in {WHITESPACE_NOWRAP, WHITESPACE_PRE}: @@ -120,11 +173,11 @@ proc checkWrap(state: var InlineState, r: Rune) = of WORD_BREAK_BREAK_ALL: if state.ictx.thisrow.width + state.word.width + shift + r.width() * state.ictx.cellwidth > state.maxwidth: state.addWord() - state.ictx.finishRow() + state.ictx.finishRow(state.specified) state.ictx.whitespace = false of WORD_BREAK_KEEP_ALL: if state.ictx.thisrow.width + state.word.width + shift + r.width() * state.ictx.cellwidth > state.maxwidth: - state.ictx.finishRow() + state.ictx.finishRow(state.specified) state.ictx.whitespace = false else: discard @@ -135,7 +188,7 @@ proc processWhitespace(state: var InlineState, c: char) = state.ictx.whitespace = true of WHITESPACE_PRE_LINE, WHITESPACE_PRE, WHITESPACE_PRE_WRAP: if c == '\n': - state.ictx.flushLine() + state.ictx.flushLine(state.specified) else: state.ictx.whitespace = true @@ -163,9 +216,6 @@ proc renderText*(ictx: InlineContext, str: string, maxwidth: int, specified: CSS state.addWord() -proc finish(ictx: InlineContext) = - ictx.finishRow() - proc computedDimensions(bctx: BlockContext, width: int, height: Option[int]) = let pwidth = bctx.specified{"width"} if pwidth.auto: @@ -207,7 +257,7 @@ proc newBlockContext(parent: BlockContext, box: BlockBox): BlockContext = result = newBlockContext_common(parent, box) proc newInlineBlockContext(parent: BlockContext, box: InlineBlockBox): BlockContext = - newBlockContext_common(parent, box) + result = newBlockContext_common(parent, box) # Anonymous block box. proc newBlockContext(parent: BlockContext): BlockContext = @@ -231,7 +281,7 @@ proc newInlineContext(bctx: BlockContext): InlineContext = # Blocks' positions do not have to be arranged if alignBlocks is called with # children, whence the separate procedure. -proc arrangeBlocks(bctx: BlockContext) = +proc arrangeBlocks(bctx: BlockContext, selfcontained: bool) = var y = 0 var x = 0 var margin_todo = 0 @@ -258,6 +308,11 @@ proc arrangeBlocks(bctx: BlockContext) = if mtop > bctx.margin_top or mtop < 0: bctx.margin_top = mtop - bctx.margin_top + if selfcontained: + margin_todo = bctx.margin_top + bctx.height += margin_todo + y += margin_todo + apply_child(child) inc i @@ -277,6 +332,9 @@ proc arrangeBlocks(bctx: BlockContext) = if mbot > bctx.margin_bottom or mbot < 0: bctx.margin_bottom = mbot - bctx.margin_bottom + if selfcontained: + bctx.height += bctx.margin_bottom + bctx.height += bctx.padding_bottom if bctx.compheight.issome: @@ -285,10 +343,18 @@ proc arrangeBlocks(bctx: BlockContext) = bctx.width += bctx.padding_left bctx.width += bctx.padding_right -proc arrangeInlines(bctx: BlockContext) = +proc arrangeInlines(bctx: BlockContext, selfcontained: bool) = + bctx.margin_top = bctx.specified{"margin-top"}.px(bctx.viewport, bctx.compwidth) + bctx.margin_bottom = bctx.specified{"margin-bottom"}.px(bctx.viewport, bctx.compwidth) + bctx.width += bctx.padding_left bctx.inline.relx += bctx.padding_left + if selfcontained: + bctx.inline.rely += bctx.margin_top + bctx.height += bctx.margin_top + bctx.height += bctx.margin_bottom + bctx.height += bctx.padding_top bctx.inline.rely += bctx.padding_top @@ -298,28 +364,24 @@ proc arrangeInlines(bctx: BlockContext) = bctx.width = min(bctx.width, bctx.compwidth) -proc alignBlock(box: BlockBox) +proc alignBlock(box: BlockBox, selfcontained = false) -proc alignInlineBlock(bctx: BlockContext, box: InlineBlockBox, parentcss: CSSSpecifiedValues) = +proc alignInlineBlock(bctx: BlockContext, box: InlineBlockBox) = if box.bctx.done: return - alignBlock(box) - - box.bctx.rely += box.bctx.margin_top - box.bctx.height += box.bctx.margin_top - box.bctx.height += box.bctx.margin_bottom + alignBlock(box, true) box.bctx.relx += box.bctx.margin_left box.bctx.width += box.bctx.margin_left box.bctx.width += box.bctx.margin_right - box.ictx.addAtom(box.bctx, bctx.compwidth, parentcss) + box.ictx.addAtom(box.bctx, bctx.compwidth, box.specified) box.ictx.whitespace = false proc alignInline(bctx: BlockContext, box: InlineBox) = assert box.ictx != nil if box.newline: - box.ictx.flushLine() + box.ictx.flushLine(bctx.specified) let margin_left = box.specified{"margin-left"}.px(bctx.viewport, bctx.compwidth) box.ictx.thisrow.width += margin_left @@ -327,7 +389,7 @@ proc alignInline(bctx: BlockContext, box: InlineBox) = let paddingformat = ComputedFormat(node: box.node) let padding_left = box.specified{"padding-left"}.px(bctx.viewport, bctx.compwidth) if padding_left > 0: - box.ictx.thisrow.addSpacing(padding_left, max(box.ictx.thisrow.height, 1), paddingformat) + box.ictx.thisrow.addSpacing(padding_left, box.ictx.cellheight, paddingformat) for text in box.text: assert box.children.len == 0 @@ -342,7 +404,7 @@ proc alignInline(bctx: BlockContext, box: InlineBox) = of DISPLAY_INLINE_BLOCK: let child = InlineBlockBox(child) child.ictx = box.ictx - bctx.alignInlineBlock(child, box.specified) + bctx.alignInlineBlock(child) else: assert false, "child.t is " & $child.t @@ -355,25 +417,25 @@ proc alignInline(bctx: BlockContext, box: InlineBox) = proc alignInlines(bctx: BlockContext, inlines: seq[CSSBox]) = let ictx = bctx.newInlineContext() - for child in inlines: - case child.t - of DISPLAY_INLINE: - let child = InlineBox(child) - child.ictx = ictx - bctx.alignInline(child) - of DISPLAY_INLINE_BLOCK: - let child = InlineBlockBox(child) - child.ictx = ictx - bctx.alignInlineBlock(child, bctx.specified) - else: - assert false, "child.t is " & $child.t - ictx.finish() + if inlines.len > 0: + for child in inlines: + case child.t + of DISPLAY_INLINE: + let child = InlineBox(child) + child.ictx = ictx + bctx.alignInline(child) + of DISPLAY_INLINE_BLOCK: + let child = InlineBlockBox(child) + child.ictx = ictx + bctx.alignInlineBlock(child) + else: + assert false, "child.t is " & $child.t + ictx.finishRow(bctx.specified) + bctx.height += ictx.height if bctx.compheight.issome: bctx.height = bctx.compheight.get bctx.width = max(bctx.width, ictx.width) - bctx.margin_top = bctx.specified{"margin-top"}.px(bctx.viewport, bctx.compwidth) - bctx.margin_bottom = bctx.specified{"margin-bottom"}.px(bctx.viewport, bctx.compwidth) template flush_group() = if blockgroup.len > 0: @@ -402,19 +464,19 @@ proc alignBlocks(bctx: BlockContext, blocks: seq[CSSBox], blockgroup: var seq[CS blockgroup.add(child) else: discard #TODO -proc alignBlock(box: BlockBox) = +proc alignBlock(box: BlockBox, selfcontained = false) = if box.bctx.done: return if box.inlinelayout: # Box only contains inline boxes. box.bctx.alignInlines(box.children) - box.bctx.arrangeInlines() + box.bctx.arrangeInlines(selfcontained) else: var blockgroup: seq[CSSBox] box.bctx.alignBlocks(box.children, blockgroup, box.node) let bctx = box.bctx flush_group() - box.bctx.arrangeBlocks() + box.bctx.arrangeBlocks(selfcontained) box.bctx.done = true proc getBox(specified: CSSSpecifiedValues): CSSBox = @@ -443,7 +505,10 @@ proc getTextBox(box: CSSBox): InlineBox = new(result) result.t = DISPLAY_INLINE result.inlinelayout = true - result.specified = box.specified.inheritProperties() + if box.specified{"display"} == DISPLAY_INLINE: + result.specified = box.specified + else: + result.specified = box.specified.inheritProperties() proc getPseudoBox(bctx: BlockContext, specified: CSSSpecifiedValues): CSSBox = let box = getBox(specified) |