diff options
author | bptato <nincsnevem662@gmail.com> | 2025-02-12 21:15:38 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2025-02-12 23:27:34 +0100 |
commit | 8f3c96035f22a16459ad7c8996c280a015f0d2f5 (patch) | |
tree | 183424ee7b20eb53b313d0ff76885e7e3acdc0d5 /src | |
parent | 76e2b68af52bf51854388890172bc0b2f2aaf36b (diff) | |
download | chawan-8f3c96035f22a16459ad7c8996c280a015f0d2f5.tar.gz |
layout: separate out tree construction logic
For now, the skeleton remains in layout. Eventually it should be lazily constructed during the actual layout pass (thereby making layout "single-pass" (sometimes :p)) The end goal is to do it all in styledNode.children, so that caching can be as simple as "next box = find next matching cached box ?? make new". This does mean that the internal structure of cached boxes will always have to be reconstructed, but I don't see a better way. (I suppose it still remains possible to optimize out the unnecessary layout pass when only a repaint is needed (e.g. color change) by modifying computed values in-place.)
Diffstat (limited to 'src')
-rw-r--r-- | src/css/cascade.nim | 167 | ||||
-rw-r--r-- | src/css/csstree.nim | 484 | ||||
-rw-r--r-- | src/css/cssvalues.nim | 39 | ||||
-rw-r--r-- | src/css/layout.nim | 492 | ||||
-rw-r--r-- | src/server/buffer.nim | 1 |
5 files changed, 564 insertions, 619 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim index ef4f1376..49fc6647 100644 --- a/src/css/cascade.nim +++ b/src/css/cascade.nim @@ -14,7 +14,6 @@ import html/catom import html/dom import html/enums import html/script -import types/bitmap import types/color import types/jscolor import types/opt @@ -387,169 +386,3 @@ proc applyStyle*(element: Element) = if map[pseudo].hasValues() or window.settings.scripting == smApp: let computed = map[pseudo].applyDeclarations(element, nil, window) element.computedMap[pseudo] = computed - -# Abstraction over the DOM to pretend that elements, text, replaced and -# pseudo-elements are derived from the same type. -type - StyledType* = enum - stElement, stText, stReplacement - - StyledNode* = object - element*: Element - pseudo*: PseudoElement - computed*: CSSValues - case t*: StyledType - of stText: - text*: CharacterData - of stElement: - anonChildren: seq[StyledNode] - of stReplacement: - # replaced elements: quotes, images, or (TODO) markers - content*: CSSContent - -when defined(debug): - func `$`*(node: StyledNode): string = - case node.t - of stText: - return node.text.data - of stElement: - if node.pseudo != peNone: - return $node.element.tagType & "::" & $node.pseudo - return $node.element - of stReplacement: - return "#replacement" - -proc initStyledElement*(element: Element): StyledNode = - if element.computed == nil: - element.applyStyle() - return StyledNode(t: stElement, element: element, computed: element.computed) - -proc initStyledReplacement(parent: Element; content: sink CSSContent; - computed: CSSValues): StyledNode = - return StyledNode( - t: stReplacement, - element: parent, - content: content, - computed: computed - ) - -proc initStyledImage(parent: Element; bmp: NetworkBitmap): StyledNode = - return initStyledReplacement( - parent, - CSSContent(t: ContentImage, bmp: bmp), - parent.computed - ) - -proc initStyledPseudo(parent: Element; pseudo: PseudoElement): StyledNode = - return StyledNode( - t: stElement, - pseudo: pseudo, - element: parent, - computed: parent.computedMap[pseudo] - ) - -proc initStyledText(parent: Element; text: CharacterData): StyledNode = - return StyledNode(t: stText, element: parent, text: text) - -proc initStyledText(parent: Element; s: sink string): StyledNode = - #TODO should probably cache these... - return initStyledText(parent, newCharacterData(s)) - -proc initStyledAnon(parent: Element; children: sink seq[StyledNode]; - computed: CSSValues): StyledNode = - return StyledNode( - t: stElement, - element: parent, - anonChildren: children, - computed: computed - ) - -iterator optionChildren(styledNode: StyledNode): StyledNode {.inline.} = - let option = HTMLOptionElement(styledNode.element) - if option.select != nil and option.select.attrb(satMultiple): - yield initStyledText(option, "[") - let cdata = newCharacterData(if option.selected: "*" else: " ") - let computed = option.computed.inheritProperties() - computed{"color"} = cssColor(ANSIColor(1)) # red - computed{"white-space"} = WhitespacePre - yield initStyledAnon(option, @[initStyledText(option, cdata)], computed) - yield initStyledText(option, "]") - for it in option.childList: - if it of Element: - yield initStyledElement(Element(it)) - elif it of Text: - yield initStyledText(option, Text(it)) - -# Many yields; we use a closure iterator to avoid bloating the code. -iterator children*(styledNode: StyledNode): StyledNode {.closure.} = - if styledNode.t != stElement: - return - if styledNode.pseudo == peNone: - let parent = styledNode.element - if styledNode.anonChildren.len > 0: - for it in styledNode.anonChildren: - yield it - else: - if parent.computedMap[peBefore] != nil and - parent.computedMap[peBefore]{"content"}.len > 0: - yield initStyledPseudo(parent, peBefore) - case parent.tagType - of TAG_INPUT: - let cdata = HTMLInputElement(parent).inputString() - if cdata != nil and cdata.data.len != 0: - yield initStyledText(parent, cdata) - of TAG_TEXTAREA: - #TODO cache (do the same as with input, and add borders in render) - let cdata = HTMLTextAreaElement(parent).textAreaString() - yield initStyledText(parent, cdata) - of TAG_IMG: yield initStyledImage(parent, HTMLImageElement(parent).bitmap) - of TAG_CANVAS: - yield initStyledImage(parent, HTMLCanvasElement(parent).bitmap) - of TAG_VIDEO: yield initStyledText(parent, "[video]") - of TAG_AUDIO: yield initStyledText(parent, "[audio]") - of TAG_BR: - yield initStyledReplacement( - parent, - CSSContent(t: ContentNewline), - parent.computed - ) - of TAG_IFRAME: yield initStyledText(parent, "[iframe]") - of TAG_FRAME: yield initStyledText(parent, "[frame]") - of TAG_OPTION: - for it in styledNode.optionChildren: - yield it - elif parent.tagType(Namespace.SVG) == TAG_SVG: - yield initStyledImage(parent, SVGSVGElement(parent).bitmap) - else: - for it in parent.childList: - if it of Element: - yield initStyledElement(Element(it)) - elif it of Text: - yield initStyledText(parent, Text(it)) - if parent.computedMap[peAfter] != nil and - parent.computedMap[peAfter]{"content"}.len > 0: - yield initStyledPseudo(parent, peAfter) - else: - let parent = styledNode.element - let computed = parent.computedMap[styledNode.pseudo].inheritProperties() - for content in parent.computedMap[styledNode.pseudo]{"content"}: - yield parent.initStyledReplacement(content, computed) - -when defined(debug): - proc computedTree*(styledNode: StyledNode): string = - result = "" - if styledNode.t != stElement: - result &= $styledNode - else: - result &= "<" - if styledNode.computed{"display"} != DisplayInline: - result &= "div" - else: - result &= "span" - let computed = styledNode.computed.copyProperties() - if computed{"display"} == DisplayBlock: - computed{"display"} = DisplayInline - result &= " style='" & $computed.serializeEmpty() & "'>\n" - for it in styledNode.children: - result &= it.computedTree() - result &= "\n</div>" diff --git a/src/css/csstree.nim b/src/css/csstree.nim new file mode 100644 index 00000000..d4702cea --- /dev/null +++ b/src/css/csstree.nim @@ -0,0 +1,484 @@ +# Tree building. +# +# This wouldn't be nearly as complex as it is if not for CSS's asinine +# anonymous table box generation rules. In particular: +# * Runs of misparented boxes inside a table/table row/table row group +# must be placed in appropriate anonymous wrappers. For example, if +# we encounter two consecutive `display: block's inside a `display: +# table-row', these must be wrapped around a single `display: +# table-cell'. +# * Runs of misparented table row/table row group/table cell boxes must +# be wrapped in an anonymous table, or in some cases an anonymous +# table row and *then* an anonymous table. e.g. a `display: +# table-row', `display: table-cell', then a `display: table-row-group' +# will all be wrapped in a single table. +# * If this weren't enough, we also have to *split up* the entire table +# into an inner and an outer table. The outer table wraps the inner +# table and the caption. The inner table (of DisplayTableWrapper) +# includes the rows/row groups. +# Whatever your reason may be for looking at this: good luck. + +import chame/tags +import css/cascade +import css/cssvalues +import css/selectorparser +import html/catom +import html/dom +import types/bitmap +import types/color +import utils/twtstr + +type + StyledType* = enum + stElement, stText, stImage, stBr + + # Abstraction over the DOM to pretend that elements, text, replaced + # and pseudo-elements are derived from the same type. + StyledNode* = object + element*: Element + computed*: CSSValues + pseudo*: PseudoElement + skipChildren: bool + case t*: StyledType + of stText: + text*: CharacterData + of stElement: + anonChildren: seq[StyledNode] + of stImage: + bmp*: NetworkBitmap + of stBr: # <br> element + discard + + TreeContext* = object + quoteLevel: int + listItemCounter: int + + TreeFrame = object + parent: Element + computed: CSSValues + children: seq[StyledNode] + lastChildWasInline: bool + captionSeen: bool + anonTableDisplay: CSSDisplay + anonComputed: CSSValues + anonInlineComputed: CSSValues + pctx: ptr TreeContext + +template ctx(frame: TreeFrame): var TreeContext = + frame.pctx[] + +when defined(debug): + func `$`*(node: StyledNode): string = + case node.t + of stText: + return node.text.data + of stElement: + if node.pseudo != peNone: + return $node.element.tagType & "::" & $node.pseudo + return $node.element + of stImage: + return "#image" + of stBr: + return "#br" + +# Root +proc initStyledElement*(element: Element): StyledNode = + if element.computed == nil: + element.applyStyle() + result = StyledNode( + t: stElement, + element: element, + computed: element.computed + ) + +func inheritFor(frame: TreeFrame; display: CSSDisplay): CSSValues = + result = frame.computed.inheritProperties() + result{"display"} = display + +proc initTreeFrame(ctx: var TreeContext; parent: Element; computed: CSSValues): + TreeFrame = + result = TreeFrame(parent: parent, computed: computed, pctx: addr ctx) + +proc getAnonInlineComputed(frame: var TreeFrame): CSSValues = + if frame.anonInlineComputed == nil: + if frame.computed{"display"} == DisplayInline: + frame.anonInlineComputed = frame.computed + else: + frame.anonInlineComputed = frame.computed.inheritProperties() + return frame.anonInlineComputed + +proc displayed(frame: TreeFrame; text: CharacterData): bool = + if text.data.len == 0: + return false + return frame.computed{"display"} == DisplayInline or + frame.lastChildWasInline or + frame.computed{"white-space"} in WhiteSpacePreserve or + not text.data.onlyWhitespace() + +#TODO implement table columns +const DisplayNoneLike = { + DisplayNone, DisplayTableColumn, DisplayTableColumnGroup +} + +proc displayed(frame: TreeFrame; pseudo: PseudoElement): bool = + return frame.parent.computedMap[pseudo] != nil and + frame.parent.computedMap[pseudo]{"content"}.len > 0 and + frame.parent.computedMap[pseudo]{"display"} notin DisplayNoneLike + +proc displayed(frame: TreeFrame; element: Element): bool = + return element.computed{"display"} notin DisplayNoneLike + +proc getInternalTableParent(frame: var TreeFrame; display: CSSDisplay): + var seq[StyledNode] = + if frame.anonTableDisplay != display: + if frame.anonComputed == nil: + frame.anonComputed = frame.inheritFor(display) + frame.anonTableDisplay = display + frame.children.add(StyledNode( + t: stElement, + element: frame.parent, + computed: frame.anonComputed, + skipChildren: true + )) + return frame.children[^1].anonChildren + +# Add an anonymous table to children, and return based on display either +# * row, row group: the table children +# * cell: its last anonymous row (if there isn't one, create it) +# * caption: its outer box +proc addAnonTable(frame: var TreeFrame; parentDisplay, display: CSSDisplay): + var seq[StyledNode] = + if frame.anonComputed == nil or + frame.anonComputed{"display"} notin DisplayInnerTable + {DisplayTableRow}: + let anonDisplay = if parentDisplay == DisplayInline: + DisplayInlineTable + else: + DisplayTable + let (outer, inner) = frame.inheritFor(anonDisplay).splitTable() + frame.anonComputed = outer + frame.children.add(StyledNode( + t: stElement, + computed: outer, + skipChildren: true, + anonChildren: @[StyledNode( + t: stElement, + computed: inner, + skipChildren: true + )] + )) + if display == DisplayTableCaption: + frame.anonComputed = frame.children[^1].computed + return frame.children[^1].anonChildren + if display in RowGroupBox + {DisplayTableRow}: + frame.anonComputed = frame.children[^1].computed + return frame.children[^1].anonChildren[0].anonChildren + assert display == DisplayTableCell + if frame.anonComputed{"display"} == DisplayTableRow: + return frame.children[^1].anonChildren[0].anonChildren[^1].anonChildren + frame.anonComputed = frame.inheritFor(DisplayTableRow) + frame.children[^1].anonChildren[0].anonChildren.add(StyledNode( + t: stElement, + element: frame.parent, + computed: frame.anonComputed, + skipChildren: true + )) + return frame.children[^1].anonChildren[0].anonChildren[^1].anonChildren + +proc getParent(frame: var TreeFrame; computed: CSSValues; display: CSSDisplay): + var seq[StyledNode] = + let parentDisplay = frame.computed{"display"} + case parentDisplay + of DisplayInnerFlex: + if display in DisplayOuterInline: + if frame.anonComputed == nil: + frame.anonComputed = frame.inheritFor(DisplayBlock) + frame.children.add(StyledNode( + t: stElement, + element: frame.parent, + computed: frame.anonComputed, + skipChildren: true + )) + return frame.children[^1].anonChildren + of DisplayTableRow: + if display != DisplayTableCell: + return frame.getInternalTableParent(DisplayTableCell) + frame.anonTableDisplay = DisplayNone + of RowGroupBox: + if display != DisplayTableRow: + return frame.getInternalTableParent(DisplayTableRow) + frame.anonTableDisplay = DisplayNone + of DisplayTableWrapper: + if display notin RowGroupBox + {DisplayTableRow}: + return frame.getInternalTableParent(DisplayTableRow) + frame.anonTableDisplay = DisplayNone + of DisplayInnerTable: + if frame.children.len > 0 and display != DisplayTableCaption: + return frame.children[0].anonChildren + of DisplayListItem: + if frame.computed{"list-style-position"} == ListStylePositionOutside and + frame.children.len >= 2: + return frame.children[1].anonChildren + elif display in DisplayInternalTable: + return frame.addAnonTable(parentDisplay, display) + else: + frame.captionSeen = false + frame.anonComputed = nil + return frame.children + +proc addListItem(frame: var TreeFrame; node: sink StyledNode) = + var node = node + # Generate a marker box. + inc frame.ctx.listItemCounter + let computed = node.computed.inheritProperties() + computed{"display"} = DisplayBlock + computed{"white-space"} = WhitespacePre + let t = computed{"list-style-type"} + let markerText = StyledNode( + t: stText, + element: node.element, + text: newCharacterData(t.listMarker(frame.ctx.listItemCounter)), + computed: computed.inheritProperties() + ) + case node.computed{"list-style-position"} + of ListStylePositionOutside: + # Generate a separate box for the content and marker. + node.anonChildren.add(StyledNode( + t: stElement, + element: node.element, + computed: computed, + skipChildren: true, + anonChildren: @[markerText] + )) + let computed = node.computed.inheritProperties() + computed{"display"} = DisplayBlock + node.anonChildren.add(StyledNode( + t: stElement, + element: node.element, + computed: computed, + skipChildren: true + )) + of ListStylePositionInside: + node.anonChildren.add(markerText) + frame.getParent(node.computed, node.computed{"display"}).add(node) + +proc addTable(frame: var TreeFrame; node: sink StyledNode) = + var node = node + let (outer, inner) = node.computed.splitTable() + node.computed = outer + node.anonChildren.add(StyledNode( + t: stElement, + element: node.element, + computed: inner, + skipChildren: true + )) + frame.getParent(node.computed, node.computed{"display"}).add(node) + +proc add(frame: var TreeFrame; node: sink StyledNode) = + let display = node.computed{"display"} + if frame.captionSeen and display == DisplayTableCaption: + return + if node.t == stElement and node.anonChildren.len == 0: + case display + of DisplayListItem: + frame.addListItem(node) + frame.lastChildWasInline = false + return # already added + of DisplayInnerTable: + frame.addTable(node) + frame.lastChildWasInline = false + return # already added + else: discard + frame.getParent(node.computed, display).add(node) + frame.lastChildWasInline = display in DisplayOuterInline + if display == DisplayTableCaption: + frame.captionSeen = true + +proc addAnon(frame: var TreeFrame; children: sink seq[StyledNode]; + computed: CSSValues) = + frame.add(StyledNode( + t: stElement, + element: frame.parent, + anonChildren: children, + computed: computed, + skipChildren: true + )) + +proc addElement(frame: var TreeFrame; element: Element) = + if element.computed == nil: + element.applyStyle() + if frame.displayed(element): + frame.add(StyledNode( + t: stElement, + element: element, + computed: element.computed + )) + +proc addPseudo(frame: var TreeFrame; pseudo: PseudoElement) = + if frame.displayed(pseudo): + frame.add(StyledNode( + t: stElement, + pseudo: pseudo, + element: frame.parent, + computed: frame.parent.computedMap[pseudo] + )) + +proc addText(frame: var TreeFrame; text: CharacterData) = + if frame.displayed(text): + frame.add(StyledNode( + t: stText, + element: frame.parent, + text: text, + computed: frame.getAnonInlineComputed() + )) + +proc addText(frame: var TreeFrame; s: sink string) = + #TODO should probably cache these... + frame.addText(newCharacterData(s)) + +proc addImage(frame: var TreeFrame; bmp: NetworkBitmap) = + if bmp != nil and bmp.cacheId != -1: + frame.add(StyledNode( + t: stImage, + element: frame.parent, + bmp: bmp, + computed: frame.getAnonInlineComputed() + )) + else: + frame.addText("[img]") + +proc addBr(frame: var TreeFrame) = + frame.add(StyledNode( + t: stBr, + element: frame.parent, + computed: frame.computed + )) + +proc addElementChildren(frame: var TreeFrame) = + for it in frame.parent.childList: + if it of Element: + let element = Element(it) + frame.addElement(element) + elif it of Text: + #TODO collapse subsequent text nodes into one StyledNode + # (it isn't possible in HTML, only with JS DOM manipulation) + let text = Text(it) + frame.addText(text) + +proc addOptionChildren(frame: var TreeFrame; option: HTMLOptionElement) = + if option.select != nil and option.select.attrb(satMultiple): + frame.addText("[") + let cdata = newCharacterData(if option.selected: "*" else: " ") + let computed = option.computed.inheritProperties() + computed{"color"} = cssColor(ANSIColor(1)) # red + computed{"white-space"} = WhitespacePre + block anon: + var aframe = frame.ctx.initTreeFrame(option, computed) + aframe.addText(cdata) + frame.addAnon(move(aframe.children), computed) + frame.addText("]") + frame.addElementChildren() + +proc addChildren(frame: var TreeFrame) = + case frame.parent.tagType + of TAG_INPUT: + let cdata = HTMLInputElement(frame.parent).inputString() + if cdata != nil: + frame.addText(cdata) + of TAG_TEXTAREA: + #TODO cache (do the same as with input, and add borders in render) + frame.addText(HTMLTextAreaElement(frame.parent).textAreaString()) + of TAG_IMG: frame.addImage(HTMLImageElement(frame.parent).bitmap) + of TAG_CANVAS: frame.addImage(HTMLCanvasElement(frame.parent).bitmap) + of TAG_VIDEO: frame.addText("[video]") + of TAG_AUDIO: frame.addText("[audio]") + of TAG_BR: frame.addBr() + of TAG_IFRAME: frame.addText("[iframe]") + of TAG_FRAME: frame.addText("[frame]") + of TAG_OPTION: + let option = HTMLOptionElement(frame.parent) + frame.addOptionChildren(option) + elif frame.parent.tagType(Namespace.SVG) == TAG_SVG: + frame.addImage(SVGSVGElement(frame.parent).bitmap) + else: + frame.addElementChildren() + +proc addContent(frame: var TreeFrame; content: CSSContent; ctx: var TreeContext; + computed: CSSValues) = + case content.t + of ContentString: + frame.addText(content.s) + of ContentOpenQuote: + let quotes = frame.computed{"quotes"} + if quotes == nil: + frame.addText(quoteStart(ctx.quoteLevel)) + elif quotes.qs.len > 0: + frame.addText(quotes.qs[min(ctx.quoteLevel, quotes.qs.high)].s) + else: + return + inc ctx.quoteLevel + of ContentCloseQuote: + if ctx.quoteLevel > 0: + dec ctx.quoteLevel + let quotes = computed{"quotes"} + if quotes == nil: + frame.addText(quoteEnd(ctx.quoteLevel)) + elif quotes.qs.len > 0: + frame.addText(quotes.qs[min(ctx.quoteLevel, quotes.qs.high)].e) + of ContentNoOpenQuote: + inc ctx.quoteLevel + of ContentNoCloseQuote: + if ctx.quoteLevel > 0: + dec ctx.quoteLevel + +proc build(frame: var TreeFrame; styledNode: StyledNode; + ctx: var TreeContext) = + for child in styledNode.anonChildren: + frame.add(child) + if styledNode.skipChildren: + return + let parent = styledNode.element + if styledNode.pseudo == peNone: + frame.addPseudo(peBefore) + frame.addChildren() + frame.addPseudo(peAfter) + else: + let computed = parent.computedMap[styledNode.pseudo].inheritProperties() + for content in parent.computedMap[styledNode.pseudo]{"content"}: + frame.addContent(content, ctx, computed) + +iterator children*(styledNode: StyledNode; ctx: var TreeContext): StyledNode + {.inline.} = + if styledNode.t == stElement: + for reset in styledNode.computed{"counter-reset"}: + if reset.name == "list-item": + ctx.listItemCounter = reset.num + let listItemCounter = ctx.listItemCounter + let parent = styledNode.element + var frame = ctx.initTreeFrame(parent, styledNode.computed) + frame.build(styledNode, ctx) + for child in frame.children: + yield child + ctx.listItemCounter = listItemCounter + +when defined(debug): + proc computedTree*(styledNode: StyledNode; ctx: var TreeContext): string = + result = "" + if styledNode.t != stElement: + result &= $styledNode + else: + result &= "<" + if styledNode.computed{"display"} != DisplayInline: + result &= "div" + else: + result &= "span" + let computed = styledNode.computed.copyProperties() + if computed{"display"} == DisplayBlock: + computed{"display"} = DisplayInline + result &= " style='" & $computed.serializeEmpty() & "'>\n" + for it in styledNode.children(ctx): + result &= it.computedTree(ctx) + result &= "\n</div>" + + proc computedTree*(styledNode: StyledNode): string = + var ctx = TreeContext() + return styledNode.computedTree(ctx) diff --git a/src/css/cssvalues.nim b/src/css/cssvalues.nim index 2980e130..00011cc5 100644 --- a/src/css/cssvalues.nim +++ b/src/css/cssvalues.nim @@ -191,7 +191,6 @@ type DisplayInlineFlex = "inline-flex" # internal, for layout DisplayTableWrapper = "" - DisplayInlineTableWrapper = "" CSSWhiteSpace* = enum WhitespaceNormal = "normal" @@ -283,7 +282,7 @@ type CSSContentType* = enum ContentString, ContentOpenQuote, ContentCloseQuote, ContentNoOpenQuote, - ContentNoCloseQuote, ContentImage, ContentNewline + ContentNoCloseQuote CSSFloat* = enum FloatNone = "none" @@ -354,11 +353,8 @@ type num*: float32 CSSContent* = object - case t*: CSSContentType - of ContentImage: - bmp*: NetworkBitmap - else: - s*: string + t*: CSSContentType + s*: string # nil -> auto CSSQuotes* = ref object @@ -538,11 +534,25 @@ const OverflowScrollLike* = {OverflowScroll, OverflowAuto, OverflowOverlay} const OverflowHiddenLike* = {OverflowHidden, OverflowClip} const FlexReverse* = {FlexDirectionRowReverse, FlexDirectionColumnReverse} const DisplayOuterInline* = { - DisplayInline, DisplayInlineTable, DisplayInlineBlock, - DisplayInlineTableWrapper, DisplayInlineFlex + DisplayInline, DisplayInlineTable, DisplayInlineBlock, DisplayInlineFlex } const DisplayInnerFlex* = {DisplayFlex, DisplayInlineFlex} +const RowGroupBox* = { + # Note: caption is not included here + DisplayTableRowGroup, DisplayTableHeaderGroup, DisplayTableFooterGroup +} +const ProperTableChild* = RowGroupBox + { + DisplayTableRow, DisplayTableColumn, DisplayTableColumnGroup +} +const DisplayInnerTable* = {DisplayTable, DisplayInlineTable} +const DisplayInternalTable* = { + DisplayTableCell, DisplayTableRow, DisplayTableCaption +} + RowGroupBox +const ProperTableRowParent* = RowGroupBox + {DisplayTableWrapper} #TODO remove const PositionAbsoluteFixed* = {PositionAbsolute, PositionFixed} +const WhiteSpacePreserve* = { + WhitespacePre, WhitespacePreLine, WhitespacePreWrap +} # Forward declarations proc parseValue(cvals: openArray[CSSComponentValue]; t: CSSPropertyType; @@ -586,8 +596,6 @@ func `$`*(bmp: NetworkBitmap): string = return "" #TODO func `$`*(content: CSSContent): string = - if content.t == ContentImage: - return $content.bmp if content.s != "": return "url(" & content.s & ")" return "none" @@ -711,7 +719,7 @@ func inherited*(t: CSSPropertyType): bool = func blockify*(display: CSSDisplay): CSSDisplay = case display of DisplayBlock, DisplayTable, DisplayListItem, DisplayNone, DisplayFlowRoot, - DisplayFlex, DisplayTableWrapper, DisplayInlineTableWrapper: + DisplayFlex, DisplayTableWrapper: #TODO grid return display of DisplayInline, DisplayInlineBlock, DisplayTableRow, @@ -724,12 +732,6 @@ func blockify*(display: CSSDisplay): CSSDisplay = of DisplayInlineFlex: return DisplayFlex -func toTableWrapper*(display: CSSDisplay): CSSDisplay = - if display == DisplayTable: - return DisplayTableWrapper - assert display == DisplayInlineTable - return DisplayInlineTableWrapper - func bfcify*(overflow: CSSOverflow): CSSOverflow = if overflow == OverflowVisible: return OverflowAuto @@ -1875,6 +1877,7 @@ func splitTable*(computed: CSSValues): tuple[outer, innner: CSSValues] = inner.copyFrom(computed, t) outer.setInitial(t) outer{"display"} = computed{"display"} + inner{"display"} = DisplayTableWrapper return (outer, inner) when defined(debug): diff --git a/src/css/layout.nim b/src/css/layout.nim index eddffced..0153b7e8 100644 --- a/src/css/layout.nim +++ b/src/css/layout.nim @@ -2,7 +2,7 @@ import std/algorithm import std/math import css/box -import css/cascade +import css/csstree import css/cssvalues import css/lunit import html/dom @@ -47,8 +47,6 @@ type cellSize: Size # size(w = attrsp.ppc, h = attrsp.ppl) positioned: seq[PositionedItem] myRootProperties: CSSValues - # placeholder text data - imgText: CharacterData luctx: LUContext const DefaultSpan = Span(start: 0, send: LUnit.high) @@ -120,8 +118,7 @@ func isDefinite(sc: SizeConstraint): bool = # children. func establishesBFC(computed: CSSValues): bool = return computed{"float"} != FloatNone or - computed{"display"} in {DisplayFlowRoot, DisplayTable, DisplayTableWrapper, - DisplayFlex} or + computed{"display"} in {DisplayFlowRoot, DisplayTable, DisplayFlex} or computed{"overflow-x"} notin {OverflowVisible, OverflowClip} #TODO contain, grid, multicol, column-span @@ -404,7 +401,7 @@ type firstBaselineSet: bool # Forward declarations -proc layoutTableWrapper(bctx: BlockContext; box: BlockBox; sizes: ResolvedSizes) +proc layoutTable(bctx: BlockContext; box: BlockBox; sizes: ResolvedSizes) proc layoutFlex(bctx: var BlockContext; box: BlockBox; sizes: ResolvedSizes) proc layoutRootBlock(lctx: LayoutContext; box: BlockBox; offset: Offset; sizes: ResolvedSizes; includeMargin = false) @@ -2107,16 +2104,6 @@ proc layoutListItem(bctx: var BlockContext; box: BlockBox; # Distribute the table's content width among cells with an unspecified # width. If this would give any cell a width < min_width, set that # cell's width to min_width, then re-do the distribution. -const RowGroupBox = { - # Note: caption is not included here - DisplayTableRowGroup, DisplayTableHeaderGroup, DisplayTableFooterGroup -} -const ProperTableChild = RowGroupBox + { - DisplayTableRow, DisplayTableColumn, DisplayTableColumnGroup -} -const DisplayInnerTable = {DisplayTable, DisplayInlineTable} -const ProperTableRowParent = RowGroupBox + DisplayInnerTable - type CellWrapper = ref object box: BlockBox @@ -2414,7 +2401,7 @@ proc preLayoutTableRows(tctx: var TableContext; table: BlockBox) = of DisplayTableFooterGroup: for it in child.children: tfoot.add(BlockBox(it)) - else: assert false + else: assert false, $child.computed{"display"} tctx.preLayoutTableRows(thead, table) tctx.preLayoutTableRows(tbody, table) tctx.preLayoutTableRows(tfoot, table) @@ -2553,13 +2540,13 @@ proc layoutCaption(tctx: TableContext; parent, box: BlockBox) = parent.state.size.h += outerHeight parent.state.intr.h += outerHeight - box.state.size.h + box.state.intr.h -proc layoutTable(tctx: var TableContext; table, parent: BlockBox; +proc layoutInnerTable(tctx: var TableContext; table, parent: BlockBox; sizes: ResolvedSizes) = if table.computed{"border-collapse"} != BorderCollapseCollapse: let spc = table.computed{"border-spacing"} if spc != nil: - tctx.inlineSpacing = table.computed{"border-spacing"}.a.px(0) - tctx.blockSpacing = table.computed{"border-spacing"}.b.px(0) + tctx.inlineSpacing = spc.a.px(0) + tctx.blockSpacing = spc.b.px(0) tctx.preLayoutTableRows(table) # first pass # Percentage sizes have been resolved; switch the table's space to # fit-content if its width is auto. @@ -2583,12 +2570,11 @@ proc layoutTable(tctx: var TableContext; table, parent: BlockBox; # As per standard, we must put the caption outside the actual table, inside a # block-level wrapper box. -proc layoutTableWrapper(bctx: BlockContext; box: BlockBox; - sizes: ResolvedSizes) = +proc layoutTable(bctx: BlockContext; box: BlockBox; sizes: ResolvedSizes) = let table = BlockBox(box.children[0]) table.state = BoxLayoutState() var tctx = TableContext(lctx: bctx.lctx, space: sizes.space) - tctx.layoutTable(table, box, sizes) + tctx.layoutInnerTable(table, box, sizes) box.state.size = table.state.size box.state.baseline = table.state.size.h box.state.firstBaseline = table.state.size.h @@ -2606,8 +2592,8 @@ proc layout(bctx: var BlockContext; box: BlockBox; sizes: ResolvedSizes; bctx.layoutFlow(box, sizes, canClear) of DisplayListItem: bctx.layoutListItem(box, sizes) - of DisplayTableWrapper, DisplayInlineTableWrapper: - bctx.layoutTableWrapper(box, sizes) + of DisplayInnerTable: + bctx.layoutTable(box, sizes) of DisplayInnerFlex: bctx.layoutFlex(box, sizes) else: @@ -2878,125 +2864,29 @@ proc layoutRootBlock(lctx: LayoutContext; box: BlockBox; offset: Offset; box.state.marginBottom = marginBottom # 1st pass: build tree -#TODO ideally we should move this to layout and/or cascade. -# (Anon box generation probably belongs in cascade, so that caching -# existing boxes becomes feasible.) +#TODO this tree traversal is largely redundant, and should be moved +# to csstree. (Then it should be collapsed into the second pass by +# lazily generating child boxes, taking boxes from the previous pass +# when possible.) -type - BoxBuilderContext = object - styledNode: StyledNode - lctx: LayoutContext - anonRow: BlockBox - anonTableWrapper: BlockBox - quoteLevel: int - listItemCounter: int +proc build(ctx: var TreeContext; styledNode: StyledNode; + children: var seq[CSSBox]) - ParentType = enum - ptBlock, ptInline - -# Forward declarations -proc build(ctx: var BoxBuilderContext; box: BlockBox) -proc buildInline(pctx: var BoxBuilderContext; styledNode: StyledNode): - InlineBox -proc initBoxBuilderContext(styledNode: StyledNode; lctx: LayoutContext; - parent: ptr BoxBuilderContext): BoxBuilderContext -proc flushTable(ctx: var BoxBuilderContext; parent: BlockBox) - -template initBoxBuilderContext(styledNode: StyledNode; - pctx: var BoxBuilderContext): BoxBuilderContext = - initBoxBuilderContext(styledNode, pctx.lctx, addr pctx) - -proc newMarkerBox(computed: CSSValues; listItemCounter: int): InlineBox = - let computed = computed.inheritProperties() - # Use pre, so the space at the end of the default markers isn't ignored. - computed{"white-space"} = WhitespacePre - let s = computed{"list-style-type"}.listMarker(listItemCounter) - return InlineBox( - t: ibtText, - computed: computed, - text: newCharacterData(s) +proc buildInline(ctx: var TreeContext; styledNode: StyledNode): InlineBox = + let ibox = InlineBox( + t: ibtParent, + computed: styledNode.computed, + element: styledNode.element ) + ctx.build(styledNode, ibox.children) + return ibox -proc createAnonTable(ctx: var BoxBuilderContext; parentType: ParentType): - BlockBox = - if ctx.anonTableWrapper == nil: - let (outer, inner) = rootProperties().splitTable() - outer{"display"} = case parentType - of ptInline: DisplayInlineTableWrapper - of ptBlock: DisplayTableWrapper - let innerTable = BlockBox(computed: inner) - let box = BlockBox( - computed: outer, - children: @[CSSBox(innerTable)] - ) - ctx.anonTableWrapper = box - return box - return ctx.anonTableWrapper - -proc createAnonRow(ctx: var BoxBuilderContext; parent: CSSBox): BlockBox = - if ctx.anonRow == nil: - let wrapperVals = parent.computed.inheritProperties() - wrapperVals{"display"} = DisplayTableRow - let box = BlockBox(computed: wrapperVals) - ctx.anonRow = box - return box - return ctx.anonRow - -proc flushTableRow(ctx: var BoxBuilderContext; parent: CSSBox; - parentType: ParentType) = - if ctx.anonRow != nil: - let parentDisplay = parent.computed{"display"} - if parentDisplay in ProperTableRowParent: - BlockBox(parent).children.add(ctx.anonRow) - else: - let anonTableWrapper = ctx.createAnonTable(parentType) - BlockBox(anonTableWrapper.children[0]).children.add(ctx.anonRow) - ctx.anonRow = nil - -proc flushTable(ctx: var BoxBuilderContext; parent: BlockBox) = - ctx.flushTableRow(parent, ptBlock) - if ctx.anonTableWrapper != nil: - parent.children.add(ctx.anonTableWrapper) - ctx.anonTableWrapper = nil - -proc flushInlineTable(ctx: var BoxBuilderContext; parent: InlineBox) = - ctx.flushTableRow(parent, ptInline) - if ctx.anonTableWrapper != nil: - assert ctx.anonTableWrapper.computed{"display"} == DisplayInlineTableWrapper - parent.children.add(InlineBox( - t: ibtBox, - computed: ctx.anonTableWrapper.computed.inheritProperties(), - box: ctx.anonTableWrapper - )) - ctx.anonTableWrapper = nil - -proc buildBlock(pctx: var BoxBuilderContext; styledNode: StyledNode): - BlockBox = +proc buildBlock(ctx: var TreeContext; styledNode: StyledNode): BlockBox = let box = BlockBox(computed: styledNode.computed, element: styledNode.element) - var ctx = initBoxBuilderContext(styledNode, pctx) - ctx.build(box) - pctx.quoteLevel = ctx.quoteLevel + ctx.build(styledNode, box.children) return box -proc buildInlineText(parent: StyledNode; text: CharacterData; - computed: CSSValues): InlineBox = - return InlineBox( - t: ibtText, - computed: computed, - element: parent.element, - text: text - ) - -proc buildInlineText(parent: StyledNode; text: CharacterData): InlineBox = - return InlineBox( - t: ibtText, - computed: parent.computed, - element: parent.element, - text: text - ) - -proc buildInlineBlock(ctx: var BoxBuilderContext; styledNode: StyledNode): - InlineBox = +proc buildInlineBlock(ctx: var TreeContext; styledNode: StyledNode): InlineBox = return InlineBox( t: ibtBox, computed: styledNode.computed.inheritProperties(), @@ -3004,313 +2894,51 @@ proc buildInlineBlock(ctx: var BoxBuilderContext; styledNode: StyledNode): box: ctx.buildBlock(styledNode) ) -proc buildListItem(ctx: var BoxBuilderContext; styledNode: StyledNode): - BlockBox = - inc ctx.listItemCounter - let marker = newMarkerBox(styledNode.computed, ctx.listItemCounter) - let position = styledNode.computed{"list-style-position"} - let content = BlockBox( - computed: styledNode.computed, - element: styledNode.element - ) - var contentCtx = initBoxBuilderContext(styledNode, ctx.lctx, addr ctx) - case position - of ListStylePositionOutside: - contentCtx.build(content) - content.computed = content.computed.copyProperties() - content.computed{"display"} = DisplayBlock - let markerComputed = marker.computed.copyProperties() - markerComputed{"display"} = markerComputed{"display"}.blockify() - let marker = BlockBox( - computed: markerComputed, - children: @[CSSBox(marker)] - ) - return BlockBox( - computed: styledNode.computed, - children: @[CSSBox(marker), CSSBox(content)] - ) - of ListStylePositionInside: - content.children.add(marker) - contentCtx.build(content) - return content - -proc buildFromElem(ctx: var BoxBuilderContext; styledNode: StyledNode): - CSSBox = +proc buildFromElem(ctx: var TreeContext; styledNode: StyledNode): CSSBox = return case styledNode.computed{"display"} of DisplayBlock, DisplayFlowRoot, DisplayFlex, DisplayTable, - DisplayTableCaption, DisplayTableCell, RowGroupBox, DisplayTableRow: + DisplayTableCaption, DisplayTableCell, RowGroupBox, DisplayTableRow, + DisplayTableWrapper, DisplayListItem: ctx.buildBlock(styledNode) of DisplayInlineBlock, DisplayInlineTable, DisplayInlineFlex: ctx.buildInlineBlock(styledNode) - of DisplayListItem: ctx.buildListItem(styledNode) of DisplayInline: ctx.buildInline(styledNode) - of DisplayTableColumn, DisplayTableColumnGroup: nil #TODO - of DisplayNone: nil - of DisplayTableWrapper, DisplayInlineTableWrapper: + of DisplayTableColumn, DisplayTableColumnGroup, #TODO + DisplayNone: assert false nil -proc buildReplacement(ctx: var BoxBuilderContext; child: StyledNode; - computed: CSSValues): InlineBox = - case child.content.t - of ContentOpenQuote: - let quotes = child.computed{"quotes"} - let s = if quotes == nil: - quoteStart(ctx.quoteLevel) - elif quotes.qs.len > 0: - quotes.qs[min(ctx.quoteLevel, quotes.qs.high)].s - else: - return - let node = newCharacterData(s) - result = child.buildInlineText(node, computed) - inc ctx.quoteLevel - of ContentCloseQuote: - if ctx.quoteLevel > 0: - dec ctx.quoteLevel - let quotes = child.computed{"quotes"} - let s = if quotes == nil: - quoteEnd(ctx.quoteLevel) - elif quotes.qs.len > 0: - quotes.qs[min(ctx.quoteLevel, quotes.qs.high)].e - else: - return - let text = newCharacterData(s) - return child.buildInlineText(text, computed) - of ContentNoOpenQuote: - inc ctx.quoteLevel - return nil - of ContentNoCloseQuote: - if ctx.quoteLevel > 0: - dec ctx.quoteLevel - return nil - of ContentString: - #TODO make CharacterData in cssvalues? - let text = newCharacterData(child.content.s) - return child.buildInlineText(text, computed) - of ContentImage: - if child.content.bmp != nil and child.content.bmp.cacheId != -1: - return InlineBox( - t: ibtBitmap, - computed: computed, - element: child.element, - image: InlineImage(bmp: child.content.bmp) - ) - return child.buildInlineText(ctx.lctx.imgText, computed) - of ContentNewline: - return InlineBox( - t: ibtNewline, - computed: computed, - element: child.element - ) - -proc buildInline(pctx: var BoxBuilderContext; styledNode: StyledNode): - InlineBox = - let ibox = InlineBox( - t: ibtParent, - computed: styledNode.computed, - element: styledNode.element - ) - var ctx = initBoxBuilderContext(styledNode, pctx) - for child in styledNode.children: - case child.t - of stElement: - let child = ctx.buildFromElem(child) - if child != nil: - case child.computed{"display"} - of DisplayTableRow: - ctx.flushTableRow(ibox, ptInline) - let anonTableWrapper = ctx.createAnonTable(ptInline) - BlockBox(anonTableWrapper.children[0]).children.add(child) - of RowGroupBox: - ctx.flushTableRow(ibox, ptInline) - let anonTableWrapper = ctx.createAnonTable(ptInline) - BlockBox(anonTableWrapper.children[0]).children.add(child) - of DisplayTableCell: - ctx.createAnonRow(ibox).children.add(child) - of DisplayTableCaption: - ctx.flushTableRow(ibox, ptInline) - let anonTableWrapper = ctx.createAnonTable(ptInline) - # only add first caption - if anonTableWrapper.children.len == 1: - anonTableWrapper.children.add(child) - else: - ctx.flushInlineTable(ibox) - ibox.children.add(child) - of stText: - ctx.flushInlineTable(ibox) - ibox.children.add(styledNode.buildInlineText(child.text)) - of stReplacement: - ctx.flushInlineTable(ibox) - let child = ctx.buildReplacement(child, styledNode.computed) - if child != nil: - ibox.children.add(child) - ctx.flushInlineTable(ibox) - pctx.quoteLevel = ctx.quoteLevel - return ibox - -proc initBoxBuilderContext(styledNode: StyledNode; lctx: LayoutContext; - parent: ptr BoxBuilderContext): BoxBuilderContext = - result = BoxBuilderContext(styledNode: styledNode, lctx: lctx) - if parent != nil: - result.listItemCounter = parent[].listItemCounter - result.quoteLevel = parent[].quoteLevel - for reset in styledNode.computed{"counter-reset"}: - if reset.name == "list-item": - result.listItemCounter = reset.num - -proc buildTableRowChildWrappers(box: BlockBox) = - var wrapperVals: CSSValues = nil - for child in box.children: - if child.computed{"display"} != DisplayTableCell: - wrapperVals = box.computed.inheritProperties() - wrapperVals{"display"} = DisplayTableCell - break - if wrapperVals != nil: - # fixup row: put wrappers around runs of misparented children - var children = newSeqOfCap[CSSBox](box.children.len) - var wrapper: BlockBox = nil - for child in box.children: - if child.computed{"display"} != DisplayTableCell: - if wrapper == nil: - wrapper = BlockBox(computed: wrapperVals) - children.add(wrapper) - wrapper.children.add(child) - else: - wrapper = nil - children.add(child) - box.children = move(children) - -proc buildTableRowGroupChildWrappers(box: BlockBox) = - var wrapperVals: CSSValues = nil - for child in box.children: - if child.computed{"display"} != DisplayTableRow: - wrapperVals = box.computed.inheritProperties() - wrapperVals{"display"} = DisplayTableRow - break - if wrapperVals != nil: - # fixup row group: put wrappers around runs of misparented children - var wrapper: BlockBox = nil - var children = newSeqOfCap[CSSBox](box.children.len) - for child in box.children: - if child.computed{"display"} != DisplayTableRow: - if wrapper == nil: - wrapper = BlockBox(computed: wrapperVals, children: @[child]) - children.add(wrapper) - wrapper.children.add(child) - else: - if wrapper != nil: - wrapper.buildTableRowChildWrappers() - wrapper = nil - children.add(child) - if wrapper != nil: - wrapper.buildTableRowChildWrappers() - box.children = move(children) - -proc buildTableChildWrappers(box: BlockBox; computed: CSSValues) = - let innerTable = BlockBox(computed: computed, element: box.element) - let wrapperVals = box.computed.inheritProperties() - wrapperVals{"display"} = DisplayTableRow - var caption: CSSBox = nil - var wrapper: BlockBox = nil - for child in box.children: - if child.computed{"display"} in ProperTableChild: - if wrapper != nil: - wrapper.buildTableRowChildWrappers() - wrapper = nil - innerTable.children.add(child) - elif child.computed{"display"} == DisplayTableCaption: - if caption == nil: - caption = child - else: - if wrapper == nil: - wrapper = BlockBox(computed: wrapperVals) - innerTable.children.add(wrapper) - wrapper.children.add(child) - if wrapper != nil: - wrapper.buildTableRowChildWrappers() - box.children = @[CSSBox(innerTable)] - if caption != nil: - box.children.add(caption) - -# Don't build empty anonymous inline blocks between block boxes -func canBuildAnonInline(ctx: BoxBuilderContext; box: BlockBox; s: string): - bool = - return box.children.len > 0 and ctx.anonTableWrapper == nil and - ctx.anonRow == nil and - box.children[^1].computed{"display"} in DisplayOuterInline or - box.computed.whitespacepre or not s.onlyWhitespace() - -proc build(ctx: var BoxBuilderContext; box: BlockBox) = - let inlineComputed = box.computed.inheritProperties() - for child in ctx.styledNode.children: +proc build(ctx: var TreeContext; styledNode: StyledNode; + children: var seq[CSSBox]) = + for child in styledNode.children(ctx): case child.t of stElement: - let child = ctx.buildFromElem(child) - if child != nil: - case child.computed{"display"} - of DisplayTableRow: - ctx.flushTableRow(box, ptBlock) - if box.computed{"display"} in ProperTableRowParent: - box.children.add(child) - else: - let anonTableWrapper = ctx.createAnonTable(ptBlock) - BlockBox(anonTableWrapper.children[0]).children.add(child) - of RowGroupBox: - ctx.flushTableRow(box, ptBlock) - if box.computed{"display"} in DisplayInnerTable: - box.children.add(child) - else: - let anonTableWrapper = ctx.createAnonTable(ptBlock) - BlockBox(anonTableWrapper.children[0]).children.add(child) - of DisplayTableCell: - if box.computed{"display"} == DisplayTableRow: - box.children.add(child) - else: - ctx.createAnonRow(box).children.add(child) - of DisplayTableCaption: - ctx.flushTableRow(box, ptBlock) - if box.computed{"display"} in DisplayInnerTable: - box.children.add(child) - else: - let anonTableWrapper = ctx.createAnonTable(ptBlock) - # only add first caption - if anonTableWrapper.children.len == 1: - anonTableWrapper.children.add(child) - else: - ctx.flushTable(box) - box.children.add(child) + children.add(ctx.buildFromElem(child)) of stText: - let text = child.text - if ctx.canBuildAnonInline(box, text.data): - ctx.flushTable(box) - let child = ctx.styledNode.buildInlineText(text, inlineComputed) - box.children.add(child) - of stReplacement: - let child = ctx.buildReplacement(child, inlineComputed) - if child != nil: - ctx.flushTable(box) - box.children.add(child) - ctx.flushTable(box) - case box.computed{"display"} - of DisplayInnerFlex: - let blockComputed = inlineComputed.copyProperties() - blockComputed{"display"} = DisplayBlock - for it in box.children.mitems: - if it.computed{"display"} in DisplayOuterInline: - it = BlockBox(computed: blockComputed, children: @[it]) - of DisplayInnerTable: - let (outer, inner) = box.computed.splitTable() - box.computed = outer - outer{"display"} = outer{"display"}.toTableWrapper() - box.buildTableChildWrappers(inner) - of RowGroupBox: - box.buildTableRowGroupChildWrappers() - of DisplayTableRow: - box.buildTableRowChildWrappers() - else: - discard + children.add(InlineBox( + t: ibtText, + computed: child.computed, + element: child.element, + text: child.text + )) + of stBr: + children.add(InlineBox( + t: ibtNewline, + computed: child.computed, + element: child.element + )) + of stImage: + children.add(InlineBox( + t: ibtBitmap, + computed: child.computed, + element: child.element, + image: InlineImage(bmp: child.bmp) + )) proc layout*(root: StyledNode; attrsp: ptr WindowAttributes): BlockBox = + let box = BlockBox(computed: root.computed, element: root.element) + var ctx = TreeContext() + ctx.build(root, box.children) let space = availableSpace( w = stretch(attrsp[].widthPx), h = stretch(attrsp[].heightPx) @@ -3320,12 +2948,8 @@ proc layout*(root: StyledNode; attrsp: ptr WindowAttributes): BlockBox = cellSize: size(w = attrsp.ppc, h = attrsp.ppl), positioned: @[PositionedItem(), PositionedItem()], myRootProperties: rootProperties(), - imgText: newCharacterData("[img]"), luctx: LUContext() ) - let box = BlockBox(computed: root.computed, element: root.element) - var ctx = initBoxBuilderContext(root, lctx, nil) - ctx.build(box) let sizes = lctx.resolveBlockSizes(space, box.computed) # the bottom margin is unused. lctx.layoutRootBlock(box, sizes.margin.topLeft, sizes) diff --git a/src/server/buffer.nim b/src/server/buffer.nim index f2e1d560..d20b73fe 100644 --- a/src/server/buffer.nim +++ b/src/server/buffer.nim @@ -13,6 +13,7 @@ import chame/tags import config/config import css/box import css/cascade +import css/csstree import css/layout import css/lunit import css/render |