Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/vaev-engine/driver/print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Opt<Au>>(),
Expand All @@ -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<Opt<Au>>(),
Expand Down Expand Up @@ -212,7 +212,7 @@ export Generator<Print::Page> print(Gc::Ref<Dom::Document> dom, Print::Settings
};

contentTree.fc.enterDiscovery();
auto outDiscovery = Layout::layout(
auto outDiscovery = Layout::layoutRoot(
contentTree,
pageLayoutInput.withBreakpointTraverser(Layout::BreakpointTraverser(&prevBreakpoint))
);
Expand All @@ -223,7 +223,7 @@ export Generator<Print::Page> print(Gc::Ref<Dom::Document> 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))
Expand Down
2 changes: 1 addition & 1 deletion src/vaev-engine/driver/render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export RenderResult render(Gc::Ref<Dom::Document> 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},
Expand Down
72 changes: 46 additions & 26 deletions src/vaev-engine/layout/block.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module;

#include <karm-math/au.h>
#include <karm-core/macros.h>
#include <karm-math/au.h>

export module Vaev.Engine:layout.block;

Expand Down Expand Up @@ -122,6 +122,31 @@ Output fragmentEmptyBox(Tree& tree, Input input) {
}
}

void _populateChildSpecifiedSizes(Tree& tree, Box& child, Input& childInput, Au horizontalMargins, Opt<Au> blockInlineSize) {
if (childInput.intrinsic == IntrinsicSize::AUTO or child.style->display != Display::INLINE) {
if (child.style->sizing->width.is<Keywords::Auto>()) {
// 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) {
Expand Down Expand Up @@ -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;

Expand All @@ -187,44 +207,41 @@ 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)},
.breakpointTraverser = input.breakpointTraverser.traverseInsideUsingIthChild(i),
.pendingVerticalSizes = input.pendingVerticalSizes,
};

auto margin = computeMargins(tree, c, childInput);

Opt<Au> childInlineSize = NONE;
if (c.style->sizing->width.is<Keywords::Auto>()) {
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
if (c.style->display == Display::Internal::TABLE_BOX) {
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(
Expand Down Expand Up @@ -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,
Expand Down
55 changes: 36 additions & 19 deletions src/vaev-engine/layout/flex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ struct FlexItem {
FlexProps flexItemProps;
FlexAxis fa;

// these 2 sizes do NOT account margins
Math::Insets<Au> borders;
Math::Insets<Au> padding;
Copy link
Contributor Author

@pauloamed pauloamed Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we be storing these values vs computing on the fly? discuss with the team

Vec2Au usedSize;
Math::Insets<Opt<Au>> margin;

Expand All @@ -152,19 +153,22 @@ 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,
// this can be lazy computed for performance gains
computeContentSizes(tree, containingBlock);
}

void commit(MutCursor<Frag> 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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}
}
Expand Down
65 changes: 43 additions & 22 deletions src/vaev-engine/layout/inline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
);

Expand Down Expand Up @@ -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(
Expand All @@ -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,
Expand Down
Loading
Loading