From 3b3bc801134354bec98bba8e7eec7de8a95b32a3 Mon Sep 17 00:00:00 2001 From: Paulo Medeiros Date: Tue, 3 Dec 2024 17:05:06 +0100 Subject: [PATCH] vaev-layout: implement table wrapper as BFC --- src/web/vaev-layout/base.h | 3 + src/web/vaev-layout/block.cpp | 38 ++++++++- src/web/vaev-layout/builder.cpp | 28 ++++++- src/web/vaev-layout/layout.cpp | 5 +- src/web/vaev-layout/table.cpp | 93 +++------------------ tests/css/display-table.xhtml | 139 ++++++++++++++++++++++++++++++++ 6 files changed, 216 insertions(+), 90 deletions(-) diff --git a/src/web/vaev-layout/base.h b/src/web/vaev-layout/base.h index 921f797..94681dd 100644 --- a/src/web/vaev-layout/base.h +++ b/src/web/vaev-layout/base.h @@ -38,6 +38,9 @@ struct Input { Vec2Px availableSpace = {}; Vec2Px containingBlock = {}; + // To be used between table wrapper and table box + Opt capmin = NONE; + Input withCommit(Commit c) const { auto copy = *this; copy.commit = c; diff --git a/src/web/vaev-layout/block.cpp b/src/web/vaev-layout/block.cpp index cac8043..444624b 100644 --- a/src/web/vaev-layout/block.cpp +++ b/src/web/vaev-layout/block.cpp @@ -7,7 +7,34 @@ namespace Vaev::Layout { // https://www.w3.org/TR/CSS22/visuren.html#normal-flow struct BlockFormatingContext { + + Px computeCAPMIN(Tree &tree, Box &box, Input input, Px inlineSize) { + Px capmin{}; + for (auto &c : box.children()) { + if (c.style->display != Display::TABLE_BOX) { + auto margin = computeMargins( + tree, c, + { + .containingBlock = {inlineSize, input.knownSize.y.unwrapOr(0_px)}, + } + ); + + auto minContentContrib = computeIntrinsicSize( + tree, c, IntrinsicSize::MIN_CONTENT, input.containingBlock + ); + + capmin = max( + capmin, + minContentContrib.width + margin.horizontal() + ); + } + } + + return capmin; + } + Output run(Tree &tree, Box &box, Input input) { + Px blockSize = 0_px; Px inlineSize = input.knownSize.width.unwrapOr(0_px); @@ -43,17 +70,20 @@ struct BlockFormatingContext { childInput.position = input.position + Vec2Px{margin.start, blockSize}; - auto ouput = layout( + if (c.style->display == Display::Internal::TABLE_BOX) { + childInput.capmin = computeCAPMIN(tree, box, input, inlineSize); + } + + auto output = layout( tree, c, childInput ); - if (c.style->position != Position::ABSOLUTE) { - blockSize += ouput.size.y + margin.bottom; + blockSize += output.size.y + margin.bottom; } - inlineSize = max(inlineSize, ouput.size.x + margin.start + margin.end); + inlineSize = max(inlineSize, output.size.x + margin.horizontal()); } return Output::fromSize({ diff --git a/src/web/vaev-layout/builder.cpp b/src/web/vaev-layout/builder.cpp index 73c2000..918d31a 100644 --- a/src/web/vaev-layout/builder.cpp +++ b/src/web/vaev-layout/builder.cpp @@ -215,16 +215,36 @@ static void _buildTableChildren(Style::Computer &c, Vec> co tableBox.style->display = Display::Internal::TABLE_BOX; + bool captionsOnTop = tableBox.style->table->captionSide == CaptionSide::TOP; + + if (captionsOnTop) { + for (auto &child : children) { + if (auto el = child->is()) { + if (el->tagName == Html::CAPTION) { + _buildNode(c, *el, tableWrapperBox); + } + } + } + } + for (auto &child : children) { if (auto el = child->is()) { - if (el->tagName == Html::CAPTION) { - _buildNode(c, *child, tableWrapperBox); - } else { - _buildNode(c, *child, tableBox); + if (el->tagName != Html::CAPTION) { + _buildNode(c, *el, tableBox); } } } tableWrapperBox.add(std::move(tableBox)); + + if (not captionsOnTop) { + for (auto &child : children) { + if (auto el = child->is()) { + if (el->tagName == Html::CAPTION) { + _buildNode(c, *el, tableWrapperBox); + } + } + } + } } static void _buildTable(Style::Computer &c, Strong style, Markup::Element const &el, Box &parent) { diff --git a/src/web/vaev-layout/layout.cpp b/src/web/vaev-layout/layout.cpp index e231f17..dcb2fb9 100644 --- a/src/web/vaev-layout/layout.cpp +++ b/src/web/vaev-layout/layout.cpp @@ -21,14 +21,15 @@ Output _contentLayout(Tree &tree, Box &box, Input input) { display == Display::FLOW or display == Display::FLOW_ROOT or display == Display::TABLE_CELL or - display == Display::TABLE_CAPTION + display == Display::TABLE_CAPTION or + display == Display::TABLE ) { return blockLayout(tree, box, input); } else if (display == Display::FLEX) { return flexLayout(tree, box, input); } else if (display == Display::GRID) { return gridLayout(tree, box, input); - } else if (display == Display::TABLE) { + } else if (display == Display::TABLE_BOX) { return tableLayout(tree, box, input); } else if (display == Display::INTERNAL) { return Output{}; diff --git a/src/web/vaev-layout/table.cpp b/src/web/vaev-layout/table.cpp index 774e840..248b219 100644 --- a/src/web/vaev-layout/table.cpp +++ b/src/web/vaev-layout/table.cpp @@ -98,8 +98,6 @@ struct TableFormatingContext { Vec rowGroups; Vec colGroups; - Vec captionsIdxs; - Box &wrapperBox; Box &tableBox; // Table forming algorithm @@ -129,9 +127,8 @@ struct TableFormatingContext { InsetsPx boxBorder; Vec2Px spacing; - TableFormatingContext(Tree &tree, Box &tableWrapperBox) - : wrapperBox(tableWrapperBox), - tableBox(findTableBox(tableWrapperBox)), + TableFormatingContext(Tree &tree, Box &tableBox) + : tableBox(tableBox), boxBorder(computeBorders(tree, tableBox)), spacing( { @@ -139,13 +136,6 @@ struct TableFormatingContext { resolve(tree, tableBox, tableBox.style->table->spacing.vertical), } ) { - - for (usize i = 0; i < tableWrapperBox.children().len(); ++i) { - auto &child = tableWrapperBox.children()[i]; - if (child.style->display == Display::Internal::TABLE_CAPTION) { - captionsIdxs.pushBack(i); - } - } } // https://html.spec.whatwg.org/multipage/tables.html#algorithm-for-growing-downward-growing-cells @@ -393,14 +383,6 @@ struct TableFormatingContext { numOfFooterRows = grid.size.y - ystartFooterRows; } - Box &findTableBox(Box &tableWrapperBox) { - for (auto &child : tableWrapperBox.children()) - if (child.style->display != Display::Internal::TABLE_CAPTION) - return child; - - panic("table box not found in box tree"); - } - void buildBordersGrid(Tree &tree) { bordersGrid.borders.clear(); bordersGrid.borders.resize(grid.size.x * grid.size.y); @@ -478,11 +460,12 @@ struct TableFormatingContext { tableUsedWidth = tableBox.style->sizing->width == Size::AUTO ? 0_px - : resolve(tree, tableBox, tableBox.style->sizing->width.value, input.availableSpace.x); + : resolve(tree, tableBox, tableBox.style->sizing->width.value, input.availableSpace.x) - + boxBorder.horizontal(); // NOTE: maybe remove this after borderbox param is clearer auto [columnBorders, sumBorders] = getColumnBorders(); - Px fixedWidthToAccount = boxBorder.horizontal() + Px{grid.size.x + 1} * spacing.x; + Px fixedWidthToAccount = Px{grid.size.x + 1} * spacing.x; Vec> colWidthOrNone{}; colWidthOrNone.resize(grid.size.x); @@ -566,19 +549,6 @@ struct TableFormatingContext { // https://www.w3.org/TR/css-tables-3/#intrinsic-percentage-width-of-a-column-based-on-cells-of-span-up-to-1 // We will need a way to retrieve the percentage value, which is also not yet implemented. - Px capmin{0}; - for (auto i : captionsIdxs) { - auto captionOutput = layout( - tree, - wrapperBox.children()[i], - Input{ - .commit = Commit::NO, - .intrinsic = IntrinsicSize::MIN_CONTENT, - } - ); - capmin = max(capmin, captionOutput.size.x); - } - auto getCellMinMaxWidth = [](Tree &tree, Box &box, Input &input, TableCell &cell) -> Pair { auto cellMinOutput = layout( tree, @@ -724,6 +694,7 @@ struct TableFormatingContext { for (auto x : maxColWidth) sumMaxColWidths += x; + Px capmin = input.capmin.unwrap(); // TODO: should minColWidth or maxColWidth be forcelly used if input is MIN_CONTENT or MAX_CONTENT respectivelly? if (tableBox.style->sizing->width != Size::AUTO) { // TODO: how to resolve percentage if case of table width? @@ -882,22 +853,8 @@ struct TableFormatingContext { PrefixSum colWidthPref{colWidth}, rowHeightPref{rowHeight}; Px currPositionX{input.position.x}; - // table box - layout( - tree, - tableBox, - { - .commit = Commit::YES, - .knownSize = { - tableBoxSize.x + boxBorder.horizontal(), - tableBoxSize.y + boxBorder.vertical(), - }, - .position = {currPositionX, currPositionY}, - } - ); - - currPositionX += boxBorder.start + spacing.x; - currPositionY += boxBorder.top + spacing.y; + currPositionX += spacing.x; + currPositionY += spacing.y; // cells for (usize i = 0; i < grid.size.y; currPositionY += rowHeight[i] + spacing.y, i++) { Px innnerCurrPositionX = Px{currPositionX}; @@ -933,48 +890,24 @@ struct TableFormatingContext { } } - void runCaptions(Tree &tree, Input input, Px tableUsedWidth, Px &currPositionY, Px &captionsHeight) { - for (auto i : captionsIdxs) { - auto cellOutput = layout( - tree, - wrapperBox.children()[i], - { - .commit = input.commit, - .knownSize = {tableUsedWidth, NONE}, - .position = {input.position.x, currPositionY}, - } - ); - captionsHeight += cellOutput.size.y; - currPositionY += captionsHeight; - } - } - Output run(Tree &tree, Input input) { - Px currPositionY{input.position.y}, captionsHeight{0}; - if (tableBox.style->table->captionSide == CaptionSide::TOP) { - runCaptions(tree, input, tableUsedWidth, currPositionY, captionsHeight); - } - + Px currPositionY{input.position.y}; if (input.commit == Commit::YES) { runTableBox(tree, input, currPositionY); } - if (tableBox.style->table->captionSide == CaptionSide::BOTTOM) { - runCaptions(tree, input, tableUsedWidth, currPositionY, captionsHeight); - } - return Output::fromSize({ - tableUsedWidth + boxBorder.horizontal(), - tableBoxSize.y + captionsHeight + boxBorder.vertical(), + tableUsedWidth, + tableBoxSize.y, }); } }; -Output tableLayout(Tree &tree, Box &wrapper, Input input) { +Output tableLayout(Tree &tree, Box &box, Input input) { // TODO: - vertical and horizontal alignment // - borders collapse - TableFormatingContext table(tree, wrapper); + TableFormatingContext table(tree, box); table.build(tree, input); return table.run(tree, input); } diff --git a/tests/css/display-table.xhtml b/tests/css/display-table.xhtml index 7e35573..4b696ff 100644 --- a/tests/css/display-table.xhtml +++ b/tests/css/display-table.xhtml @@ -1702,3 +1702,142 @@ + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + +
+
+
+ +
+ +
+ + + + + + + + + + +
+
+
+ +
+ +
+
+