Skip to content

Commit

Permalink
vaev-layout: fragmenting api and BFC
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 <palm@odoo.com>
Co-authored-by: Nicolas Van Bossuyt <nivb@odoo.com>
  • Loading branch information
pauloamed and sleepy-monax committed Dec 12, 2024
1 parent 2843294 commit 2bec2cb
Show file tree
Hide file tree
Showing 29 changed files with 864 additions and 342 deletions.
2 changes: 1 addition & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ Res<> render(Mime::Url const &, Strong<Markup::Document> dom, Io::Writer &output
};

auto media = constructMediaForRender(options.scale, imageSize);
auto [style, layout, paint] = Vaev::Driver::render(*dom, media, {.small = imageSize});
auto [style, layout, paint, _] = Vaev::Driver::render(*dom, media, {.small = imageSize});

if (options.dumpDom)
Sys::println("--- START OF DOM ---\n{}\n--- END OF DOM ---\n", dom);
Expand Down
170 changes: 97 additions & 73 deletions src/web/vaev-driver/print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ static void _paintMargins(Style::PageComputedStyle &pageStyle, RectPx pageRect,
.root = Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::TOP_LEFT_CORNER)),
.viewport = Layout::Viewport{.small = topLeftMarginCornerRect.size()}
};
Layout::layout(

auto [_, topLeftMarginCornerFrag] = Layout::layoutCreateFragment(
topLeftMarginCornerTree,
{
.commit = Layout::Commit::YES,
.knownSize = topLeftMarginCornerRect.size().cast<Opt<Px>>(),
.position = topLeftMarginCornerRect.topStart(),
.availableSpace = topLeftMarginCornerRect.size(),
.containingBlock = topLeftMarginCornerRect.size(),
}
);
Layout::paint(topLeftMarginCornerTree.root, stack);
Layout::paint(topLeftMarginCornerFrag, stack);

// MARK: Top Right Corner --------------------------------------------------

Expand All @@ -44,17 +44,17 @@ static void _paintMargins(Style::PageComputedStyle &pageStyle, RectPx pageRect,
.root = Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::TOP_RIGHT_CORNER)),
.viewport = Layout::Viewport{.small = topRightMarginCornerRect.size()}
};
Layout::layout(

auto [_, topRightMarginCornerFrag] = Layout::layoutCreateFragment(
topRightMarginCornerTree,
{
.commit = Layout::Commit::YES,
.knownSize = topRightMarginCornerRect.size().cast<Opt<Px>>(),
.position = topRightMarginCornerRect.topStart(),
.availableSpace = topRightMarginCornerRect.size(),
.containingBlock = topRightMarginCornerRect.size(),
}
);
Layout::paint(topRightMarginCornerTree.root, stack);
Layout::paint(topRightMarginCornerFrag, stack);

// MARK: Bottom Left Corner ------------------------------------------------

Expand All @@ -66,18 +66,17 @@ static void _paintMargins(Style::PageComputedStyle &pageStyle, RectPx pageRect,
.root = Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::BOTTOM_LEFT_CORNER)),
.viewport = Layout::Viewport{.small = bottomLeftMarginCornerRect.size()}
};
Layout::layout(

auto [_, bottomLeftMarginCornerFrag] = Layout::layoutCreateFragment(
bottomLeftMarginCornerTree,
bottomLeftMarginCornerTree.root,
{
.commit = Layout::Commit::YES,
.knownSize = bottomLeftMarginCornerRect.size().cast<Opt<Px>>(),
.position = bottomLeftMarginCornerRect.topStart(),
.availableSpace = bottomLeftMarginCornerRect.size(),
.containingBlock = bottomLeftMarginCornerRect.size(),
}
);
Layout::paint(bottomLeftMarginCornerTree.root, stack);
Layout::paint(bottomLeftMarginCornerFrag, stack);

// MARK: Bottom Right Corner -----------------------------------------------

Expand All @@ -89,17 +88,17 @@ static void _paintMargins(Style::PageComputedStyle &pageStyle, RectPx pageRect,
.root = Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::BOTTOM_RIGHT_CORNER)),
.viewport = Layout::Viewport{.small = bottomRightMarginCornerRect.size()}
};
Layout::layout(

auto [_, bottomRightMarginCornerFrag] = Layout::layoutCreateFragment(
bottomRightMarginCornerTree,
{
.commit = Layout::Commit::YES,
.knownSize = bottomRightMarginCornerRect.size().cast<Opt<Px>>(),
.position = bottomRightMarginCornerRect.topStart(),
.availableSpace = bottomRightMarginCornerRect.size(),
.containingBlock = bottomRightMarginCornerRect.size(),
}
);
Layout::paint(bottomRightMarginCornerTree.root, stack);
Layout::paint(bottomRightMarginCornerFrag, stack);

// MARK: Top ---------------------------------------------------------------

Expand All @@ -118,17 +117,16 @@ static void _paintMargins(Style::PageComputedStyle &pageStyle, RectPx pageRect,
.viewport = Layout::Viewport{.small = topRect.size()}
};

Layout::layout(
auto [_, topFrag] = Layout::layoutCreateFragment(
topTree,
{
.commit = Layout::Commit::YES,
.knownSize = topRect.size().cast<Opt<Px>>(),
.position = topRect.topStart(),
.availableSpace = topRect.size(),
.containingBlock = topRect.size(),
}
);
Layout::paint(topTree.root, stack);
Layout::paint(topFrag, stack);

// MARK: Bottom ------------------------------------------------------------

Expand All @@ -147,18 +145,17 @@ static void _paintMargins(Style::PageComputedStyle &pageStyle, RectPx pageRect,
.viewport = Layout::Viewport{.small = bottomRect.size()}
};

Layout::layout(
auto [_, bottomFrag] = Layout::layoutCreateFragment(
bottomTree,
{
.commit = Layout::Commit::YES,
.knownSize = bottomRect.size().cast<Opt<Px>>(),
.position = bottomRect.topStart(),
.availableSpace = bottomRect.size(),
.containingBlock = bottomRect.size(),
}
);

Layout::paint(bottomTree.root, stack);
Layout::paint(bottomFrag, stack);

// MARK: Left --------------------------------------------------------------
auto leftRect = RectPx::fromTwoPoint(
Expand All @@ -176,18 +173,17 @@ static void _paintMargins(Style::PageComputedStyle &pageStyle, RectPx pageRect,
.viewport = Layout::Viewport{.small = leftRect.size()}
};

Layout::layout(
auto [_, leftFrag] = Layout::layoutCreateFragment(
leftTree,
{
.commit = Layout::Commit::YES,
.knownSize = leftRect.size().cast<Opt<Px>>(),
.position = leftRect.topStart(),
.availableSpace = leftRect.size(),
.containingBlock = leftRect.size(),
}
);

Layout::paint(leftTree.root, stack);
Layout::paint(leftFrag, stack);

// MARK: Right -------------------------------------------------------------

Expand All @@ -206,18 +202,17 @@ static void _paintMargins(Style::PageComputedStyle &pageStyle, RectPx pageRect,
.viewport = Layout::Viewport{.small = rightRect.size()}
};

Layout::layout(
auto [_, rightFrag] = Layout::layoutCreateFragment(
rightTree,
{
.commit = Layout::Commit::YES,
.knownSize = rightRect.size().cast<Opt<Px>>(),
.position = rightRect.topStart(),
.availableSpace = rightRect.size(),
.containingBlock = rightRect.size(),
}
);

Layout::paint(rightTree.root, stack);
Layout::paint(rightFrag, stack);
}

static Style::Media _constructMedia(Print::Settings const &settings) {
Expand Down Expand Up @@ -277,75 +272,104 @@ Vec<Strong<Scene::Page>> print(Markup::Document const &dom, Print::Settings cons

Vec<Strong<Scene::Page>> pages;

Layout::Resolver resolver{};
Style::Page page{.name = ""s, .number = pages.len(), .blank = false};
Style::Computed initialStyle = Style::Computed::initial();
initialStyle.color = Gfx::BLACK;
initialStyle.setCustomProp("-vaev-url", {Css::Token::string(Io::format("\"{}\"", dom.url()).unwrap())});
initialStyle.setCustomProp("-vaev-title", {Css::Token::string(Io::format("\"{}\"", dom.title()).unwrap())});
initialStyle.setCustomProp("-vaev-datetime", {Css::Token::string(Io::format("\"{}\"", Sys::now()).unwrap())});

auto pageStyle = computer.computeFor(initialStyle, page);
RectPx pageRect{
media.width / Px{media.resolution.toDppx()},
media.height / Px{media.resolution.toDppx()}
};

auto scaleMatrix = Math::Trans2f::makeScale(media.resolution.toDppx());
auto pageSize = pageRect.size().cast<f64>();
auto pageScene = makeStrong<Scene::Page>(
Print::PaperStock{
"custom"s,
pageSize.width,
pageSize.height,
},
scaleMatrix
);

InsetsPx pageMargin = {};
// MARK: Page Content ------------------------------------------------------

Layout::Tree contentTree = {
Layout::build(computer, dom),
};

Layout::Breakpoint prevBreakpoint{.endIdx = 0}, currBreakpoint;
auto rootFragment = Layout::Frag();
while (true) {
Layout::Resolver resolver{};
Style::Page page{.name = ""s, .number = pages.len(), .blank = false};

if (settings.margins == Print::Margins::DEFAULT) {
pageMargin = {
resolver.resolve(pageStyle->style->margin->top, pageRect.height),
resolver.resolve(pageStyle->style->margin->end, pageRect.width),
resolver.resolve(pageStyle->style->margin->bottom, pageRect.height),
resolver.resolve(pageStyle->style->margin->start, pageRect.width),
auto pageStyle = computer.computeFor(initialStyle, page);
RectPx pageRect{
media.width / Px{media.resolution.toDppx()},
media.height / Px{media.resolution.toDppx()}
};
} else if (settings.margins == Print::Margins::CUSTOM) {
pageMargin = settings.margins.custom.cast<Px>();
} else if (settings.margins == Print::Margins::MINIMUM) {
}

RectPx pageContent = pageRect.shrink(pageMargin);
auto pageSize = pageRect.size().cast<f64>();
auto pageScene = pages.emplaceBack(
// FIXME: it can be that specific pages have different dimensions
makeStrong<Scene::Page>(
Print::PaperStock{
"custom"s,
pageSize.width,
pageSize.height,
},
scaleMatrix
)
);

InsetsPx pageMargin = {};

if (settings.margins == Print::Margins::DEFAULT) {
pageMargin = {
resolver.resolve(pageStyle->style->margin->top, pageRect.height),
resolver.resolve(pageStyle->style->margin->end, pageRect.width),
resolver.resolve(pageStyle->style->margin->bottom, pageRect.height),
resolver.resolve(pageStyle->style->margin->start, pageRect.width),
};
} else if (settings.margins == Print::Margins::CUSTOM) {
pageMargin = settings.margins.custom.cast<Px>();
} else if (settings.margins == Print::Margins::MINIMUM) {
}

if (settings.headerFooter and settings.margins != Print::Margins::NONE)
_paintMargins(*pageStyle, pageRect, pageContent, *pageScene);
RectPx pageContent = pageRect.shrink(pageMargin);

// MARK: Page Content ------------------------------------------------------
Layout::Viewport vp{
.small = pageContent.size(),
};

Layout::Viewport vp{
.small = pageContent.size(),
};
contentTree.viewport = vp;
contentTree.fc.defaultSize = pageContent.size();

Layout::Tree contentTree = {
Layout::build(computer, dom),
vp,
};
if (settings.headerFooter and settings.margins != Print::Margins::NONE)
_paintMargins(*pageStyle, pageRect, pageContent, *pageScene);

Layout::layout(
contentTree,
{
.commit = Layout::Commit::YES,
Layout::Input pageLayoutInput{
.knownSize = {pageContent.width, NONE},
.position = pageContent.topStart(),
.availableSpace = pageContent.size(),
.containingBlock = pageContent.size(),
}
);
};

contentTree.fc.setDiscovery();
auto outDiscovery = Layout::layout(
contentTree,
pageLayoutInput.withBreakpointTraverser(Layout::BreakpointTraverser(&prevBreakpoint))
);

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

contentTree.fc.unsetDiscovery();
auto outReal = Layout::layout(
contentTree,
pageLayoutInput
.withFragment(&rootFragment)
.withBreakpointTraverser(Layout::BreakpointTraverser(&prevBreakpoint, &currBreakpoint))
);

Layout::paint(contentTree.root, *pageScene);
pageScene->prepare();
pages.pushBack(pageScene);
Layout::paint(last(rootFragment.children), *pageScene);
pageScene->prepare();

if (outReal.completelyLaidOut)
break;

std::swap(prevBreakpoint, currBreakpoint);
}

return pages;
}
Expand Down
2 changes: 2 additions & 0 deletions src/web/vaev-driver/print.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#include <karm-print/printer.h>
#include <karm-scene/page.h>
#include <vaev-layout/frag.h>

This comment has been minimized.

Copy link
@pauloamed

pauloamed Dec 12, 2024

Author Contributor

remove

#include <vaev-layout/tree.h>
#include <vaev-markup/dom.h>

namespace Vaev::Driver {
Expand Down
13 changes: 9 additions & 4 deletions src/web/vaev-driver/render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,15 @@ 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 [outDiscovery, root] = Layout::layoutCreateFragment(
tree,
{
.commit = Layout::Commit::YES,
.knownSize = {viewport.small.width, NONE},
.availableSpace = {viewport.small.width, 0_px},
.containingBlock = {viewport.small.width, viewport.small.height},
Expand All @@ -57,8 +62,7 @@ RenderResult render(Markup::Document const &dom, Style::Media const &media, Layo
logDebugIf(DEBUG_RENDER, "layout tree layout time: {}", elapsed);

auto paintStart = Sys::now();

Layout::paint(tree.root, *sceneRoot);
Layout::paint(root, *sceneRoot);
sceneRoot->prepare();

elapsed = Sys::now() - paintStart;
Expand All @@ -68,6 +72,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
5 changes: 4 additions & 1 deletion src/web/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
Loading

0 comments on commit 2bec2cb

Please sign in to comment.