Skip to content

Commit

Permalink
vaev-layout: first steps of fragmenting
Browse files Browse the repository at this point in the history
Before there was no concept of fragments and the layout step was taking the
box tree as input and saving the used layout metrics into boxes themselves.

This commit introduces the concept of Fragments, Fragmentainers and
Fragmentation Context
(https://drafts.csswg.org/css-break-4/#fragmentation-model).

Currently, only pages, the block formatting context, and PDF under the 'print'
command are supported.

The commit mechanism was changed from an enum (yes/no) to support these new
concepts.
Now, since layouting the box tree generates the Fragment tree, a layout call
that should commit a Fragment for a Box into the Fragment tree will:
- create the said Fragment
- pass a reference to the said Fragment to it child call
- add the created Fragment to the list of children of its parent in the Fragment
tree by using the passed reference

To Fragment, breakpoints with appeal were implemented, following Chrome,
https://chromium.googlesource.com/chromium/src/+/refs/heads/main/third_party/blink/renderer/core/layout/block_fragmentation_tutorial.md
and its golden rule
> Break at the breakpoint with the highest appeal (primary criterion) that also
fits as much content as possible (secondary criterion).

Co-authored-by: Paulo Medeiros <[email protected]>
Co-authored-by: Nicolas Van Bossuyt <[email protected]>
  • Loading branch information
pauloamed and sleepy-monax committed Nov 26, 2024
1 parent 19ad974 commit 9eda309
Show file tree
Hide file tree
Showing 27 changed files with 828 additions and 302 deletions.
88 changes: 69 additions & 19 deletions src/vaev-driver/render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,27 +77,36 @@ RenderResult render(Markup::Document const &dom, Style::Media const &media, Layo

start = Sys::now();

Layout::layout(
elapsed = Sys::now() - start;

logDebugIf(DEBUG_RENDER, "layout tree measure time: {}", elapsed);

start = Sys::now();

auto root = Layout::Frag();

tree.fc.createNextFragmentainer();
tree.fc.isDiscoveryMode = false;
auto outDiscovery = Layout::layout(
tree,
tree.root,
{
.commit = Layout::Commit::YES,
.fragment = &root,
.knownSize = {viewport.small.width, NONE},
.availableSpace = {viewport.small.width, 0_px},
.containingBlock = {viewport.small.width, viewport.small.height},
}
);

Layout::layoutPositioned(tree, tree.root, {viewport.small.width, viewport.small.height});
Layout::layoutPositioned(tree, root.children[0], {viewport.small.width, viewport.small.height});

auto sceneRoot = makeStrong<Scene::Stack>();

elapsed = Sys::now() - start;
logDebugIf(DEBUG_RENDER, "layout tree layout time: {}", elapsed);

auto paintStart = Sys::now();

Layout::paint(tree.root, *sceneRoot);
Layout::paint(root.children[0], *sceneRoot);
sceneRoot->prepare();

elapsed = Sys::now() - paintStart;
Expand All @@ -107,6 +116,7 @@ RenderResult render(Markup::Document const &dom, Style::Media const &media, Layo
std::move(stylebook),
makeStrong<Layout::Box>(std::move(tree.root)),
sceneRoot,
makeStrong<Layout::Frag>(root)
};
}

Expand Down Expand Up @@ -134,24 +144,64 @@ Vec<Strong<Scene::Page>> print(Markup::Document const &dom, Style::Media const &
Layout::Tree tree = {
Layout::build(computer, dom),
vp,
Layout::FragmentationContext({
Px{media.width},
Px{media.height},
})
};

Layout::layout(
tree,
tree.root,
{
.commit = Layout::Commit::YES,
.knownSize = {vp.small.width, NONE},
.availableSpace = {vp.small.width, 0_px},
.containingBlock = {vp.small.width, vp.small.height},
}
);
auto root = Layout::Frag();

Layout::Breakpoint prevBreakpoint{.endIdx = 0}, currBreakpoint;

while (true) {
tree.fc.createNextFragmentainer();
tree.fc.isDiscoveryMode = true;
auto outDiscovery = Layout::layout(
tree,
tree.root,
{
.knownSize = {vp.small.width, NONE},
.availableSpace = {vp.small.width, 0_px},
.containingBlock = {vp.small.width, vp.small.height},
.bt = Layout::BreakpointTraverser(&prevBreakpoint),
}
);

currBreakpoint = outDiscovery.completelyLaidOut
? Layout::Breakpoint::buildClassB(1, false)
: outDiscovery.breakpoint.unwrap();

tree.fc.isDiscoveryMode = false;
auto outReal = Layout::layout(
tree,
tree.root,
{
.fragment = &root,
.knownSize = {vp.small.width, NONE},
.availableSpace = {vp.small.width, 0_px},
.containingBlock = {vp.small.width, vp.small.height},
.bt = Layout::BreakpointTraverser(&prevBreakpoint, &currBreakpoint),
}
);

if (outReal.completelyLaidOut)
break;

std::swap(prevBreakpoint, currBreakpoint);
}

Vec<Strong<Scene::Page>> pages;
auto page = makeStrong<Scene::Page>(vp.small.size().cast<isize>());
Layout::paint(tree.root, *page);
page->prepare();
pages.pushBack(page);
for (auto &c : root.children) {
Layout::paint(
c,
*pages.emplaceBack(
// FIXME: it can be that specific pages have different dimensions
makeStrong<Scene::Page>(vp.small.size().cast<isize>())
)
);
last(pages)->prepare();
}

return pages;
}
Expand Down
5 changes: 4 additions & 1 deletion src/vaev-driver/render.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <karm-scene/page.h>
#include <vaev-base/length.h>
#include <vaev-layout/box.h>
#include <vaev-layout/frag.h>
#include <vaev-layout/tree.h>
#include <vaev-markup/dom.h>
#include <vaev-style/media.h>

Expand All @@ -12,7 +14,8 @@ namespace Vaev::Driver {
struct RenderResult {
Style::StyleBook style;
Strong<Layout::Box> layout;
Strong<Scene::Node> scene;
Strong<Scene::Node> scenes;
Strong<Layout::Frag> frag;
};

RenderResult render(Markup::Document const &dom, Style::Media const &media, Layout::Viewport viewport);
Expand Down
109 changes: 0 additions & 109 deletions src/vaev-layout/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,115 +23,6 @@ struct Viewport {
RectPx dynamic = small;
};

enum struct Commit {
NO, // No, only compute sizes
YES, // Yes, commit computed values to the tree
};

/// Input to the layout algorithm.

struct Input {
Commit commit = Commit::NO; //< Should the computed values be committed to the layout?
IntrinsicSize intrinsic = IntrinsicSize::AUTO;
Math::Vec2<Opt<Px>> knownSize = {};
Vec2Px position = {};
Vec2Px availableSpace = {};
Vec2Px containingBlock = {};

Input withCommit(Commit c) const {
auto copy = *this;
copy.commit = c;
return copy;
}

Input withIntrinsic(IntrinsicSize i) const {
auto copy = *this;
copy.intrinsic = i;
return copy;
}

Input withKnownSize(Math::Vec2<Opt<Px>> size) const {
auto copy = *this;
copy.knownSize = size;
return copy;
}

Input withPosition(Vec2Px pos) const {
auto copy = *this;
copy.position = pos;
return copy;
}

Input withAvailableSpace(Vec2Px space) const {
auto copy = *this;
copy.availableSpace = space;
return copy;
}

Input withContainingBlock(Vec2Px block) const {
auto copy = *this;
copy.containingBlock = block;
return copy;
}
};

/// Output of the layout algorithm.

struct Output {
Vec2Px size;

Px width() const {
return size.x;
}

Px height() const {
return size.y;
}

static Output fromSize(Vec2Px size) {
return Output{size};
}
};

/// Computed layout values.

struct Layout {
InsetsPx padding{};
InsetsPx borders{};
Vec2Px position; //< Position relative to the content box of the containing block
Vec2Px borderSize;
InsetsPx margin{};
RadiiPx radii{};
Px fontSize{16};

void repr(Io::Emit &e) const {
e("(layout paddings: {} borders: {} position: {} borderSize: {} margin: {} radii: {})",
padding, borders, position, borderSize, margin, radii);
}

Layout offseted(Vec2Px offset) const {
auto copy = *this;
copy.position = position + offset;
return copy;
}

RectPx borderBox() const {
return RectPx{position, borderSize};
}

RectPx paddingBox() const {
return borderBox().shrink(borders);
}

RectPx contentBox() const {
return paddingBox().shrink(padding);
}

RectPx marginBox() const {
return borderBox().grow(margin);
}
};

struct Box;

struct Tree;
Expand Down
Loading

0 comments on commit 9eda309

Please sign in to comment.