From ce676c7c83d28d051ce7035be95650babb05dfcc Mon Sep 17 00:00:00 2001 From: Paulo Medeiros Date: Wed, 6 Aug 2025 09:51:07 +0200 Subject: [PATCH 1/3] vaev-engine: Fix by passing containing block in table layout, allowing correct padding resolution. --- src/vaev-engine/layout/table.cpp | 6 +- tests/css/display-table.xhtml | 95 ++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/vaev-engine/layout/table.cpp b/src/vaev-engine/layout/table.cpp index adae447c..e8f467ac 100644 --- a/src/vaev-engine/layout/table.cpp +++ b/src/vaev-engine/layout/table.cpp @@ -546,14 +546,14 @@ struct TableFormatingContext : FormatingContext { tree, box, IntrinsicSize::MIN_CONTENT, - {} // FIXME + {tableComputedWidth, 0_au} ); auto cellMaxOutput = computeIntrinsicSize( tree, box, IntrinsicSize::MAX_CONTENT, - {} // FIXME + {tableComputedWidth, 0_au} ); auto cellMinWidth = cellMinOutput.x; @@ -840,6 +840,7 @@ struct TableFormatingContext : FormatingContext { { .intrinsic = IntrinsicSize::MIN_CONTENT, .knownSize = {colWidth[j], NONE}, + .containingBlock = {tableUsedWidth, 0_au}, } ); @@ -1008,6 +1009,7 @@ struct TableFormatingContext : FormatingContext { verticalSize, }, .position = {currPositionX, startPositionY}, + .containingBlock = tableBoxSize, .breakpointTraverser = breakpointsForCell, .pendingVerticalSizes = input.pendingVerticalSizes, } diff --git a/tests/css/display-table.xhtml b/tests/css/display-table.xhtml index d99cdbe1..9f912328 100644 --- a/tests/css/display-table.xhtml +++ b/tests/css/display-table.xhtml @@ -1885,3 +1885,98 @@ + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + +
+
+
+
+
+ + + + + + + +
+
+
+
+
+
+
From 67de6580f4e5d10c695b6d52953749b84d872f09 Mon Sep 17 00:00:00 2001 From: Paulo Medeiros Date: Tue, 5 Aug 2025 16:03:56 +0200 Subject: [PATCH 2/3] vaev-engine: Expose padding, radii, size computation API as public. --- src/vaev-engine/layout/layout-impl.cpp | 16 ++++++++-------- src/vaev-engine/layout/layout.cpp | 6 ++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/vaev-engine/layout/layout-impl.cpp b/src/vaev-engine/layout/layout-impl.cpp index c5a3fb04..67c51680 100644 --- a/src/vaev-engine/layout/layout-impl.cpp +++ b/src/vaev-engine/layout/layout-impl.cpp @@ -89,7 +89,7 @@ InsetsAu computeBorders(Tree& tree, Box& box) { return res; } -static InsetsAu _computePaddings(Tree& tree, Box& box, Vec2Au containingBlock) { +InsetsAu computePaddings(Tree& tree, Box& box, Vec2Au containingBlock) { InsetsAu res; auto padding = box.style->padding; @@ -101,7 +101,7 @@ static InsetsAu _computePaddings(Tree& tree, Box& box, Vec2Au containingBlock) { return res; } -static Math::Radii _computeRadii(Tree& tree, Box& box, Vec2Au size) { +Math::Radii computeRadii(Tree& tree, Box& box, Vec2Au size) { auto radii = box.style->borders->radii; Math::Radii res; @@ -123,7 +123,7 @@ Vec2Au computeIntrinsicSize(Tree& tree, Box& box, IntrinsicSize intrinsic, Vec2A } auto borders = computeBorders(tree, box); - auto padding = _computePaddings(tree, box, containingBlock); + auto padding = computePaddings(tree, box, containingBlock); auto output = _contentLayout( tree, @@ -138,7 +138,7 @@ Vec2Au computeIntrinsicSize(Tree& tree, Box& box, IntrinsicSize intrinsic, Vec2A return output.size + padding.all() + borders.all(); } -static Opt _computeSpecifiedSize(Tree& tree, Box& box, Size size, Vec2Au containingBlock, bool isWidth) { +Opt computeSpecifiedSize(Tree& tree, Box& box, Size size, Vec2Au containingBlock, bool isWidth) { if (size.is()) { auto intrinsicSize = computeIntrinsicSize(tree, box, IntrinsicSize::MIN_CONTENT, containingBlock); return isWidth ? Opt{intrinsicSize.x} : Opt{NONE}; @@ -202,12 +202,12 @@ void fillKnownSizeWithSpecifiedSizeIfEmpty(Tree& tree, Box& box, Input& input) { auto sizing = box.style->sizing; if (input.knownSize.width == NONE) { - auto specifiedWidth = _computeSpecifiedSize(tree, box, sizing->width, input.containingBlock, true); + auto specifiedWidth = computeSpecifiedSize(tree, box, sizing->width, input.containingBlock, true); input.knownSize.width = specifiedWidth; } if (input.knownSize.height == NONE) { - auto specifiedHeight = _computeSpecifiedSize(tree, box, sizing->height, input.containingBlock, false); + auto specifiedHeight = computeSpecifiedSize(tree, box, sizing->height, input.containingBlock, false); input.knownSize.height = specifiedHeight; } } @@ -218,7 +218,7 @@ Output layout(Tree& tree, Box& box, Input input) { fillKnownSizeWithSpecifiedSizeIfEmpty(tree, box, input); auto borders = computeBorders(tree, box); - auto padding = _computePaddings(tree, box, input.containingBlock); + auto padding = computePaddings(tree, box, input.containingBlock); input.knownSize.width = input.knownSize.width.map([&](auto s) { return max(0_au, s - padding.horizontal() - borders.horizontal()); @@ -310,7 +310,7 @@ Output layout(Tree& tree, Box& box, Input input) { currFrag.metrics.borderSize = size; currFrag.metrics.padding = padding; currFrag.metrics.borders = borders; - currFrag.metrics.radii = _computeRadii(tree, box, size); + currFrag.metrics.radii = computeRadii(tree, box, size); currFrag.metrics.outlineOffset = resolve(tree, box, box.style->outline->offset); currFrag.metrics.outlineWidth = resolve(tree, box, box.style->outline->width); diff --git a/src/vaev-engine/layout/layout.cpp b/src/vaev-engine/layout/layout.cpp index f0ecb8b9..a4243b09 100644 --- a/src/vaev-engine/layout/layout.cpp +++ b/src/vaev-engine/layout/layout.cpp @@ -12,6 +12,12 @@ export InsetsAu computeMargins(Tree& tree, Box& box, Input input); export InsetsAu computeBorders(Tree& tree, Box& box); +export InsetsAu computePaddings(Tree& tree, Box& box, Vec2Au containingBlock); + +export Math::Radii computeRadii(Tree& tree, Box& box, Vec2Au size); + +export Opt computeSpecifiedSize(Tree& tree, Box& box, Size size, Vec2Au containingBlock, bool isWidth); + export Vec2Au computeIntrinsicSize(Tree& tree, Box& box, IntrinsicSize intrinsic, Vec2Au containingBlock); export Output layout(Tree& tree, Box& box, Input input); From cd700dd061370eefb6b0d3f5f814b1197705c084 Mon Sep 17 00:00:00 2001 From: Paulo Medeiros Date: Wed, 6 Aug 2025 10:26:39 +0200 Subject: [PATCH 3/3] vaev-engine: Refactor layout interface, shifting sizing and insets computation to parent. --- src/vaev-engine/driver/print.cpp | 8 +- src/vaev-engine/driver/render.cpp | 2 +- src/vaev-engine/layout/block.cpp | 72 ++-- src/vaev-engine/layout/flex.cpp | 55 +-- src/vaev-engine/layout/inline.cpp | 65 ++-- src/vaev-engine/layout/layout-impl.cpp | 233 ++++++++----- src/vaev-engine/layout/layout.cpp | 31 +- src/vaev-engine/layout/replaced.cpp | 8 +- src/vaev-engine/layout/table.cpp | 73 ++-- tests/css/display-block.xhtml | 442 +++++++++++++++---------- tests/css/display-flex.xhtml | 80 +++++ 11 files changed, 683 insertions(+), 386 deletions(-) diff --git a/src/vaev-engine/driver/print.cpp b/src/vaev-engine/driver/print.cpp index 985afdcb..d887cdb4 100644 --- a/src/vaev-engine/driver/print.cpp +++ b/src/vaev-engine/driver/print.cpp @@ -27,7 +27,7 @@ void _paintCornerMargin(Style::PageSpecifiedValues& pageStyle, Scene::Stack& sta .root = Layout::buildForPseudoElement(pageStyle.area(area)), .viewport = Layout::Viewport{.small = rect.size()} }; - auto [_, frag] = Layout::layoutCreateFragment( + auto [_, frag] = Layout::layoutAndCommitRoot( tree, { .knownSize = rect.size().cast>(), @@ -48,7 +48,7 @@ void _paintMainMargin(Style::PageSpecifiedValues& pageStyle, Scene::Stack& stack .root = std::move(box), .viewport = Layout::Viewport{.small = rect.size()} }; - auto [_, frag] = Layout::layoutCreateFragment( + auto [_, frag] = Layout::layoutAndCommitRoot( tree, { .knownSize = rect.size().cast>(), @@ -212,7 +212,7 @@ export Generator print(Gc::Ref dom, Print::Settings }; contentTree.fc.enterDiscovery(); - auto outDiscovery = Layout::layout( + auto outDiscovery = Layout::layoutRoot( contentTree, pageLayoutInput.withBreakpointTraverser(Layout::BreakpointTraverser(&prevBreakpoint)) ); @@ -223,7 +223,7 @@ export Generator print(Gc::Ref dom, Print::Settings : outDiscovery.breakpoint.unwrap(); contentTree.fc.leaveDiscovery(); - auto [outFragmentation, fragment] = Layout::layoutCreateFragment( + auto [outFragmentation, fragment] = Layout::layoutAndCommitRoot( contentTree, pageLayoutInput .withBreakpointTraverser(Layout::BreakpointTraverser(&prevBreakpoint, &currBreakpoint)) diff --git a/src/vaev-engine/driver/render.cpp b/src/vaev-engine/driver/render.cpp index 64fdedf3..5fc5326c 100644 --- a/src/vaev-engine/driver/render.cpp +++ b/src/vaev-engine/driver/render.cpp @@ -36,7 +36,7 @@ export RenderResult render(Gc::Ref dom, Style::Media const& media auto canvasColor = fixupBackgrounds(computer, dom, tree); - auto [outDiscovery, root] = Layout::layoutCreateFragment( + auto [outDiscovery, root] = Layout::layoutAndCommitRoot( tree, { .knownSize = {viewport.small.width, NONE}, diff --git a/src/vaev-engine/layout/block.cpp b/src/vaev-engine/layout/block.cpp index cae9015c..400cbce1 100644 --- a/src/vaev-engine/layout/block.cpp +++ b/src/vaev-engine/layout/block.cpp @@ -1,7 +1,7 @@ module; -#include #include +#include export module Vaev.Engine:layout.block; @@ -122,6 +122,31 @@ Output fragmentEmptyBox(Tree& tree, Input input) { } } +void _populateChildSpecifiedSizes(Tree& tree, Box& child, Input& childInput, Au horizontalMargins, Opt blockInlineSize) { + if (childInput.intrinsic == IntrinsicSize::AUTO or child.style->display != Display::INLINE) { + if (child.style->sizing->width.is()) { + // https://www.w3.org/TR/css-tables-3/#layout-principles + // Unlike other block-level boxes, tables do not fill their containing block by default. + // When their width computes to auto, they behave as if they had fit-content specified instead. + // This is different from most block-level boxes, which behave as if they had stretch instead. + if (child.style->display == Display::TABLE_BOX) { + // Do nothing. 'fit-content' is kinda intrinsic size, when we don't populate knownSize. + } else if (blockInlineSize) { + // When the inline size is not known, we cannot enforce it to the child. (?) + childInput.knownSize.width = blockInlineSize.unwrap() - horizontalMargins; + } + } else { + childInput.knownSize.width = computeSpecifiedWidth( + tree, child, child.style->sizing->width, childInput.containingBlock + ); + } + + childInput.knownSize.height = computeSpecifiedHeight( + tree, child, child.style->sizing->height, childInput.containingBlock + ); + } +} + // https://www.w3.org/TR/CSS22/visuren.html#normal-flow struct BlockFormatingContext : FormatingContext { Au _computeCapmin(Tree& tree, Box& box, Input input, Au inlineSize) { @@ -157,11 +182,6 @@ struct BlockFormatingContext : FormatingContext { return fragmentEmptyBox(tree, input); } - // NOTE: Our parent has no clue about our width but wants us to commit, - // we need to compute it first - if (input.fragment and not input.knownSize.width) - inlineSize = run(tree, box, input.withFragment(nullptr), startAt, stopAt).width(); - Breakpoint currentBreakpoint; BaselinePositionsSet firstBaselineSet, lastBaselineSet; @@ -187,7 +207,6 @@ struct BlockFormatingContext : FormatingContext { // continue; Input childInput = { - .fragment = input.fragment, .intrinsic = input.intrinsic, .availableSpace = {input.availableSpace.x, 0_au}, .containingBlock = {inlineSize, input.knownSize.y.unwrapOr(0_au)}, @@ -195,21 +214,18 @@ struct BlockFormatingContext : FormatingContext { .pendingVerticalSizes = input.pendingVerticalSizes, }; - auto margin = computeMargins(tree, c, childInput); - - Opt childInlineSize = NONE; - if (c.style->sizing->width.is()) { - childInlineSize = inlineSize - margin.horizontal(); - } + UsedSpacings usedSpacings{ + .padding = computePaddings(tree, c, childInput.containingBlock), + .borders = computeBorders(tree, c), + .margin = computeMargins(tree, c, childInput) + }; if (not impliesRemovingFromFlow(c.style->position)) { // TODO: collapsed margins for sibling elements - blockSize += max(margin.top, lastMarginBottom) - lastMarginBottom; - if (input.fragment or input.knownSize.x) - childInput.knownSize.width = childInlineSize; + blockSize += max(usedSpacings.margin.top, lastMarginBottom) - lastMarginBottom; } - childInput.position = input.position + Vec2Au{margin.start, blockSize}; + childInput.position = input.position + Vec2Au{usedSpacings.margin.start, blockSize}; // HACK: Table Box mostly behaves like a block box, let's compute its capmin // and avoid duplicating the layout code @@ -217,14 +233,15 @@ struct BlockFormatingContext : FormatingContext { childInput.capmin = _computeCapmin(tree, box, input, inlineSize); } - auto output = layout( - tree, - c, - childInput - ); + _populateChildSpecifiedSizes(tree, c, childInput, usedSpacings.margin.horizontal(), input.knownSize.x); + + auto output = input.fragment + ? layoutAndCommitBorderBox(tree, c, childInput, *input.fragment, usedSpacings) + : layoutBorderBox(tree, c, childInput, usedSpacings); + if (not impliesRemovingFromFlow(c.style->position)) { - blockSize += output.size.y + margin.bottom; - lastMarginBottom = margin.bottom; + blockSize += output.size.y + usedSpacings.margin.bottom; + lastMarginBottom = usedSpacings.margin.bottom; } maybeProcessChildBreakpoint( @@ -252,11 +269,14 @@ struct BlockFormatingContext : FormatingContext { blockWasCompletelyLaidOut = output.completelyLaidOut and i + 1 == box.children().len(); } - inlineSize = max(inlineSize, output.size.x + margin.horizontal()); + inlineSize = max(inlineSize, output.size.x + usedSpacings.margin.horizontal()); } return { - .size = Vec2Au{inlineSize, blockSize}, + .size = Vec2Au{ + input.knownSize.x.unwrapOr(inlineSize), + input.knownSize.y.unwrapOr(blockSize) + }, .completelyLaidOut = blockWasCompletelyLaidOut, .breakpoint = currentBreakpoint, .firstBaselineSet = firstBaselineSet, diff --git a/src/vaev-engine/layout/flex.cpp b/src/vaev-engine/layout/flex.cpp index 9cca85b2..84d018ad 100644 --- a/src/vaev-engine/layout/flex.cpp +++ b/src/vaev-engine/layout/flex.cpp @@ -134,7 +134,8 @@ struct FlexItem { FlexProps flexItemProps; FlexAxis fa; - // these 2 sizes do NOT account margins + Math::Insets borders; + Math::Insets padding; Vec2Au usedSize; Math::Insets> margin; @@ -152,7 +153,8 @@ struct FlexItem { // InsetsAu borders; FlexItem(Tree& tree, Box& box, bool isRowOriented, Vec2Au containingBlock) - : box(&box), flexItemProps(*box.style->flex), fa(isRowOriented) { + : box(&box), flexItemProps(*box.style->flex), fa(isRowOriented), + borders(computeBorders(tree, box)), padding(computePaddings(tree, box, containingBlock)) { // FIXME: check if really needed speculateValues(tree, Input{.containingBlock = containingBlock}); // TODO: not always we will need min/max content sizes, @@ -160,11 +162,13 @@ struct FlexItem { computeContentSizes(tree, containingBlock); } - void commit(MutCursor frag) { - frag->metrics.margin.top = margin.top.unwrapOr(speculativeMargin.top); - frag->metrics.margin.start = margin.start.unwrapOr(speculativeMargin.start); - frag->metrics.margin.end = margin.end.unwrapOr(speculativeMargin.end); - frag->metrics.margin.bottom = margin.bottom.unwrapOr(speculativeMargin.bottom); + InsetsAu resolvedMargin() { + return { + margin.top.unwrapOr(speculativeMargin.top), + margin.end.unwrapOr(speculativeMargin.end), + margin.bottom.unwrapOr(speculativeMargin.bottom), + margin.start.unwrapOr(speculativeMargin.start), + }; } void computeContentSizes(Tree& tree, Vec2Au containingBlock) { @@ -227,12 +231,23 @@ struct FlexItem { } void speculateValues(Tree& t, Input input) { - speculativeSize = layout(t, *box, input).size; speculativeMargin = computeMargins( t, *box, input ); + + if (not input.knownSize.width) + input.knownSize.width = computeSpecifiedWidth( + t, *box, box->style->sizing->width, input.containingBlock + ); + + if (not input.knownSize.height) + input.knownSize.height = computeSpecifiedHeight( + t, *box, box->style->sizing->height, input.containingBlock + ); + + speculativeSize = layoutBorderBox(t, *box, input, UsedSpacings{.padding = padding, .borders = borders}).size; } // https://www.w3.org/TR/css-flexbox-1/#valdef-flex-basis-auto @@ -1423,17 +1438,19 @@ struct FlexFormatingContext : FormatingContext { for (auto& flexItem : flexLine.items) { flexItem.position = flexItem.position + flexLine.position + input.position; - auto out = layout( - tree, - *flexItem.box, - { - .fragment = input.fragment, - .knownSize = {flexItem.usedSize.x, flexItem.usedSize.y}, - .position = flexItem.position, - .availableSpace = availableSpace, - } - ); - flexItem.commit(input.fragment); + UsedSpacings usedSpacings{ + .padding = std::move(flexItem.padding), + .borders = std::move(flexItem.borders), + .margin = flexItem.resolvedMargin(), + }; + + Input childInput{ + .knownSize = {flexItem.usedSize.x, flexItem.usedSize.y}, + .position = flexItem.position, + .availableSpace = availableSpace, + }; + + layoutAndCommitBorderBox(tree, *flexItem.box, childInput, *input.fragment, usedSpacings); } } } diff --git a/src/vaev-engine/layout/inline.cpp b/src/vaev-engine/layout/inline.cpp index 314f645f..80ce1ba9 100644 --- a/src/vaev-engine/layout/inline.cpp +++ b/src/vaev-engine/layout/inline.cpp @@ -56,18 +56,31 @@ struct InlineFormatingContext : FormatingContext { auto& atomicBox = *inlineBox.atomicBoxes[boxStrutCell.id]; + Input childInput{ + .availableSpace = {inlineSize, input.availableSpace.y}, + .containingBlock = { + input.knownSize.x.unwrapOr(0_au), + input.knownSize.y.unwrapOr(0_au) + }, + }; + + childInput.knownSize.width = computeSpecifiedWidth( + tree, atomicBox, atomicBox.style->sizing->width, childInput.containingBlock + ); + + childInput.knownSize.height = computeSpecifiedHeight( + tree, atomicBox, atomicBox.style->sizing->height, childInput.containingBlock + ); + // NOTE: We set the same availableSpace to child inline boxes since line wrapping is possible i.e. in the // worst case, they will take up the whole availableSpace, and a line break will be done right before them - auto atomicBoxOutput = layout( + auto atomicBoxOutput = layoutBorderBox( tree, atomicBox, - Input{ - .knownSize = {NONE, NONE}, - .availableSpace = {inlineSize, input.availableSpace.y}, - .containingBlock = { - input.knownSize.x.unwrapOr(0_au), - input.knownSize.y.unwrapOr(0_au) - }, + childInput, + UsedSpacings{ + .padding = computePaddings(tree, atomicBox, childInput.containingBlock), + .borders = computeBorders(tree, atomicBox), } ); @@ -100,19 +113,24 @@ struct InlineFormatingContext : FormatingContext { }; } - layout( - tree, - atomicBox, - Input{ - .fragment = input.fragment, - .knownSize = knownSize, - .position = input.position + positionInProse, - .containingBlock = { - input.knownSize.x.unwrapOr(0_au), - input.knownSize.y.unwrapOr(0_au) - }, - } - ); + Input childInput{ + .knownSize = knownSize, + .position = input.position + positionInProse, + .containingBlock = { + input.knownSize.x.unwrapOr(0_au), + input.knownSize.y.unwrapOr(0_au) + }, + }; + + UsedSpacings usedSpacings{ + .padding = computePaddings(tree, atomicBox, childInput.containingBlock), + .borders = computeBorders(tree, atomicBox), + }; + + if (input.fragment) + layoutAndCommitBorderBox(tree, atomicBox, childInput, *input.fragment, usedSpacings); + else + layoutBorderBox(tree, atomicBox, childInput, usedSpacings); } if (tree.fc.allowBreak() and not tree.fc.acceptsFit( @@ -128,7 +146,10 @@ struct InlineFormatingContext : FormatingContext { } return { - .size = size, + .size = { + input.knownSize.x.unwrapOr(size.x), + input.knownSize.y.unwrapOr(size.y), + }, .completelyLaidOut = true, .firstBaselineSet = firstBaselineSet, .lastBaselineSet = lastBaselineSet, diff --git a/src/vaev-engine/layout/layout-impl.cpp b/src/vaev-engine/layout/layout-impl.cpp index 67c51680..7c22d1fd 100644 --- a/src/vaev-engine/layout/layout-impl.cpp +++ b/src/vaev-engine/layout/layout-impl.cpp @@ -71,6 +71,11 @@ InsetsAu computeMargins(Tree& tree, Box& box, Input input) { } InsetsAu computeBorders(Tree& tree, Box& box) { + // NOTE: In borders collapse mode, we assume that the table box borders are 'transfered' to the cells + if (box.style->display == Display::TABLE_BOX and box.style->table->collapse == BorderCollapse::COLLAPSE) { + return InsetsAu{}; + } + InsetsAu res; auto borders = box.style->borders; @@ -138,26 +143,45 @@ Vec2Au computeIntrinsicSize(Tree& tree, Box& box, IntrinsicSize intrinsic, Vec2A return output.size + padding.all() + borders.all(); } -Opt computeSpecifiedSize(Tree& tree, Box& box, Size size, Vec2Au containingBlock, bool isWidth) { +Opt computeSpecifiedWidth(Tree& tree, Box& box, Size size, Vec2Au containingBlock) { if (size.is()) { auto intrinsicSize = computeIntrinsicSize(tree, box, IntrinsicSize::MIN_CONTENT, containingBlock); - return isWidth ? Opt{intrinsicSize.x} : Opt{NONE}; + return intrinsicSize.x; } else if (size.is()) { auto intrinsicSize = computeIntrinsicSize(tree, box, IntrinsicSize::MAX_CONTENT, containingBlock); - return isWidth ? Opt{intrinsicSize.x} : Opt{NONE}; + return intrinsicSize.x; } else if (size.is()) { auto minIntrinsicSize = computeIntrinsicSize(tree, box, IntrinsicSize::MIN_CONTENT, containingBlock); auto maxIntrinsicSize = computeIntrinsicSize(tree, box, IntrinsicSize::MAX_CONTENT, containingBlock); auto stretchIntrinsicSize = computeIntrinsicSize(tree, box, IntrinsicSize::STRETCH_TO_FIT, containingBlock); - if (isWidth) - return clamp(stretchIntrinsicSize.x, minIntrinsicSize.x, maxIntrinsicSize.x); - else - return clamp(stretchIntrinsicSize.y, minIntrinsicSize.y, maxIntrinsicSize.y); + return clamp(stretchIntrinsicSize.x, minIntrinsicSize.x, maxIntrinsicSize.x); + } else if (size.is()) { + return NONE; + } else if (auto calc = size.is>>()) { + return resolve(tree, box, *calc, containingBlock.x); + } else { + logWarn("unknown specified size: {}", size); + return 0_au; + } +} + +Opt computeSpecifiedHeight(Tree& tree, Box& box, Size size, Vec2Au containingBlock) { + if (size.is()) { + // https://drafts.csswg.org/css-sizing-3/#valdef-width-min-content + // for a box’s block size, unless otherwise specified, this is equivalent to its automatic size. + return NONE; + } else if (size.is()) { + // https://drafts.csswg.org/css-sizing-3/#valdef-width-max-content + // for a box’s block size, unless otherwise specified, this is equivalent to its automatic size. + return NONE; + } else if (size.is()) { + // Since this depends on min/max content size, this is also unknown. + return NONE; } else if (size.is()) { return NONE; } else if (auto calc = size.is>>()) { - return resolve(tree, box, *calc, isWidth ? containingBlock.x : containingBlock.y); + return resolve(tree, box, *calc, containingBlock.y); } else { logWarn("unknown specified size: {}", size); return 0_au; @@ -198,42 +222,7 @@ static void _maybeSetMonolithicBreakpoint(Fragmentainer& fc, bool isMonolticDisp outputBreakpoint = bottomOfContentBreakForTopMonolitic; } -void fillKnownSizeWithSpecifiedSizeIfEmpty(Tree& tree, Box& box, Input& input) { - auto sizing = box.style->sizing; - - if (input.knownSize.width == NONE) { - auto specifiedWidth = computeSpecifiedSize(tree, box, sizing->width, input.containingBlock, true); - input.knownSize.width = specifiedWidth; - } - - if (input.knownSize.height == NONE) { - auto specifiedHeight = computeSpecifiedSize(tree, box, sizing->height, input.containingBlock, false); - input.knownSize.height = specifiedHeight; - } -} - -Output layout(Tree& tree, Box& box, Input input) { - // FIXME: confirm how the preferred width/height parameters interacts with intrinsic size argument from input - - fillKnownSizeWithSpecifiedSizeIfEmpty(tree, box, input); - - auto borders = computeBorders(tree, box); - auto padding = computePaddings(tree, box, input.containingBlock); - - input.knownSize.width = input.knownSize.width.map([&](auto s) { - return max(0_au, s - padding.horizontal() - borders.horizontal()); - }); - - input.knownSize.height = input.knownSize.height.map([&](auto s) { - return max(0_au, s - padding.vertical() - borders.vertical()); - }); - - input.availableSpace.height = max(0_au, input.availableSpace.height - padding.vertical() - borders.vertical()); - input.availableSpace.width = max(0_au, input.availableSpace.width - padding.horizontal() - borders.horizontal()); - - input.position = input.position + borders.topStart() + padding.topStart(); - input.pendingVerticalSizes += borders.bottom + padding.bottom; - +Output layoutContentBox(Tree& tree, Box& box, Input input) { bool isMonolithicDisplay = box.style->display == Display::Inside::FLEX or box.style->display == Display::Inside::GRID; @@ -254,14 +243,6 @@ Output layout(Tree& tree, Box& box, Input input) { if (not out.completelyLaidOut and out.breakpoint == NONE) panic("if it was not completely laid out, there should be a breakpoint"); - auto size = out.size; - size.width = input.knownSize.width.unwrapOr(size.width); - if (out.completelyLaidOut and not input.breakpointTraverser.prevIteration) { - size.height = input.knownSize.height.unwrapOr(size.height); - } - - // TODO: Class C breakpoint - _maybeSetMonolithicBreakpoint( tree.fc, isMonolithicDisplay, @@ -274,70 +255,138 @@ Output layout(Tree& tree, Box& box, Input input) { tree.fc.leaveMonolithicBox(); return { - .size = size + padding.all() + borders.all(), + .size = out.size, .completelyLaidOut = out.completelyLaidOut, .breakpoint = out.breakpoint, - .firstBaselineSet = out.firstBaselineSet.translate(padding.top + borders.top), - .lastBaselineSet = out.lastBaselineSet.translate(padding.top + borders.top), + .firstBaselineSet = out.firstBaselineSet, + .lastBaselineSet = out.lastBaselineSet, }; } else { Opt stopAt = tree.fc.allowBreak() ? input.breakpointTraverser.getEnd() : NONE; - auto parentFrag = input.fragment; - Frag currFrag(&box); - input.fragment = input.fragment ? &currFrag : nullptr; - if (isMonolithicDisplay) tree.fc.enterMonolithicBox(); auto out = _contentLayout(tree, box, input, startAt, stopAt); - auto size = out.size; - size.width = input.knownSize.width.unwrapOr(size.width); - if ((not tree.fc.allowBreak()) or (out.completelyLaidOut and not input.breakpointTraverser.prevIteration)) { - size.height = input.knownSize.height.unwrapOr(size.height); - } - if (isMonolithicDisplay) tree.fc.leaveMonolithicBox(); - size = size + padding.all() + borders.all(); + return { + .size = out.size, + .completelyLaidOut = out.completelyLaidOut, + .firstBaselineSet = out.firstBaselineSet, + .lastBaselineSet = out.lastBaselineSet, + }; + } +} - if (parentFrag) { - currFrag.metrics.position = input.position - borders.topStart() - padding.topStart(); - currFrag.metrics.borderSize = size; - currFrag.metrics.padding = padding; - currFrag.metrics.borders = borders; - currFrag.metrics.radii = computeRadii(tree, box, size); - currFrag.metrics.outlineOffset = resolve(tree, box, box.style->outline->offset); - currFrag.metrics.outlineWidth = resolve(tree, box, box.style->outline->width); +Input _adaptToContentBox(Input input, UsedSpacings const& usedSpacings) { + auto borders = usedSpacings.borders; + auto padding = usedSpacings.padding; - parentFrag->add(std::move(currFrag)); + input.knownSize.x = input.knownSize.x.map( + [&](Au size) { + return size - borders.horizontal() - padding.horizontal(); } + ); - return Output{ - .size = size, - .completelyLaidOut = out.completelyLaidOut, - .firstBaselineSet = out.firstBaselineSet.translate(padding.top + borders.top), - .lastBaselineSet = out.lastBaselineSet.translate(padding.top + borders.top), - }; - } + input.knownSize.y = input.knownSize.y.map( + [&](Au size) { + return size - borders.vertical() - padding.vertical(); + } + ); + input.position = input.position + borders.topStart() + padding.topStart(); + input.pendingVerticalSizes += borders.bottom + padding.bottom; + + return input; +} + +Output layoutBorderBox(Tree& tree, Box& box, Input input, UsedSpacings const& usedSpacings) { + input = _adaptToContentBox(input, usedSpacings); + auto output = layoutContentBox(tree, box, input); + output.size = output.size + usedSpacings.borders.all() + usedSpacings.padding.all(); + return output; +} + +Output layoutAndCommitBorderBox(Tree& tree, Box& box, Input input, Frag& parentFrag, UsedSpacings const& usedSpacings) { + input = _adaptToContentBox(input, usedSpacings); + auto output = layoutAndCommitContentBox(tree, box, input, parentFrag, usedSpacings); + output.size = output.size + usedSpacings.borders.all() + usedSpacings.padding.all(); + return output; +} + +Output layoutAndCommitContentBox(Tree& tree, Box& box, Input input, Frag& parentFrag, UsedSpacings const& usedSpacings) { + Frag currFrag(&box); + + auto output = layoutContentBox(tree, box, input.withFragment(&currFrag)); + + currFrag.metrics = Metrics{ + .padding = usedSpacings.padding, + .borders = usedSpacings.borders, + .outlineOffset = resolve(tree, box, box.style->outline->offset), + .outlineWidth = resolve(tree, box, box.style->outline->width), + .position = input.position - usedSpacings.borders.topStart() - usedSpacings.padding.topStart(), + .borderSize = output.size + usedSpacings.borders.all() + usedSpacings.padding.all(), + .margin = usedSpacings.margin, + .radii = computeRadii(tree, box, output.size + usedSpacings.borders.all() + usedSpacings.padding.all()), + }; + + parentFrag.add(std::move(currFrag)); + + return output; } -Output layout(Tree& tree, Input input) { - auto out = layout(tree, tree.root, input); - if (input.fragment) - layoutPositioned(tree, input.fragment->children()[0], input.containingBlock); - return out; +Output layoutRoot(Tree& tree, Input input) { + if (not input.knownSize.width) + input.knownSize.width = computeSpecifiedWidth( + tree, tree.root, tree.root.style->sizing->width, input.containingBlock + ); + + if (not input.knownSize.height) + input.knownSize.height = computeSpecifiedHeight( + tree, tree.root, tree.root.style->sizing->height, input.containingBlock + ); + + auto output = layoutBorderBox( + tree, tree.root, input, + { + .padding = computePaddings(tree, tree.root, input.containingBlock), + .borders = computeBorders(tree, tree.root), + } + ); + + return output; } -Tuple layoutCreateFragment(Tree& tree, Input input) { - auto root = Layout::Frag(); - input.fragment = &root; - auto out = layout(tree, input); - return {out, std::move(root.children()[0])}; +Tuple layoutAndCommitRoot(Tree& tree, Input input) { + auto parentFragOfRoot = Layout::Frag(); + + if (not input.knownSize.width) + input.knownSize.width = computeSpecifiedWidth( + tree, tree.root, tree.root.style->sizing->width, input.containingBlock + ); + + if (not input.knownSize.height) + input.knownSize.height = computeSpecifiedHeight( + tree, tree.root, tree.root.style->sizing->height, input.containingBlock + ); + + auto out = layoutAndCommitBorderBox( + tree, tree.root, input, parentFragOfRoot, + { + .padding = computePaddings(tree, tree.root, input.containingBlock), + .borders = computeBorders(tree, tree.root), + } + ); + + auto fragOfRoot = std::move(parentFragOfRoot.children()[0]); + + layoutPositioned(tree, fragOfRoot, input.containingBlock); + + return {out, std::move(fragOfRoot)}; } } // namespace Vaev::Layout diff --git a/src/vaev-engine/layout/layout.cpp b/src/vaev-engine/layout/layout.cpp index a4243b09..422657c4 100644 --- a/src/vaev-engine/layout/layout.cpp +++ b/src/vaev-engine/layout/layout.cpp @@ -8,6 +8,17 @@ import :layout.base; namespace Vaev::Layout { +struct UsedSpacings { + InsetsAu padding{}; + InsetsAu borders{}; + InsetsAu margin{}; + + void repr(Io::Emit& e) const { + e("(used spacings paddings: {} borders: {} margin: {})", + padding, borders, margin); + } +}; + export InsetsAu computeMargins(Tree& tree, Box& box, Input input); export InsetsAu computeBorders(Tree& tree, Box& box); @@ -16,16 +27,26 @@ export InsetsAu computePaddings(Tree& tree, Box& box, Vec2Au containingBlock); export Math::Radii computeRadii(Tree& tree, Box& box, Vec2Au size); -export Opt computeSpecifiedSize(Tree& tree, Box& box, Size size, Vec2Au containingBlock, bool isWidth); +export Opt computeSpecifiedWidth(Tree& tree, Box& box, Size size, Vec2Au containingBlock); + +export Opt computeSpecifiedHeight(Tree& tree, Box& box, Size size, Vec2Au containingBlock); export Vec2Au computeIntrinsicSize(Tree& tree, Box& box, IntrinsicSize intrinsic, Vec2Au containingBlock); -export Output layout(Tree& tree, Box& box, Input input); +// MARK: Layout --------------------------------------------------------------------- + +// Main function for laying out a box and its children. +export Output layoutContentBox(Tree& tree, Box& box, Input input); -export Output layout(Tree& tree, Input input); +// Fragment/commit wrapper for content box layout +export Output layoutAndCommitContentBox(Tree& tree, Box& box, Input input, Frag& parentFrag, UsedSpacings const& usedSpacings); -export Tuple layoutCreateFragment(Tree& tree, Input input); +// Border box wrappers for content box layout functions +export Output layoutBorderBox(Tree& tree, Box& box, Input input, UsedSpacings const& usedSpacings); +export Output layoutAndCommitBorderBox(Tree& tree, Box& box, Input input, Frag& parentFrag, UsedSpacings const& usedSpacings); -export void fillKnownSizeWithSpecifiedSizeIfEmpty(Tree& tree, Box& box, Input& input); +// Layout wrappers for root elements +export Output layoutRoot(Tree& tree, Input input); +export Tuple layoutAndCommitRoot(Tree& tree, Input input); } // namespace Vaev::Layout diff --git a/src/vaev-engine/layout/replaced.cpp b/src/vaev-engine/layout/replaced.cpp index 80bacdfe..4aa8ab42 100644 --- a/src/vaev-engine/layout/replaced.cpp +++ b/src/vaev-engine/layout/replaced.cpp @@ -52,14 +52,12 @@ struct ReplacedFormatingContext : FormatingContext { auto resolvedRect = SVG::resolve(SVG::buildRectangle(*(*box)->style), resolveTo); auto frag = Layout::Frag(); - Input input{ - .fragment = &frag, + Input childInput{ .knownSize = {resolvedRect.width, resolvedRect.height}, .position = {resolvedRect.x, resolvedRect.y}, }; - layout(tree, *box, input); - + auto output = layoutAndCommitBorderBox(tree, *box, childInput, frag, UsedSpacings{}); return makeBox(std::move(frag.children()[0])); } unreachable(); @@ -104,8 +102,6 @@ struct ReplacedFormatingContext : FormatingContext { if (auto image = box.content.is()) { size = image->bound().size().cast(); } else if (auto svg = box.content.is()) { - fillKnownSizeWithSpecifiedSizeIfEmpty(tree, box, input); - auto aspectRatio = SVG::intrinsicAspectRatio(box.style->svg->viewBox, box.style->sizing->width, box.style->sizing->height); size = _defaultSizing(input.knownSize, aspectRatio, input.containingBlock); diff --git a/src/vaev-engine/layout/table.cpp b/src/vaev-engine/layout/table.cpp index e8f467ac..6449611c 100644 --- a/src/vaev-engine/layout/table.cpp +++ b/src/vaev-engine/layout/table.cpp @@ -439,7 +439,7 @@ struct TableFormatingContext : FormatingContext { // MARK: Fixed Table Layout ------------------------------------------------------------------------ // https://www.w3.org/TR/CSS22/tables.html#fixed-table-layout - void computeFixedColWidths(Tree& tree, Box& box, Au availableXSpace) { + void computeFixedColWidths(Tree& tree, Box& box, Au knownSizeX) { // NOTE: Percentages for 'width' and 'height' on the table (box) // are calculated relative to the containing block of the // table wrapper box, not the table wrapper box itself. @@ -451,11 +451,7 @@ struct TableFormatingContext : FormatingContext { if (not(box.style->sizing->width.is() or box.style->sizing->width.is>>())) logWarn("width can't be anything other than 'auto' or a length in a table context"); - tableUsedWidth = - not box.style->sizing->width.is>>() - ? 0_au // AUTO case - : resolve(tree, box, box.style->sizing->width.unwrap>>(), availableXSpace) - - boxBorder.horizontal(); // NOTE: maybe remove this after borderbox param is clearer + tableUsedWidth = knownSizeX; auto [columnBorders, sumBorders] = getColumnBorders(); @@ -464,7 +460,6 @@ struct TableFormatingContext : FormatingContext { Vec> colWidthOrNone{}; colWidthOrNone.resize(grid.size.x); for (auto& col : cols) { - auto const& width = col.el.style->sizing->width; if (not(width.is() or width.is>>())) @@ -704,7 +699,7 @@ struct TableFormatingContext : FormatingContext { } // https://www.w3.org/TR/CSS22/tables.html#auto-table-layout - void computeAutoColWidths(Tree& tree, Box& box, Au capmin, Au containingBlockX) { + void computeAutoColWidths(Tree& tree, Opt knownSizeX, Au capmin, Au containingBlockX) { // FIXME: This is a rough approximation of the algorithm. // We need to distinguish between percentage-based and fixed lengths: // - Percentage-based sizes are fixed and cannot have extra space distributed to them. @@ -715,11 +710,10 @@ struct TableFormatingContext : FormatingContext { // 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. - if (auto boxWidthCalc = box.style->sizing->width.is>>()) { + if (knownSizeX) { auto [minWithoutPerc, maxWithoutPerc] = computeMinMaxAutoWidths(tree, grid.size.x, 0_au); - Au tableComputedWidth = resolve(tree, box, *boxWidthCalc, containingBlockX); - tableUsedWidth = max(capmin, tableComputedWidth); + tableUsedWidth = max(capmin, *knownSizeX); auto sumMinWithoutPerc = iter(minWithoutPerc).sum(); if (sumMinWithoutPerc > tableUsedWidth) { @@ -728,7 +722,7 @@ struct TableFormatingContext : FormatingContext { return; } - auto [minWithPerc, maxWithPerc] = computeMinMaxAutoWidths(tree, grid.size.x, tableComputedWidth); + auto [minWithPerc, maxWithPerc] = computeMinMaxAutoWidths(tree, grid.size.x, *knownSizeX); auto sumMaxWithoutPerc = iter(maxWithoutPerc).sum(); Vec& distWOPToUse = sumMaxWithoutPerc < tableUsedWidth ? maxWithoutPerc : minWithoutPerc; @@ -834,14 +828,19 @@ struct TableFormatingContext : FormatingContext { } } - auto cellOutput = layout( + UsedSpacings usedSpacings{ + .padding = computePaddings(tree, *cell.box, {tableUsedWidth, 0_au}), + .borders = computeBorders(tree, *cell.box), + }; + + auto cellOutput = layoutBorderBox( tree, *cell.box, { - .intrinsic = IntrinsicSize::MIN_CONTENT, .knownSize = {colWidth[j], NONE}, .containingBlock = {tableUsedWidth, 0_au}, - } + }, + usedSpacings ); for (usize k = 0; k < rowSpan; k++) { @@ -889,10 +888,12 @@ struct TableFormatingContext : FormatingContext { struct CacheParametersFromInput { Au containingBlockX; Au capmin; + Opt knownSizeX; CacheParametersFromInput(Input const& i) : containingBlockX(i.containingBlock.x), - capmin(i.capmin.unwrap()) {} + capmin(i.capmin.unwrap()), + knownSizeX(i.knownSize.x) {} bool operator==(CacheParametersFromInput const& c) const = default; }; @@ -924,12 +925,12 @@ struct TableFormatingContext : FormatingContext { // However, Chrome does not implement this exception, and we are not implementing it either. bool shouldRunAutoAlgorithm = box.style->table->tableLayout == TableLayout::AUTO or - box.style->sizing->width.is(); + not input.knownSizeX; if (shouldRunAutoAlgorithm) - computeAutoColWidths(tree, box, input.capmin, input.containingBlockX); + computeAutoColWidths(tree, input.knownSizeX, input.capmin, input.containingBlockX); else - computeFixedColWidths(tree, box, input.containingBlockX); + computeFixedColWidths(tree, box, *input.knownSizeX); computeRowHeights(tree); @@ -999,21 +1000,25 @@ struct TableFormatingContext : FormatingContext { // // (See https://www.w3.org/TR/CSS22/tables.html#height-layout) auto colSpan = cell.box->attrs.colSpan; - auto outputCell = layout( - tree, - *cell.box, - { - .fragment = input.fragment, - .knownSize = { - colWidthPref.query(j, j + colSpan - 1) + spacing.x * Au{colSpan - 1}, - verticalSize, - }, - .position = {currPositionX, startPositionY}, - .containingBlock = tableBoxSize, - .breakpointTraverser = breakpointsForCell, - .pendingVerticalSizes = input.pendingVerticalSizes, - } - ); + Input childInput{ + .knownSize = { + colWidthPref.query(j, j + colSpan - 1) + spacing.x * Au{colSpan - 1}, + verticalSize, + }, + .position = {currPositionX, startPositionY}, + .containingBlock = tableBoxSize, + .breakpointTraverser = breakpointsForCell, + .pendingVerticalSizes = input.pendingVerticalSizes, + }; + + UsedSpacings usedSpacings{ + .padding = computePaddings(tree, *cell.box, tableBoxSize), + .borders = computeBorders(tree, *cell.box), + }; + + auto outputCell = input.fragment + ? layoutAndCommitBorderBox(tree, *cell.box, childInput, *input.fragment, usedSpacings) + : layoutBorderBox(tree, *cell.box, childInput, usedSpacings); if (tree.fc.isDiscoveryMode()) { if (cellBox->style->break_->inside == BreakInside::AVOID) { diff --git a/tests/css/display-block.xhtml b/tests/css/display-block.xhtml index 7b1a9ff1..74044d63 100644 --- a/tests/css/display-block.xhtml +++ b/tests/css/display-block.xhtml @@ -29,7 +29,7 @@ - + @@ -114,185 +114,185 @@ - - - - - - - - - - - - - - - -
-
-
-
-
- - -
-
-
-
-
- - -
-
-
-
-
- - -
-
-
-
-
- - -
-
-
-
-
- - -
-
-
-
-
- - -
-
-
-
-
- - -
-
-
-
-
- - -
-
-
-
-
- - -
-
-
-
-
- - -
-
-
-
-
- - -
-
-
-
-
+ + + + + + + + + + + + + + + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
- - - - - - - - - - - - - - - -
-
-
-
-
- - -
-
-
-
-
+ + + + + + + + + + + + + + + +
+
+
+
+
+ + +
+
+
+
+
@@ -338,3 +338,91 @@ + + + + + + + + + + + + + + + + + +
+
+ + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ + +
diff --git a/tests/css/display-flex.xhtml b/tests/css/display-flex.xhtml index 3347aeeb..921c433a 100644 --- a/tests/css/display-flex.xhtml +++ b/tests/css/display-flex.xhtml @@ -2404,3 +2404,83 @@ + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ + +