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 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).

This first version only works for the block formatting context and only PDF
is supported with the 'print' subcommand.

Co-authored-by: Paulo Medeiros <palm@odoo.com>
Co-authored-by: Nicolas Van Bossuyt <nivb@odoo.com>
  • Loading branch information
pauloamed and sleepy-monax committed Nov 8, 2024
1 parent 24037e6 commit a7e9e53
Show file tree
Hide file tree
Showing 27 changed files with 674 additions and 286 deletions.
82 changes: 56 additions & 26 deletions src/vaev-driver/render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,26 +87,30 @@ RenderResult render(Markup::Document const &dom, Style::Media const &media, Vec2

start = Sys::now();

Layout::layout(
auto root = Layout::Frag();

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

Layout::layoutPositioned(tree, root.children[0], {vp.small.width, vp.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 @@ -115,11 +119,12 @@ RenderResult render(Markup::Document const &dom, Style::Media const &media, Vec2
return {
std::move(stylebook),
makeStrong<Layout::Box>(std::move(tree.root)),
sceneRoot,
{sceneRoot},
makeStrong<Layout::Frag>(root)
};
}

RenderResult render(Markup::Document &dom, Style::Media const &media, Print::PaperStock paper) {
RenderResult print(Markup::Document &dom, Style::Media const &media, Vec2Px dimensions) {
Style::StyleBook stylebook;
stylebook.add(
fetchStylesheet("bundle://vaev-view/user-agent.css"_url)
Expand All @@ -134,36 +139,61 @@ RenderResult render(Markup::Document &dom, Style::Media const &media, Print::Pap
Style::Computer computer{media, stylebook};

Layout::Viewport vp{
.small = {
Px{paper.width},
Px{paper.height},
},
.small = dimensions,
};

Layout::Tree tree = {
Layout::build(computer, dom),
vp,
Layout::FragmentationContext(dimensions)
};

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

while (true) {
tree.fc.createNextFragmentainer();

auto outDiscovery = Layout::layout(
tree,
tree.root,
{
.knownSize = {vp.small.width, NONE},
.availableSpace = {vp.small.width, Px{0}},
.containingBlock = {vp.small.width, vp.small.height},
},
true
);

auto outReal = Layout::layout(
tree,
tree.root,
{
.fragment = &root,
.knownSize = {vp.small.width, NONE},
.availableSpace = {vp.small.width, Px{0}},
.containingBlock = {vp.small.width, vp.small.height},
},
false
);

if (tree.fc.getStartPosition(tree.root) == 1)
break;
}

auto sceneRoot = makeStrong<Scene::Page>();
Layout::paint(tree.root, *sceneRoot);
sceneRoot->prepare();
Vec<Strong<Scene::Node>> scenes;
for (auto &c : root.children) {
auto scenePage = makeStrong<Scene::Page>();
Layout::paint(c, *scenePage);
scenePage->prepare();

scenes.pushBack(scenePage);
}

return {
std::move(stylebook),
makeStrong<Layout::Box>(std::move(tree.root)),
sceneRoot,
scenes,
makeStrong<Layout::Frag>(root)
};
}

Expand Down
7 changes: 5 additions & 2 deletions src/vaev-driver/render.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <karm-scene/base.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 @@ -11,11 +13,12 @@ namespace Vaev::Driver {
struct RenderResult {
Style::StyleBook style;
Strong<Layout::Box> layout;
Strong<Scene::Node> scene;
Vec<Strong<Scene::Node>> scenes;
Strong<Layout::Frag> frag;
};

RenderResult render(Markup::Document const &dom, Style::Media const &media, Vec2Px viewport);

RenderResult render(Markup::Document &dom, Style::Media const &media, Print::PaperStock paper);
RenderResult print(Markup::Document &dom, Style::Media const &media, Vec2Px dimensions);

} // namespace Vaev::Driver
65 changes: 0 additions & 65 deletions src/vaev-layout/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,71 +22,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 = {};
};

/// Output of the layout algorithm.

struct Output {
Vec2Px size;

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 a7e9e53

Please sign in to comment.