import math import options import unicode import layout/box import html/tags import html/dom import css/values import utils/twtstr import io/term # p is what to use for percentage values func cells_in(l: CSSLength, state: Viewport, d: int, p: Option[int], o: bool): int = return cells(l, d, state.term, p, o) func cells_w(l: CSSLength, state: Viewport, p: int): int = return l.cells_in(state, state.term.ppc, p.some, true) func cells_h(l: CSSLength, state: Viewport, p: Option[int]): int = return l.cells_in(state, state.term.ppl, p, false) func cells_h(l: CSSLength, state: Viewport, p: int): int = return l.cells_in(state, state.term.ppl, p.some, false) func px(l: CSSLength, state: Viewport, p = 0): int {.inline.} = return px(l, state.term, p) # Build phase type InlineState = object ictx: InlineContext skip: bool node: Node word: InlineWord maxwidth: int specified: CSSComputedValues func whitespacepre(specified: CSSComputedValues): 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.cellwidth func cellheight(viewport: Viewport): int {.inline.} = viewport.term.ppl func cellheight(ictx: InlineContext): int {.inline.} = ictx.viewport.cellheight # Whitespace between words func computeShift(ictx: InlineContext, specified: CSSComputedValues): int = if ictx.whitespacenum > 0: if ictx.thisrow.atoms.len > 0 or specified.whitespacepre: let spacing = specified{"word-spacing"} if spacing.auto: return ictx.cellwidth * ictx.whitespacenum #return spacing.cells_w(ictx.viewport, 0) return spacing.px(ictx.viewport) * ictx.whitespacenum return 0 func computeLineHeight(viewport: Viewport, specified: CSSComputedValues): 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() let format = ComputedFormat() let specified = state.specified format.color = specified{"color"} format.fontstyle = specified{"font-style"} format.fontweight = specified{"font-weight"} format.textdecoration = specified{"text-decoration"} format.node = state.node word.format = format state.ictx.format = format state.word = word proc horizontalAlignRow(ictx: InlineContext, row: InlineRow, specified: CSSComputedValues, maxwidth: int, last = false) = let maxwidth = if ictx.shrink: ictx.maxwidth else: maxwidth # we don't support directions for now so left = start and right = end case specified{"text-align"} of TEXT_ALIGN_START, TEXT_ALIGN_LEFT: discard of TEXT_ALIGN_END, TEXT_ALIGN_RIGHT: # move everything let x = max(maxwidth, row.width) - row.width for atom in row.atoms: atom.offset.x += x of TEXT_ALIGN_CENTER: let x = max((max(maxwidth - row.offset.x, row.width)) div 2 - row.width div 2, 0) for atom in row.atoms: atom.offset.x += x of TEXT_ALIGN_JUSTIFY: if not specified.whitespacepre and not last: var sumwidth = 0 var spaces = 0 for atom in row.atoms: if atom of InlineSpacing: discard else: inc spaces sumwidth += atom.width dec spaces if spaces > 0: let spacingwidth = (ictx.maxwidth - sumwidth) div spaces let oldwidth = row.width row.width = 0 for atom in row.atoms: atom.offset.x = row.width if atom of InlineSpacing: let atom = InlineSpacing(atom) atom.width = spacingwidth row.width += atom.width else: discard proc verticalAlignRow(ictx: InlineContext) = let row = ictx.thisrow var baseline = if row.height < row.lineheight: let lines = row.lineheight div ictx.cellheight int(ceil(lines / 2)) * ictx.cellheight else: 0 # line-height is the minimum line height row.height = max(row.height, row.lineheight) for atom in row.atoms: case atom.vertalign.keyword of VERTICAL_ALIGN_BASELINE: let len = atom.vertalign.length.px(ictx.viewport, row.lineheight) baseline = max(baseline, atom.height + len) of VERTICAL_ALIGN_TOP, VERTICAL_ALIGN_BOTTOM: row.height = max(atom.height, row.height) of VERTICAL_ALIGN_MIDDLE: baseline = max(baseline, atom.height div 2) else: baseline = max(baseline, atom.height) row.height = max(baseline, row.height) for atom in row.atoms: let diff = case atom.vertalign.keyword of VERTICAL_ALIGN_BASELINE: let len = atom.vertalign.length.px(ictx.viewport, row.lineheight) baseline - atom.height - len of VERTICAL_ALIGN_MIDDLE: baseline - atom.height div 2 of VERTICAL_ALIGN_TOP: 0 of VERTICAL_ALIGN_BOTTOM: row.height - atom.height else: baseline - atom.height atom.offset.y += diff proc addSpacing(row: InlineRow, width, height: int, format: ComputedFormat) {.inline.} = let spacing = InlineSpacing(width: width, height: height, format: format) spacing.offset.x = row.width row.width += spacing.width row.atoms.add(spacing) proc flushWhitespace(ictx: InlineContext, specified: CSSComputedValues) = let shift = ictx.computeShift(specified) ictx.whitespacenum = 0 if shift > 0: ictx.thisrow.addSpacing(shift, ictx.cellheight, ictx.format) proc finishRow(ictx: InlineContext, specified: CSSComputedValues, maxwidth: int, force = false) = if ictx.thisrow.atoms.len != 0 or force: ictx.flushWhitespace(specified) ictx.verticalAlignRow() let oldrow = ictx.thisrow ictx.rows.add(oldrow) ictx.height += oldrow.height ictx.maxwidth = max(ictx.maxwidth, oldrow.width) ictx.thisrow = InlineRow(offset: Offset(y: oldrow.offset.y + oldrow.height)) proc finish(ictx: InlineContext, specified: CSSComputedValues, maxwidth: int) = ictx.finishRow(specified, maxwidth) for row in ictx.rows: ictx.horizontalAlignRow(row, specified, maxwidth, row == ictx.rows[^1]) proc addAtom(ictx: InlineContext, atom: InlineAtom, maxwidth: int, specified: CSSComputedValues) = var shift = ictx.computeShift(specified) ictx.whitespacenum = 0 # Line wrapping if not specified.whitespacepre: if ictx.thisrow.width + atom.width + shift > maxwidth: ictx.finishRow(specified, maxwidth, false) # Recompute on newline shift = ictx.computeShift(specified) if atom.width > 0 and atom.height > 0: atom.vertalign = specified{"vertical-align"} if shift > 0: ictx.thisrow.addSpacing(shift, ictx.cellheight, ictx.format) atom.offset.x += 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) if atom of InlineWord: ictx.format = InlineWord(atom).format else: ictx.format = nil ictx.thisrow.atoms.add(atom) proc addWord(state: var InlineState) = if state.word.str != "": var word = state.word word.height = state.ictx.cellheight state.ictx.addAtom(word, state.maxwidth, state.specified) state.newWord() # Start a new line, even if the previous one is empty proc flushLine(ictx: InlineContext, specified: CSSComputedValues, maxwidth: int) = ictx.thisrow.lineheight = computeLineHeight(ictx.viewport, specified) ictx.finishRow(specified, maxwidth, true) proc checkWrap(state: var InlineState, r: Rune) = if state.specified{"white-space"} in {WHITESPACE_NOWRAP, WHITESPACE_PRE}: return let shift = state.ictx.computeShift(state.specified) case state.specified{"word-break"} 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.specified, state.maxwidth, false) state.ictx.whitespacenum = 0 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.specified, state.maxwidth, false) state.ictx.whitespacenum = 0 else: discard proc processWhitespace(state: var InlineState, c: char) = state.addWord() case state.specified{"white-space"} of WHITESPACE_NORMAL, WHITESPACE_NOWRAP: state.ictx.whitespacenum = max(state.ictx.whitespacenum, 1) of WHITESPACE_PRE_LINE, WHITESPACE_PRE, WHITESPACE_PRE_WRAP: if c == '\n': state.ictx.flushLine(state.specified, state.maxwidth) else: inc state.ictx.whitespacenum proc renderText*(ictx: InlineContext, str: string, maxwidth: int, specified: CSSComputedValues, node: Node) = var state: InlineState state.specified = specified state.ictx = ictx state.maxwidth = maxwidth state.node = node state.ictx.flushWhitespace(state.specified) state.newWord() #if str.strip().len > 0: #eprint "start", str.strip() var i = 0 while i < str.len: if str[i].isWhitespace(): state.processWhitespace(str[i]) inc i else: var r: Rune fastRuneAt(str, i, r) state.checkWrap(r) state.word.str &= r state.word.width += r.width() * state.ictx.cellwidth state.addWord() type PreferredDimensions = object compwidth: int compheight: Option[int] margin_left: int margin_right: int padding_left: int padding_right: int padding_top: int padding_bottom: int proc preferredDimensions(computed: CSSComputedValues, viewport: Viewport, width: int, height: Option[int]): PreferredDimensions = let pwidth = computed{"width"} if pwidth.auto: result.compwidth = width else: result.compwidth = pwidth.px(viewport, width) result.margin_left = computed{"margin-left"}.px(viewport, width) result.margin_right = computed{"margin-right"}.px(viewport, width) result.padding_top = computed{"padding-top"}.px(viewport, width) result.padding_bottom = computed{"padding-bottom"}.px(viewport, width) result.padding_left = computed{"padding-left"}.px(viewport, width) result.padding_right = computed{"padding-right"}.px(viewport, width) if result.compwidth >= width: result.compwidth -= result.margin_left result.compwidth -= result.margin_right result.compwidth -= result.padding_left result.compwidth -= result.padding_right let pheight = computed{"height"} if not pheight.auto: #bctx.compheight = pheight.cells_h(bctx.viewport, height).some if pheight.unit != UNIT_PERC: result.compheight = pheight.px(viewport).some elif height.issome: result.compheight = pheight.px(viewport, height.get).some proc setPreferredDimensions(bctx: BlockContext, width: int, height: Option[int]) = let preferred = preferredDimensions(bctx.specified, bctx.viewport, width, height) bctx.compwidth = preferred.compwidth bctx.compheight = preferred.compheight bctx.padding_top = preferred.padding_top bctx.padding_bottom = preferred.padding_bottom bctx.padding_left = preferred.padding_left bctx.padding_right = preferred.padding_right bctx.margin_left = preferred.margin_left bctx.margin_right = preferred.margin_right proc newBlockContext_common2(bctx: BlockContext, parent: BlockContext, box: BoxBuilder) {.inline.} = bctx.viewport = parent.viewport bctx.specified = box.specified bctx.setPreferredDimensions(parent.compwidth, parent.compheight) proc newBlockContext_common(parent: BlockContext, box: BoxBuilder): BlockContext {.inline.} = new(result) result.newBlockContext_common2(parent, box) proc newBlockContext(parent: BlockContext, box: BlockBoxBuilder): BlockContext = result = newBlockContext_common(parent, box) result.shrink = result.specified{"width"}.auto and parent.shrink proc newListItem(parent: BlockContext, builder: ListItemBoxBuilder): ListItem = new(result) result.newBlockContext_common2(parent, builder.content) result.shrink = result.specified{"width"}.auto and parent.shrink proc newInlineBlockContext(parent: BlockContext, builder: InlineBlockBoxBuilder): BlockContext = result = newBlockContext_common(parent, builder.content) result.shrink = result.specified{"width"}.auto proc newInlineBlock(parent: BlockContext, builder: InlineBlockBoxBuilder): InlineBlock = new(result) result.bctx = parent.newInlineBlockContext(builder) proc newBlockContext(viewport: Viewport, box: BlockBoxBuilder): BlockContext = new(result) result.viewport = viewport result.specified = box.specified result.setPreferredDimensions(viewport.term.width_px, none(int)) result.shrink = result.specified{"width"}.auto proc newInlineContext(bctx: BlockContext): InlineContext = new(result) result.thisrow = InlineRow() result.viewport = bctx.viewport result.shrink = bctx.shrink # Blocks' positions do not have to be positiond if buildBlocks is called with # children, whence the separate procedure. proc positionBlocks(bctx: BlockContext, selfcontained: bool) = var y = 0 var x = 0 var margin_todo = 0 y += bctx.padding_top bctx.height += bctx.padding_top x += bctx.padding_left if bctx.specified{"text-align"} == TEXT_ALIGN_MOZ_CENTER: x += bctx.compwidth div 2 template apply_child(child: BlockContext) = child.offset.y = y child.offset.x = x + child.margin_left if bctx.specified{"text-align"} == TEXT_ALIGN_MOZ_CENTER: child.offset.x -= child.width div 2 y += child.height bctx.height += child.height bctx.width = max(bctx.width, child.width) margin_todo = child.margin_bottom var i = 0 if i < bctx.nested.len: let child = bctx.nested[i] bctx.margin_top = child.margin_top let mtop = bctx.specified{"margin-top"}.px(bctx.viewport, bctx.compwidth) 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 while i < bctx.nested.len: let child = bctx.nested[i] if child.margin_top > margin_todo or child.margin_top < 0: margin_todo += child.margin_top - margin_todo y += margin_todo bctx.height += margin_todo apply_child(child) inc i bctx.margin_bottom = margin_todo let mbot = bctx.specified{"margin-bottom"}.px(bctx.viewport, bctx.compwidth) 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: bctx.height = bctx.compheight.get bctx.width += bctx.padding_left bctx.width += bctx.padding_right proc positionInlines(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.offset.x += bctx.padding_left if selfcontained: bctx.inline.offset.x += bctx.margin_top bctx.height += bctx.margin_top bctx.height += bctx.margin_bottom bctx.height += bctx.padding_top bctx.inline.offset.x += bctx.padding_top bctx.height += bctx.padding_bottom bctx.width += bctx.padding_right if bctx.specified{"width"}.auto: bctx.width = min(bctx.width, bctx.compwidth) else: bctx.width = bctx.compwidth proc buildBlock(box: BlockBoxBuilder, parent: BlockContext, selfcontained = false): BlockContext proc buildInlines(bctx: BlockContext, inlines: seq[BoxBuilder]): InlineContext proc buildBlocks(bctx: BlockContext, blocks: seq[BoxBuilder], node: Node) proc applyInlineDimensions(bctx: BlockContext) = bctx.height += bctx.inline.height if bctx.compheight.issome: bctx.height = bctx.compheight.get bctx.width = max(bctx.width, bctx.inline.maxwidth) proc buildInlineBlock(builder: InlineBlockBoxBuilder, parent: InlineContext, parentblock: BlockContext): InlineBlock = assert builder.content != nil result = parentblock.newInlineBlock(builder) let blockbuilder = BlockBoxBuilder(builder.content) if blockbuilder.inlinelayout: # Builder only contains inline boxes. result.bctx.inline = result.bctx.buildInlines(blockbuilder.children) result.bctx.applyInlineDimensions() result.bctx.positionInlines(false) else: # Builder only contains block boxes. result.bctx.buildBlocks(blockbuilder.children, blockbuilder.node) result.bctx.positionBlocks(false) let preferred = preferredDimensions(builder.specified, parentblock.viewport, parentblock.compwidth, parentblock.compheight) let pwidth = builder.specified{"width"} if pwidth.auto: # Half-baked shrink-to-fit result.bctx.width = min(max(result.bctx.width, parent.maxwidth), preferred.compwidth) else: result.bctx.width = preferred.compwidth # Set inline block dimensions, result.width = result.bctx.width result.height = result.bctx.height # Plus margins, for the final result. result.width += result.bctx.margin_left result.height += result.bctx.margin_top result.width += result.bctx.margin_right result.height += result.bctx.margin_bottom # Set offset here because positionInlines will reset it. result.bctx.offset.x = result.bctx.margin_left result.bctx.offset.y = result.bctx.margin_top proc buildInline(bctx: BlockContext, box: InlineBoxBuilder) = assert box.ictx != nil if box.newline: box.ictx.flushLine(bctx.specified, bctx.compwidth) let margin_left = box.specified{"margin-left"}.px(bctx.viewport, bctx.compwidth) box.ictx.thisrow.width += margin_left 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, box.ictx.cellheight, paddingformat) let originalRow = box.ictx.thisrow let originalWidth = box.ictx.thisrow.width for text in box.text: assert box.children.len == 0 box.ictx.renderText(text, bctx.compwidth, box.specified, box.node) for child in box.children: case child.specified{"display"} of DISPLAY_INLINE: let child = InlineBoxBuilder(child) child.ictx = box.ictx bctx.buildInline(child) of DISPLAY_INLINE_BLOCK: let child = InlineBlockBoxBuilder(child) let iblock = child.buildInlineBlock(box.ictx, bctx) box.ictx.addAtom(iblock, bctx.compwidth, child.specified) else: assert false, "child.t is " & $child.specified{"display"} let padding_right = box.specified{"padding-right"}.px(bctx.viewport, bctx.compwidth) if padding_right > 0: box.ictx.thisrow.addSpacing(padding_right, max(box.ictx.thisrow.height, 1), paddingformat) let margin_right = box.specified{"margin-right"}.px(bctx.viewport, bctx.compwidth) box.ictx.thisrow.width += margin_right proc buildInlines(bctx: BlockContext, inlines: seq[BoxBuilder]): InlineContext = let ictx = bctx.newInlineContext() if inlines.len > 0: for child in inlines: case child.specified{"display"} of DISPLAY_INLINE: let child = InlineBoxBuilder(child) child.ictx = ictx bctx.buildInline(child) of DISPLAY_INLINE_BLOCK: let child = InlineBlockBoxBuilder(child) let iblock = child.buildInlineBlock(ictx, bctx) ictx.addAtom(iblock, bctx.compwidth, child.specified) ictx.whitespacenum = 0 else: assert false, "child.t is " & $child.specified{"display"} ictx.finish(bctx.specified, bctx.compwidth) return ictx proc buildListItem(builder: ListItemBoxBuilder, parent: BlockContext, selfcontained = false): ListItem = result = parent.newListItem(builder) var tmp: seq[BoxBuilder] tmp.add(builder.marker) result.marker = result.buildInlines(tmp) if builder.content.inlinelayout: # Builder only contains inline boxes. result.inline = result.buildInlines(builder.content.children) result.applyInlineDimensions() result.positionInlines(selfcontained) else: # Builder only contains block boxes. result.buildBlocks(builder.content.children, builder.content.node) result.positionBlocks(selfcontained) proc buildBlocks(bctx: BlockContext, blocks: seq[BoxBuilder], node: Node) = for child in blocks: case child.specified{"display"} of DISPLAY_BLOCK: bctx.nested.add(buildBlock(BlockBoxBuilder(child), bctx)) of DISPLAY_LIST_ITEM: bctx.nested.add(buildListItem(ListItemBoxBuilder(child), bctx)) else: assert false, "child.t is " & $child.specified{"display"} # Build a block box inside another block box, based on a builder. proc buildBlock(box: BlockBoxBuilder, parent: BlockContext, selfcontained = false): BlockContext = assert parent != nil result = parent.newBlockContext(box) if box.inlinelayout: # Builder only contains inline boxes. result.inline = result.buildInlines(box.children) result.applyInlineDimensions() result.positionInlines(selfcontained) else: # Builder only contains block boxes. result.buildBlocks(box.children, box.node) result.positionBlocks(selfcontained) # Build a block box whose parent is the viewport, based on a builder. proc buildBlock(box: BlockBoxBuilder, viewport: Viewport, selfcontained = false): BlockContext = result = viewport.newBlockContext(box) if box.inlinelayout: # Builder only contains inline boxes. result.inline = result.buildInlines(box.children) result.applyInlineDimensions() result.positionInlines(selfcontained) else: # Builder only contains block boxes. result.buildBlocks(box.children, box.node) result.positionBlocks(selfcontained) # Generation phase #WARNING yes there is a {}= macro but that modifies the specified value # reference itself and those are copied across arrays... #TODO figure out something here proc setDisplay(computed: var CSSComputedValues, display: CSSDisplay) = computed[PROPERTY_DISPLAY] = CSSComputedValue(t: PROPERTY_DISPLAY, v: VALUE_DISPLAY, display: display) # Returns a block box, disregarding the specified value of display proc getBlockBox(specified: CSSComputedValues): BlockBoxBuilder = new(result) result.specified = specified.copyProperties() result.specified.setDisplay(DISPLAY_BLOCK) proc getInlineBlockBox(specified: CSSComputedValues): InlineBlockBoxBuilder = new(result) result.specified = specified.copyProperties() result.specified.setDisplay(DISPLAY_INLINE_BLOCK) proc getTextBox(box: BoxBuilder): InlineBoxBuilder = new(result) result.inlinelayout = true result.specified = box.specified.inheritProperties() proc getTextBox(specified: CSSComputedValues): InlineBoxBuilder = new(result) result.inlinelayout = true result.specified = specified.inheritProperties() proc getMarkerBox(elem: Element): MarkerBoxBuilder = new(result) result.inlinelayout = true result.specified = elem.css.inheritProperties() result.specified.setDisplay(DISPLAY_INLINE) if elem.tagType == TAG_LI: result.ordinalvalue = HTMLLIElement(elem).ordinalvalue else: result.ordinalvalue = 1 if elem.css{"list-style-position"} == LIST_STYLE_POSITION_INSIDE: result.inside = true result.text.add(elem.css{"list-style-type"}.listMarker(result.ordinalvalue)) proc getListItemBox(elem: Element): ListItemBoxBuilder = assert elem.css{"display"} == DISPLAY_LIST_ITEM new(result) result.specified = elem.css.copyProperties() result.marker = getMarkerBox(elem) func getInputBox(parent: BoxBuilder, input: HTMLInputElement, viewport: Viewport): InlineBoxBuilder = let textbox = parent.getTextBox() textbox.node = input textbox.text.add(input.inputString()) return textbox func getInputBox(specified: CSSComputedValues, input: HTMLInputElement, viewport: Viewport): InlineBoxBuilder = let textbox = specified.getTextBox() textbox.node = input textbox.text.add(input.inputString()) return textbox # Don't generate empty anonymous inline blocks between block boxes func canGenerateAnonymousInline(blockgroup: seq[BoxBuilder], specified: CSSComputedValues, text: Text): bool = return blockgroup.len > 0 and blockgroup[^1].specified{"display"} == DISPLAY_INLINE or specified{"white-space"} in {WHITESPACE_PRE_LINE, WHITESPACE_PRE, WHITESPACE_PRE_WRAP} or not text.data.onlyWhitespace() proc generateBlockBox(elem: Element, viewport: Viewport): BlockBoxBuilder template flush_block_group3(specified: CSSComputedValues) = if blockgroup.len > 0: let bbox = getBlockBox(specified.inheritProperties()) bbox.inlinelayout = true bbox.children = blockgroup box.children.add(bbox) blockgroup.setLen(0) template flush_ibox() = if ibox != nil: assert ibox.specified{"display"} in {DISPLAY_INLINE, DISPLAY_INLINE_BLOCK} blockgroup.add(ibox) ibox = nil proc generateInlineBoxes(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[BoxBuilder], viewport: Viewport) proc generateFromElem(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[BoxBuilder], viewport: Viewport, ibox: var InlineBoxBuilder) = if elem.tagType == TAG_BR: ibox = box.getTextBox() ibox.newline = true flush_ibox case elem.css{"display"} of DISPLAY_BLOCK: flush_block_group3(elem.css) let childbox = elem.generateBlockBox(viewport) box.children.add(childbox) of DISPLAY_LIST_ITEM: flush_block_group3(elem.css) let childbox = getListItemBox(elem) childbox.content = elem.generateBlockBox(viewport) box.children.add(childbox) of DISPLAY_INLINE: flush_ibox box.generateInlineBoxes(elem, blockgroup, viewport) of DISPLAY_INLINE_BLOCK: flush_ibox let childbox = getInlineBlockBox(box.specified) childbox.content = elem.generateBlockBox(viewport) blockgroup.add(childbox) else: discard #TODO proc generateInlinePseudoBox(box: BlockBoxBuilder, specified: CSSComputedValues, blockgroup: var seq[BoxBuilder], viewport: Viewport) = var ibox: InlineBoxBuilder = nil if specified{"content"}.len > 0: ibox = getTextBox(specified) ibox.text.add($specified{"content"}) flush_ibox proc generateBlockPseudoBox(specified: CSSComputedValues, viewport: Viewport): BlockBoxBuilder = let box = getBlockBox(specified) var blockgroup: seq[BoxBuilder] var ibox: InlineBoxBuilder = nil if specified{"content"}.len > 0: ibox = getTextBox(specified) ibox.text.add($specified{"content"}) flush_ibox flush_block_group3(specified) return box proc generatePseudo(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[BoxBuilder], viewport: Viewport, ibox: var InlineBoxBuilder, specified: CSSComputedValues) = case specified{"display"} of DISPLAY_BLOCK: flush_block_group3(elem.css) let childbox = generateBlockPseudoBox(specified, viewport) box.children.add(childbox) of DISPLAY_LIST_ITEM: flush_block_group3(elem.css) let childbox = getListItemBox(elem) childbox.content = elem.generateBlockBox(viewport) box.children.add(childbox) of DISPLAY_INLINE: flush_ibox box.generateInlinePseudoBox(specified, blockgroup, viewport) of DISPLAY_INLINE_BLOCK: flush_ibox let childbox = getInlineBlockBox(box.specified) childbox.content = generateBlockPseudoBox(specified, viewport) blockgroup.add(childbox) else: discard #TODO proc generateBoxBefore(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[BoxBuilder], viewport: Viewport, ibox: var InlineBoxBuilder) = if elem.pseudo[PSEUDO_BEFORE] != nil: box.generatePseudo(elem, blockgroup, viewport, ibox, elem.pseudo[PSEUDO_BEFORE]) if elem.tagType == TAG_INPUT: flush_ibox let input = HTMLInputElement(elem) ibox = box.getInputBox(input, viewport) proc generateBoxAfter(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[BoxBuilder], viewport: Viewport, ibox: var InlineBoxBuilder) = if elem.pseudo[PSEUDO_AFTER] != nil: box.generatePseudo(elem, blockgroup, viewport, ibox, elem.pseudo[PSEUDO_AFTER]) proc generateInlineBoxes(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[BoxBuilder], viewport: Viewport) = var ibox: InlineBoxBuilder = nil generateBoxBefore(box, elem, blockgroup, viewport, ibox) for child in elem.childNodes: case child.nodeType of ELEMENT_NODE: let child = Element(child) box.generateFromElem(child, blockgroup, viewport, ibox) of TEXT_NODE: let child = Text(child) if ibox == nil: ibox = getTextBox(elem.css) ibox.node = elem ibox.text.add(child.data) else: discard generateBoxAfter(box, elem, blockgroup, viewport, ibox) flush_ibox proc generateBlockBox(elem: Element, viewport: Viewport): BlockBoxBuilder = let box = getBlockBox(elem.css) var blockgroup: seq[BoxBuilder] var ibox: InlineBoxBuilder = nil generateBoxBefore(box, elem, blockgroup, viewport, ibox) for child in elem.childNodes: case child.nodeType of ELEMENT_NODE: flush_ibox let child = Element(child) box.generateFromElem(child, blockgroup, viewport, ibox) of TEXT_NODE: let child = Text(child) if canGenerateAnonymousInline(blockgroup, box.specified, child): if ibox == nil: ibox = getTextBox(elem.css) ibox.node = elem ibox.text.add(child.data) else: discard generateBoxAfter(box, elem, blockgroup, viewport, ibox) flush_ibox if blockgroup.len > 0: # Avoid unnecessary anonymous block boxes if box.children.len == 0: box.children = blockgroup box.inlinelayout = true else: flush_block_group3(elem.css) return box proc generateBoxBuilders(elem: Element, viewport: Viewport): BlockBoxBuilder = return generateBlockBox(elem, viewport) proc renderLayout*(viewport: var Viewport, document: Document) = viewport.root = document.root.generateBoxBuilders(viewport) viewport.root.bctx = buildBlock(viewport.root, viewport)