diff --git a/src/vaev-driver/render.cpp b/src/vaev-driver/render.cpp index b5954f6..803ce13 100644 --- a/src/vaev-driver/render.cpp +++ b/src/vaev-driver/render.cpp @@ -87,17 +87,22 @@ 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(); @@ -105,8 +110,7 @@ RenderResult render(Markup::Document const &dom, Style::Media const &media, Vec2 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; @@ -115,11 +119,12 @@ RenderResult render(Markup::Document const &dom, Style::Media const &media, Vec2 return { std::move(stylebook), makeStrong(std::move(tree.root)), - sceneRoot, + {sceneRoot}, + makeStrong(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) @@ -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(); - Layout::paint(tree.root, *sceneRoot); - sceneRoot->prepare(); + Vec> scenes; + for (auto &c : root.children) { + auto scenePage = makeStrong(); + Layout::paint(c, *scenePage); + scenePage->prepare(); + + scenes.pushBack(scenePage); + } return { std::move(stylebook), makeStrong(std::move(tree.root)), - sceneRoot, + scenes, + makeStrong(root) }; } diff --git a/src/vaev-driver/render.h b/src/vaev-driver/render.h index 4e85572..823eb31 100644 --- a/src/vaev-driver/render.h +++ b/src/vaev-driver/render.h @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include @@ -11,11 +13,12 @@ namespace Vaev::Driver { struct RenderResult { Style::StyleBook style; Strong layout; - Strong scene; + Vec> scenes; + Strong 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 diff --git a/src/vaev-layout/base.h b/src/vaev-layout/base.h index 058b10e..c7b9ad1 100644 --- a/src/vaev-layout/base.h +++ b/src/vaev-layout/base.h @@ -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> 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; diff --git a/src/vaev-layout/block.cpp b/src/vaev-layout/block.cpp index 3afd574..100861b 100644 --- a/src/vaev-layout/block.cpp +++ b/src/vaev-layout/block.cpp @@ -1,10 +1,21 @@ -#include "block.h" +#include "input_output.h" -#include "box.h" #include "layout.h" +#include "tree.h" namespace Vaev::Layout { +// assuming second has more content than first +Breakpoint best(Breakpoint const &first, Breakpoint const &second) { + if (first.appeal == 0 and second.appeal == 0) + return first; + + if (first.appeal <= second.appeal) + return second; + else + return first; +} + // https://www.w3.org/TR/CSS22/visuren.html#normal-flow struct BlockFormatingContext { static Px _determineWidth(Tree &tree, Box &box, Input input) { @@ -14,7 +25,6 @@ struct BlockFormatingContext { tree, c, { - .commit = Commit::NO, .intrinsic = input.intrinsic, } ); @@ -25,13 +35,60 @@ struct BlockFormatingContext { return width; } - Output run(Tree &tree, Box &box, Input input) { - Px blockSize = Px{0}; + Output run(Tree &tree, Box &box, Input input, usize startAt, Opt stopAt) { + bool isDiscoveryMode = stopAt == NONE; + Px blockSize{0}; Px inlineSize = input.knownSize.width.unwrapOrElse([&] { return _determineWidth(tree, box, input); }); - for (auto &c : box.children()) { + Breakpoint currentBreakpoint; + + // BREAKPOINT CLASS C (top of content box, non-empty block) + if (tree.fc.allowBreak() and isDiscoveryMode and box.children().len() > 0) { + if (tree.fc.getCurrentFragmentainer()->acceptsFit( + input.position, + Vec2Px{inlineSize, blockSize}, + input.ancestralsBorderPadding + )) { + currentBreakpoint = Breakpoint{ + .endChildren = 0u, + .appeal = 1 + }; + } else { + return Output{ + .size = Vec2Px{blockSize, inlineSize}, + .completelyLaidOut = false, + .breakpoint = Breakpoint{ + .endChildren = 0, // FIXME + .appeal = 0, + }, + }; + } + } + + usize endChildren = stopAt.unwrapOr(box.children().len()); + + bool blockWasCompletelyLaidOutNonDiscovery = (box.children().len() == 0), lastChildCompletelyLaidOut = true; + bool blockWasCompletelyLaidOutDiscovery = true; + + for (usize i = startAt; i < endChildren; ++i) { + auto &c = box.children()[i]; + + // FORCED BREAK + if (tree.fc.allowBreak() and isDiscoveryMode and c.style->break_->before == BreakBetween::PAGE and i > startAt) { + + return { + .size = Vec2Px{inlineSize, blockSize}, + .completelyLaidOut = false, + .breakpoint = Breakpoint{ + .endChildren = i, + // since this is a FORCED break, it will have maximum appeal + .appeal = Limits::MAX + } + }; + } + // TODO: Implement floating // if (c.style->float_ != Float::NONE) // continue; @@ -43,9 +100,10 @@ struct BlockFormatingContext { } Input childInput = { - .commit = input.commit, + .fragment = input.fragment, .availableSpace = {inlineSize, Px{0}}, .containingBlock = {inlineSize, input.knownSize.y.unwrapOr(Px{0})}, + .ancestralsBorderPadding = input.ancestralsBorderPadding }; auto margin = computeMargins(tree, c, childInput); @@ -57,29 +115,94 @@ struct BlockFormatingContext { childInput.position = input.position + Vec2Px{margin.start, blockSize}; - auto ouput = layout( - tree, - c, - childInput - ); + Output output = + tree.fc.allowBreak() + ? layout( + tree, + c, + childInput, + stopAt == NONE + ) + : Output::fromSize(layout(tree, c, childInput).size); if (c.style->position != Position::ABSOLUTE) { - blockSize += ouput.size.y + margin.bottom; + blockSize += output.size.y + margin.bottom; + } + + if (tree.fc.allowBreak()) { + if (isDiscoveryMode) { + // breakpoint inside child (from this blocks perspective) + // BREAK CLASS X (recursive case) + Breakpoint insideChild{ + .endChildren = i + 1, + .appeal = box.style->break_->inside == BreakInside::AVOID + ? min(output.breakpoint.unwrap().appeal, 1u) + : output.breakpoint.unwrap().appeal + }; + + currentBreakpoint = best(currentBreakpoint, insideChild); + + // breakpoint right after child + // this can only happen IF child was fully laid out and its not last child (not class C) + // BREAK CLASS A + if (output.completelyLaidOut) { + if (i + 1 < box.children().len()) { + bool isAvoided = + box.style->break_->inside == BreakInside::AVOID or + c.style->break_->after == BreakBetween::AVOID or + box.children()[i + 1].style->break_->before == BreakBetween::AVOID; + + Breakpoint afterChild{ + .endChildren = i + 1, + .appeal = isAvoided ? 1u : 2u + }; + currentBreakpoint = best(currentBreakpoint, afterChild); + } + } else { + blockWasCompletelyLaidOutDiscovery = false; + break; + } + + // FORCED BREAK + if (c.style->break_->after == BreakBetween::PAGE) { + currentBreakpoint.appeal = Limits::MAX; + return { + .size = Vec2Px{inlineSize, blockSize}, + .completelyLaidOut = false, + .breakpoint = Breakpoint{ + .endChildren = i + 1, + // since this is a FORCED break, it will have maximum appeal + .appeal = Limits::MAX + } + }; + } + } else { + if (i + 1 == endChildren) { + lastChildCompletelyLaidOut = output.completelyLaidOut; + + if (not output.completelyLaidOut) + blockWasCompletelyLaidOutNonDiscovery = false; + else + blockWasCompletelyLaidOutNonDiscovery = i + 1 == box.children().len(); + } + } } } // layoutFloat(t, f, input.containingBlock); - return Output::fromSize({ - inlineSize, - blockSize, - }); + return Output{ + .size = Vec2Px{inlineSize, blockSize}, + .completelyLaidOut = isDiscoveryMode ? blockWasCompletelyLaidOutDiscovery : blockWasCompletelyLaidOutNonDiscovery, + .lastChildCompletelyLaidOut = lastChildCompletelyLaidOut, + .breakpoint = currentBreakpoint + }; } }; -Output blockLayout(Tree &tree, Box &box, Input input) { +Output blockLayout(Tree &tree, Box &box, Input input, usize startAt, Opt stopAt) { BlockFormatingContext fc; - return fc.run(tree, box, input); + return fc.run(tree, box, input, startAt, stopAt); } } // namespace Vaev::Layout diff --git a/src/vaev-layout/block.h b/src/vaev-layout/block.h index ff77b12..9634ed1 100644 --- a/src/vaev-layout/block.h +++ b/src/vaev-layout/block.h @@ -1,9 +1,11 @@ #pragma once -#include "base.h" +#include "input_output.h" + +#include "tree.h" namespace Vaev::Layout { -Output blockLayout(Tree &tree, Box &box, Input input); +Output blockLayout(Tree &tree, Box &box, Input input, usize startAt, Opt stopAt); } // namespace Vaev::Layout diff --git a/src/vaev-layout/box.cpp b/src/vaev-layout/box.cpp index 16dbc0e..1031359 100644 --- a/src/vaev-layout/box.cpp +++ b/src/vaev-layout/box.cpp @@ -2,6 +2,7 @@ #include #include "box.h" +#include "frag.h" namespace Vaev::Layout { @@ -37,7 +38,7 @@ void Box::add(Box &&box) { void Box::repr(Io::Emit &e) const { if (children()) { - e("(box {} {} {} {}", attrs, style->display, style->position, layout.borderBox()); + e("(box {} {} {}", attrs, style->display, style->position); e.indentNewline(); for (auto &c : children()) { c.repr(e); @@ -46,7 +47,7 @@ void Box::repr(Io::Emit &e) const { e.deindent(); e(")"); } else { - e("(box {} {} {} {})", attrs, style->display, style->position, layout.borderBox()); + e("(box {} {} {})", attrs, style->display, style->position); } } diff --git a/src/vaev-layout/box.h b/src/vaev-layout/box.h index 4e01bc2..c6cb3b9 100644 --- a/src/vaev-layout/box.h +++ b/src/vaev-layout/box.h @@ -11,6 +11,8 @@ namespace Vaev::Layout { // MARK: Box ------------------------------------------------------------------ +struct Box; + using Content = Union< None, Vec, @@ -31,7 +33,6 @@ struct Box : public Meta::NoCopy { Strong style; Strong fontFace; Content content = NONE; - Layout layout; Attrs attrs; Box(Strong style, Strong fontFace); @@ -47,9 +48,4 @@ struct Box : public Meta::NoCopy { void repr(Io::Emit &e) const; }; -struct Tree { - Box root; - Viewport viewport; -}; - } // namespace Vaev::Layout diff --git a/src/vaev-layout/flex.cpp b/src/vaev-layout/flex.cpp index b3bb73a..a81570e 100644 --- a/src/vaev-layout/flex.cpp +++ b/src/vaev-layout/flex.cpp @@ -152,17 +152,17 @@ struct FlexItem { FlexItem(Tree &tree, Box &box, bool isRowOriented, Vec2Px containingBlock) : box(&box), flexItemProps(*box.style->flex), fa(isRowOriented) { - speculateValues(tree, Input{Commit::NO}); + 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() { - box->layout.margin.top = margin.top.unwrapOr(speculativeMargin.top); - box->layout.margin.start = margin.start.unwrapOr(speculativeMargin.start); - box->layout.margin.end = margin.end.unwrapOr(speculativeMargin.end); - box->layout.margin.bottom = margin.bottom.unwrapOr(speculativeMargin.bottom); + 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); } void computeContentSizes(Tree &tree, Vec2Px containingBlock) { @@ -234,10 +234,7 @@ struct FlexItem { speculativeMargin = computeMargins( t, *box, - { - .commit = Commit::NO, - .containingBlock = input.availableSpace, // FIXME - } + input ); } @@ -430,7 +427,7 @@ struct FlexItem { } } - void alignCrossStretch(Tree &tree, Input input, Px lineCrossSize) { + void alignCrossStretch(Tree &tree, Px lineCrossSize) { if ( fa.crossAxis(box->style->sizing).type == Size::Type::AUTO and fa.startCrossAxis(*box->style->margin) != Width::Type::AUTO and @@ -444,7 +441,6 @@ struct FlexItem { speculateValues( tree, { - .commit = input.commit, .knownSize = fa.extractMainAxisAndFillOptOther(usedSize, elementSpeculativeCrossSize), } ); @@ -453,7 +449,7 @@ struct FlexItem { fa.crossAxis(position) = getMargin(START_CROSS); } - void alignItem(Tree &tree, Input input, Px lineCrossSize, Style::Align::Keywords parentAlignItems) { + void alignItem(Tree &tree, Px lineCrossSize, Style::Align::Keywords parentAlignItems) { auto align = box->style->aligns.alignSelf.keyword; if (align == Style::Align::AUTO) @@ -473,7 +469,7 @@ struct FlexItem { return; case Style::Align::STRETCH: - alignCrossStretch(tree, input, lineCrossSize); + alignCrossStretch(tree, lineCrossSize); return; default: @@ -482,7 +478,7 @@ struct FlexItem { return; } } -}; +}; // namespace Vaev::Layout struct FlexLine { MutSlice items; @@ -645,7 +641,6 @@ struct FlexFormatingContext { i.speculateValues( tree, { - .commit = Commit::NO, .knownSize = fa.extractMainAxisAndFillOptOther(i.flexBaseSize), .availableSpace = availableSpace, } @@ -1038,7 +1033,6 @@ struct FlexFormatingContext { i.speculateValues( tree, { - .commit = Commit::NO, .knownSize = fa.extractMainAxisAndFillOptOther(i.usedSize), .availableSpace = fa.extractMainAxisAndFillOther(i.usedSize, availableCrossSpace), } @@ -1254,7 +1248,7 @@ struct FlexFormatingContext { } } - if (input.commit == Commit::YES) { + if (input.fragment) { // This is done after any flexible lengths and any auto margins have been resolved. // NOTE: justifying doesnt change sizes/margins, thus will only run when committing and setting positions auto justifyContent = box.style->aligns.justifyContent.keyword; @@ -1318,12 +1312,11 @@ struct FlexFormatingContext { // 14. MARK: Align all flex items ------------------------------------------ // https://www.w3.org/TR/css-flexbox-1/#algo-cross-align - void _alignAllFlexItems(Tree &tree, Box &box, Input input) { + void _alignAllFlexItems(Tree &tree, Box &box) { for (auto &flexLine : _lines) { for (auto &flexItem : flexLine.items) { flexItem.alignItem( tree, - input, flexLine.crossSize, box.style->aligns.alignItems.keyword ); @@ -1443,14 +1436,13 @@ struct FlexFormatingContext { tree, *flexItem.box, { - .commit = Commit::YES, + .fragment = input.fragment, .knownSize = {flexItem.usedSize.x, flexItem.usedSize.y}, .position = flexItem.position, .availableSpace = flexItem.usedSize, } ); - - flexItem.commit(); + flexItem.commit(input.fragment); } } } @@ -1499,7 +1491,7 @@ struct FlexFormatingContext { _resolveCrossAxisAutoMargins(); // 14. Align all flex items along the cross-axis. - _alignAllFlexItems(tree, box, input); + _alignAllFlexItems(tree, box); // 15. Determine the flex container's used cross size _determineFlexContainerUsedCrossSize(input, box); @@ -1508,7 +1500,7 @@ struct FlexFormatingContext { _alignAllFlexLines(box); // XX. Commit - if (input.commit == Commit::YES) + if (input.fragment) _commit(tree, input); return Output::fromSize(fa.buildPair(_usedMainSize, _usedCrossSize)); diff --git a/src/vaev-layout/flex.h b/src/vaev-layout/flex.h index 20895de..a9658cc 100644 --- a/src/vaev-layout/flex.h +++ b/src/vaev-layout/flex.h @@ -1,6 +1,6 @@ #pragma once -#include "layout.h" +#include "input_output.h" namespace Vaev::Layout { diff --git a/src/vaev-layout/frag.h b/src/vaev-layout/frag.h new file mode 100644 index 0000000..a4aedb8 --- /dev/null +++ b/src/vaev-layout/frag.h @@ -0,0 +1,63 @@ +#pragma once + +#include "box.h" + +namespace Vaev::Layout { + +struct Metrics { + InsetsPx padding{}; + InsetsPx borders{}; + Vec2Px position; //< Position relative to the content box of the containing block + Vec2Px borderSize; + InsetsPx margin{}; + RadiiPx radii{}; + + void repr(Io::Emit &e) const { + e("(layout paddings: {} borders: {} position: {} borderSize: {} margin: {} radii: {})", + padding, borders, position, borderSize, margin, radii); + } + + 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 Frag { + MutCursor box; + Metrics metrics; + Vec children; + + Frag(MutCursor box) : box{std::move(box)} {} + + Frag() : box{nullptr} {} + + Style::Computed const &style() const { + return *box->style; + } + + /// Offset the position of this fragment and its subtree. + void offset(Vec2Px d) { + metrics.position = metrics.position + d; + for (auto &c : children) + c.offset(d); + } + + /// Add a child fragment. + void add(Frag &&frag) { + children.pushBack(std::move(frag)); + } +}; + +} // namespace Vaev::Layout diff --git a/src/vaev-layout/fragmentainer.h b/src/vaev-layout/fragmentainer.h new file mode 100644 index 0000000..3f2c4fb --- /dev/null +++ b/src/vaev-layout/fragmentainer.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include + +#include "frag.h" + +namespace Vaev::Layout { + +// TODO: if we have columns inside a page, who determines the position of the columns? the page or the column? +// columns are a pain... + +// https://www.w3.org/TR/css-break-3/#fragmentainer +struct Fragmentainer { + Vec2Px size; + + bool acceptsFit(Vec2Px position, Vec2Px sizeBox, Vec2Px ancestralsBorderPadding) { + Vec2Px total = position + sizeBox + ancestralsBorderPadding; + return total.x <= size.x and total.y <= size.y; + } +}; + +// https://www.w3.org/TR/css-break-3/#fragmentation-context +struct FragmentationContext { + + Vec2Px defaultSize = {Limits::MAX, Limits::MAX}; + + // TODO: discuss this being global with team + // TODO: merge maps into map of tuples (better memory access pattern) + Map layoutUntilComitted, layoutUntilNotComitted; + + FragmentationContext() {} + + FragmentationContext(Vec2Px defaultSize) : defaultSize(defaultSize) {} + + Vec> fragmentainers; + + void createNextFragmentainer() { + fragmentainers.pushBack(makeStrong(Fragmentainer(defaultSize))); + } + + Strong getCurrentFragmentainer() { + return fragmentainers[fragmentainers.len() - 1]; + } + + bool allowBreak() { + return not(defaultSize == Vec2Px{Limits::MAX, Limits::MAX}); + } + + usize getStartPosition(Box &box) { + if (not layoutUntilComitted.has(&box)) + return 0; + else + return layoutUntilComitted.get(&box); + } + + usize getDiscoveredEndPosition(Box &box) { + return layoutUntilNotComitted.get(&box); + } + + void setDiscoveredEndPosition(Box &box, usize pos) { + layoutUntilNotComitted.put(&box, pos); + } + + void setLaidOutEndPosition(Box &box, usize pos) { + layoutUntilComitted.put(&box, pos); + } +}; + +} // namespace Vaev::Layout diff --git a/src/vaev-layout/grid.cpp b/src/vaev-layout/grid.cpp index ad56a4d..b9de004 100644 --- a/src/vaev-layout/grid.cpp +++ b/src/vaev-layout/grid.cpp @@ -6,7 +6,7 @@ namespace Vaev::Layout { Output gridLayout(Tree &tree, Box &box, Input input) { // FIXME: Implement grid layout - return blockLayout(tree, box, input); + return blockLayout(tree, box, input, 0, NONE); } } // namespace Vaev::Layout diff --git a/src/vaev-layout/grid.h b/src/vaev-layout/grid.h index 91ffd99..30ecd11 100644 --- a/src/vaev-layout/grid.h +++ b/src/vaev-layout/grid.h @@ -1,6 +1,6 @@ #pragma once -#include "base.h" +#include "input_output.h" namespace Vaev::Layout { diff --git a/src/vaev-layout/inline.h b/src/vaev-layout/inline.h index 838475f..ecc1d14 100644 --- a/src/vaev-layout/inline.h +++ b/src/vaev-layout/inline.h @@ -1,6 +1,6 @@ #pragma once -#include "base.h" +#include "input_output.h" namespace Vaev::Layout { diff --git a/src/vaev-layout/input_output.h b/src/vaev-layout/input_output.h new file mode 100644 index 0000000..d8a2eec --- /dev/null +++ b/src/vaev-layout/input_output.h @@ -0,0 +1,65 @@ +#pragma once + +#include "base.h" +#include "frag.h" + +namespace Vaev::Layout { + +/// Input to the layout algorithm. +struct Input { + /// Parent fragment where the layout will be attached. + MutCursor fragment = nullptr; + IntrinsicSize intrinsic = IntrinsicSize::AUTO; + Math::Vec2> knownSize = {}; + Vec2Px position = {}; + Vec2Px availableSpace = {}; + Vec2Px containingBlock = {}; + + // TODO: instead of stringing this around, maybe change this (and check method of fragmentainer) to a + // "availableSpaceInFragmentainer" parameter + Vec2Px ancestralsBorderPadding = {}; +}; + +// NOTE: all these comments might be erased once we are secure on the strucutre and have proper documentation + +// TODO: consider adding classification for breakpoints, what would make appeal computing easier and less error prone +struct Breakpoint { + // only children with (index < endChildren) will be laid out + usize endChildren = 0; + // appeal = 0: an overflow occured; we want the earliest breakpoint possible + // appeal > 0: no overflow occured: we want the latest breakpoint possible with biggest appeal + usize appeal = 0; +}; + +struct Output { + // size of subtree maximizing displayed content while respecting + // - endchild constraint or + // - not overflowing fragmentainer or + // - forced break + Vec2Px size; + + // was the box subtree laid out until the end? + // - discovery mode: until the very end of the box + // - non discovery mode: all subtrees until endChildren-1 were completly laid out + // useful for: + // - discovery mode: knowing if a child was complete so we can break after it + // (if not fully laid out, we need to stop the block formatting context) + // - non-discovery mode: knowing if we should start from endChild or endChild + 1 the next pass + // if im the endChild-1, i'll need to revisit me the next pass if + // - my last child will need to be revisted (reason for lastChildCompletelyLaidOut) + // - my last child doesnt need to be revisted, but i still have some children to visit + bool completelyLaidOut; + bool lastChildCompletelyLaidOut = true; + + // only to be used in discovery mode + Opt breakpoint = NONE; + + static Output fromSize(Vec2Px size) { + return { + .size = size, + .completelyLaidOut = true + }; + } +}; + +} // namespace Vaev::Layout diff --git a/src/vaev-layout/layout.cpp b/src/vaev-layout/layout.cpp index c89d241..9ead6be 100644 --- a/src/vaev-layout/layout.cpp +++ b/src/vaev-layout/layout.cpp @@ -6,22 +6,46 @@ #include "grid.h" #include "inline.h" #include "table.h" +#include "tree.h" #include "values.h" namespace Vaev::Layout { -Output _contentLayout(Tree &tree, Box &box, Input input) { +Output _contentLayout(Tree &tree, Box &box, Input input, usize startAt, Opt stopAt) { auto display = box.style->display; if (auto run = box.content.is>()) { - return inlineLayout(tree, box, input); + auto out = inlineLayout(tree, box, input); + + if (not tree.fc.getCurrentFragmentainer()->acceptsFit( + input.position, + out.size, + input.ancestralsBorderPadding + )) { + return Output{ + .size = out.size, + .completelyLaidOut = false, + .breakpoint = Breakpoint{ + .endChildren = 0, + .appeal = 0 + } + }; + } else + return Output{ + .size = out.size, + .completelyLaidOut = true, + .breakpoint = Breakpoint{ + .endChildren = 1, + .appeal = 2 + } + }; } else if ( display == Display::FLOW or display == Display::FLOW_ROOT or display == Display::TABLE_CELL or display == Display::TABLE_CAPTION ) { - return blockLayout(tree, box, input); + return blockLayout(tree, box, input, startAt, stopAt); } else if (display == Display::FLEX) { return flexLayout(tree, box, input); } else if (display == Display::GRID) { @@ -31,7 +55,7 @@ Output _contentLayout(Tree &tree, Box &box, Input input) { } else if (display == Display::INTERNAL) { return Output{}; } else { - return blockLayout(tree, box, input); + return blockLayout(tree, box, input, startAt, stopAt); } } @@ -102,17 +126,17 @@ Vec2Px computeIntrinsicSize(Tree &tree, Box &box, IntrinsicSize intrinsic, Vec2P auto borders = computeBorders(tree, box); auto padding = _computePaddings(tree, box, containingBlock); - auto [size] = _contentLayout( + auto output = _contentLayout( tree, box, { - .commit = Commit::NO, .intrinsic = intrinsic, .knownSize = {NONE, NONE}, - } + }, + 0, NONE ); - return size + padding.all() + borders.all(); + return output.size + padding.all() + borders.all(); } static Opt _computeSpecifiedSize(Tree &tree, Box &box, Size size, Vec2Px containingBlock, bool isWidth) { @@ -141,8 +165,8 @@ static Opt _computeSpecifiedSize(Tree &tree, Box &box, Size size, Vec2Px con } } -Output layout(Tree &tree, Box &box, Input input) { - // FIXME: confirm how the preffered width/height parameters interacts with intrinsic size argument from input +Output layout(Tree &tree, Box &box, Input input, bool discoveryMode) { + // FIXME: confirm how the preferred width/height parameters interacts with intrinsic size argument from input auto borders = computeBorders(tree, box); auto padding = _computePaddings(tree, box, input.containingBlock); auto sizing = box.style->sizing; @@ -166,23 +190,75 @@ Output layout(Tree &tree, Box &box, Input input) { }); input.position = input.position + borders.topStart() + padding.topStart(); + input.ancestralsBorderPadding = input.ancestralsBorderPadding + borders.bottomEnd() + padding.bottomEnd(); + + usize startAt = tree.fc.allowBreak() ? tree.fc.getStartPosition(box) : 0; + + if (discoveryMode) { + if (not tree.fc.allowBreak()) + panic("discovery mode only allowed if we can break"); + auto out = _contentLayout(tree, box, input, startAt, NONE); + + auto size = out.size; + size.width = input.knownSize.width.unwrapOr(size.width); + size.height = input.knownSize.height.unwrapOr(size.height); + + // BREAKPOINT CLASS C (bottom of content box) + if (tree.fc.allowBreak() and out.completelyLaidOut) { + if (tree.fc.getCurrentFragmentainer()->acceptsFit( + input.position, + size, + input.ancestralsBorderPadding + )) { + out.breakpoint = Breakpoint{ + .endChildren = box.children().len(), + .appeal = 1 + }; + } else { + out.completelyLaidOut = false; + } + } + + out.size = size + padding.all() + borders.all(); - auto [size] = _contentLayout(tree, box, input); + tree.fc.setDiscoveredEndPosition(box, out.breakpoint.unwrap().endChildren); - size.width = input.knownSize.width.unwrapOr(size.width); - size.height = input.knownSize.height.unwrapOr(size.height); + return out; + } else { + Opt stopAt = tree.fc.allowBreak() + ? Opt{tree.fc.getDiscoveredEndPosition(box)} + : NONE; - size = size + padding.all() + borders.all(); + auto parentFrag = input.fragment; + Frag currFrag(&box); + input.fragment = &currFrag; - if (input.commit == Commit::YES) { - box.layout.position = input.position - borders.topStart() - padding.topStart(); - box.layout.borderSize = size; - box.layout.padding = padding; - box.layout.borders = borders; - box.layout.radii = _computeRadii(tree, box, size); - } + auto out = _contentLayout(tree, box, input, startAt, stopAt); + + auto size = out.size; + size.width = input.knownSize.width.unwrapOr(size.width); + size.height = input.knownSize.height.unwrapOr(size.height); - return Output::fromSize(size); + size = size + padding.all() + borders.all(); + + 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); + + parentFrag->add(std::move(currFrag)); + } + + if (tree.fc.allowBreak()) + tree.fc.setLaidOutEndPosition(box, out.lastChildCompletelyLaidOut ? stopAt.unwrap() : stopAt.unwrap() - 1); + + return Output{ + .size = size, + .completelyLaidOut = out.completelyLaidOut + }; + } } } // namespace Vaev::Layout diff --git a/src/vaev-layout/layout.h b/src/vaev-layout/layout.h index d274638..de50fdc 100644 --- a/src/vaev-layout/layout.h +++ b/src/vaev-layout/layout.h @@ -1,5 +1,7 @@ #pragma once +#include "input_output.h" + #include "base.h" namespace Vaev::Layout { @@ -10,6 +12,6 @@ InsetsPx computeBorders(Tree &tree, Box &box); Vec2Px computeIntrinsicSize(Tree &tree, Box &box, IntrinsicSize intrinsic, Vec2Px containingBlock); -Output layout(Tree &tree, Box &box, Input input); +Output layout(Tree &tree, Box &box, Input input, bool discoveryMode = false); } // namespace Vaev::Layout diff --git a/src/vaev-layout/paint.cpp b/src/vaev-layout/paint.cpp index 2903b28..da15640 100644 --- a/src/vaev-layout/paint.cpp +++ b/src/vaev-layout/paint.cpp @@ -2,21 +2,22 @@ #include #include "paint.h" +#include "vaev-layout/frag.h" namespace Vaev::Layout { -static bool _paintBorders(Box &box, Gfx::Color currentColor, Gfx::Borders &borders) { - currentColor = resolve(box.style->color, currentColor); +static bool _paintBorders(Frag &frag, Gfx::Color currentColor, Gfx::Borders &borders) { + currentColor = resolve(frag.style().color, currentColor); - borders.radii = box.layout.radii.cast(); + borders.radii = frag.metrics.radii.cast(); - auto bordersLayout = box.layout.borders; + auto bordersLayout = frag.metrics.borders; borders.widths.top = bordersLayout.top.cast(); borders.widths.bottom = bordersLayout.bottom.cast(); borders.widths.start = bordersLayout.start.cast(); borders.widths.end = bordersLayout.end.cast(); - auto bordersStyle = box.style->borders; + auto bordersStyle = frag.style().borders; borders.styles[0] = bordersStyle->top.style; borders.styles[1] = bordersStyle->end.style; borders.styles[2] = bordersStyle->bottom.style; @@ -30,8 +31,8 @@ static bool _paintBorders(Box &box, Gfx::Color currentColor, Gfx::Borders &borde return not borders.widths.zero(); } -static void _paintBox(Box &box, Gfx::Color currentColor, Scene::Stack &stack) { - auto const &backgrounds = box.style->backgrounds; +static void _paintFrag(Frag &frag, Gfx::Color currentColor, Scene::Stack &stack) { + auto const &backgrounds = frag.style().backgrounds; Scene::Box paint; bool hasBackgrounds = any(backgrounds); @@ -49,37 +50,38 @@ static void _paintBox(Box &box, Gfx::Color currentColor, Scene::Stack &stack) { } } - bool hasBorders = _paintBorders(box, currentColor, paint.borders); - paint.bound = box.layout.borderBox().cast(); + bool hasBorders = _paintBorders(frag, currentColor, paint.borders); + paint.bound = frag.metrics.borderBox().cast(); - if (hasBackgrounds or hasBorders) + if (hasBackgrounds or hasBorders) { stack.add(makeStrong(std::move(paint))); + } } -static void _establishStackingContext(Box &box, Scene::Stack &stack); -static void _paintStackingContext(Box &box, Scene::Stack &stack); +static void _establishStackingContext(Frag &frag, Scene::Stack &stack); +static void _paintStackingContext(Frag &frag, Scene::Stack &stack); -static void _paintBox(Box &box, Scene::Stack &stack) { +static void _paintFrag(Frag &frag, Scene::Stack &stack) { Gfx::Color currentColor = Gfx::BLACK; - currentColor = resolve(box.style->color, currentColor); + currentColor = resolve(frag.style().color, currentColor); - _paintBox(box, currentColor, stack); + _paintFrag(frag, currentColor, stack); - if (auto prose = box.content.is>()) { + if (auto prose = frag.box->content.is>()) { (*prose)->_style.color = currentColor; - Karm::Text::Font font = {box.fontFace, box.layout.fontSize.cast()}; + Karm::Text::Font font = {frag.box->fontFace, Px{16}.cast()}; Math::Vec2f baseline = {0, font.metrics().ascend}; stack.add(makeStrong( - box.layout.borderBox().topStart().cast(), + frag.metrics.borderBox().topStart().cast(), *prose )); } } -static void _paintChildren(Box &box, Scene::Stack &stack, auto predicate) { - for (auto &c : box.children()) { - auto &s = *c.style; +static void _paintChildren(Frag &frag, Scene::Stack &stack, auto predicate) { + for (auto &c : frag.children) { + auto &s = c.style(); auto zIndex = s.zIndex; if (zIndex != ZIndex::AUTO) { @@ -97,59 +99,59 @@ static void _paintChildren(Box &box, Scene::Stack &stack, auto predicate) { } if (predicate(s)) - _paintBox(c, stack); + _paintFrag(c, stack); _paintChildren(c, stack, predicate); } } -static void _paintStackingContext(Box &box, Scene::Stack &stack) { +static void _paintStackingContext(Frag &frag, Scene::Stack &stack) { // 1. the background and borders of the element forming the stacking context. - _paintBox(box, stack); + _paintFrag(frag, stack); // 2. the child stacking contexts with negative stack levels (most negative first). - _paintChildren(box, stack, [](Style::Computed const &s) { + _paintChildren(frag, stack, [](Style::Computed const &s) { return s.zIndex.value < 0; }); // 3. the in-flow, non-inline-level, non-positioned descendants. - _paintChildren(box, stack, [](Style::Computed const &s) { + _paintChildren(frag, stack, [](Style::Computed const &s) { return s.zIndex == ZIndex::AUTO and s.display != Display::INLINE and s.position == Position::STATIC; }); // 4. the non-positioned floats. - _paintChildren(box, stack, [](Style::Computed const &s) { + _paintChildren(frag, stack, [](Style::Computed const &s) { return s.zIndex == ZIndex::AUTO and s.position == Position::STATIC and s.float_ != Float::NONE; }); // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks. - _paintChildren(box, stack, [](Style::Computed const &s) { + _paintChildren(frag, stack, [](Style::Computed const &s) { return s.zIndex == ZIndex::AUTO and s.display == Display::INLINE and s.position == Position::STATIC; }); // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0. - _paintChildren(box, stack, [](Style::Computed const &s) { + _paintChildren(frag, stack, [](Style::Computed const &s) { return s.zIndex.value == 0 and s.position != Position::STATIC; }); // 7. the child stacking contexts with positive stack levels (least positive first). - _paintChildren(box, stack, [](Style::Computed const &s) { + _paintChildren(frag, stack, [](Style::Computed const &s) { return s.zIndex.value > 0; }); } -static void _establishStackingContext(Box &box, Scene::Stack &stack) { +static void _establishStackingContext(Frag &frag, Scene::Stack &stack) { auto innerStack = makeStrong(); - innerStack->zIndex = box.style->zIndex.value; - _paintStackingContext(box, *innerStack); + innerStack->zIndex = frag.style().zIndex.value; + _paintStackingContext(frag, *innerStack); stack.add(std::move(innerStack)); } -void paint(Box &box, Scene::Stack &stack) { - _paintStackingContext(box, stack); +void paint(Frag &frag, Scene::Stack &stack) { + _paintStackingContext(frag, stack); } -void wireframe(Box &box, Gfx::Canvas &g) { - for (auto &c : box.children()) +void wireframe(Frag &frag, Gfx::Canvas &g) { + for (auto &c : frag.children) wireframe(c, g); g.strokeStyle({ @@ -158,7 +160,7 @@ void wireframe(Box &box, Gfx::Canvas &g) { .align = Gfx::INSIDE_ALIGN, }); - g.stroke(box.layout.borderBox().cast()); + g.stroke(frag.metrics.borderBox().cast()); } } // namespace Vaev::Layout diff --git a/src/vaev-layout/paint.h b/src/vaev-layout/paint.h index 922fe99..c22a915 100644 --- a/src/vaev-layout/paint.h +++ b/src/vaev-layout/paint.h @@ -2,12 +2,12 @@ #include -#include "box.h" +#include "frag.h" namespace Vaev::Layout { -void wireframe(Box &box, Gfx::Canvas &g); +void wireframe(Frag &frag, Gfx::Canvas &g); -void paint(Box &box, Scene::Stack &stack); +void paint(Frag &frag, Scene::Stack &stack); } // namespace Vaev::Layout diff --git a/src/vaev-layout/positioned.cpp b/src/vaev-layout/positioned.cpp index 829d661..db3fa15 100644 --- a/src/vaev-layout/positioned.cpp +++ b/src/vaev-layout/positioned.cpp @@ -5,44 +5,40 @@ namespace Vaev::Layout { -void layoutPositioned(Tree &tree, Box &box, RectPx containingBlock) { - if (box.style->position == Position::ABSOLUTE or box.style->position == Position::RELATIVE) { +void layoutPositioned(Tree &tree, Frag &frag, RectPx containingBlock) { + auto &style = frag.style(); + auto &metrics = frag.metrics; + + if (style.position == Position::ABSOLUTE or style.position == Position::RELATIVE) { auto origin = containingBlock.topStart(); - if (box.style->position == Position::RELATIVE) - origin = box.layout.position; + if (style.position == Position::RELATIVE) + origin = metrics.position; - auto top = box.layout.position.y; - auto start = box.layout.position.x; + auto top = metrics.position.y; + auto start = metrics.position.x; - auto topOffset = box.style->offsets->top; + auto topOffset = style.offsets->top; if (topOffset != Width::AUTO) { - top = origin.y + resolve(tree, box, topOffset, containingBlock.height); + top = origin.y + resolve(tree, *frag.box, topOffset, containingBlock.height); } - auto startOffset = box.style->offsets->start; + auto startOffset = style.offsets->start; if (startOffset != Width::AUTO) { - start = origin.x + resolve(tree, box, startOffset, containingBlock.width); + start = origin.x + resolve(tree, *frag.box, startOffset, containingBlock.width); } - auto endOffset = box.style->offsets->end; + auto endOffset = frag.style().offsets->end; if (endOffset != Width::AUTO) { - start = (origin.x + containingBlock.width) - resolve(tree, box, endOffset, containingBlock.width) - box.layout.borderSize.width; + start = (origin.x + containingBlock.width) - resolve(tree, *frag.box, endOffset, containingBlock.width) - metrics.borderSize.width; } - layout( - tree, - box, - { - .commit = Commit::YES, - .knownSize = box.layout.borderBox().size().cast>(), - .position = {start, top}, - } - ); - - containingBlock = box.layout.contentBox(); + Vec2Px newPositionOffset = Vec2Px{start, top} - metrics.position; + frag.offset(newPositionOffset); + + containingBlock = metrics.contentBox(); } - for (auto &c : box.children()) { + for (auto &c : frag.children) { layoutPositioned(tree, c, containingBlock); } } diff --git a/src/vaev-layout/positioned.h b/src/vaev-layout/positioned.h index 2e9bd17..992bc53 100644 --- a/src/vaev-layout/positioned.h +++ b/src/vaev-layout/positioned.h @@ -1,9 +1,9 @@ #pragma once -#include "box.h" +#include "frag.h" namespace Vaev::Layout { -void layoutPositioned(Tree &tree, Box &box, RectPx containingBlock); +void layoutPositioned(Tree &tree, Frag &frag, RectPx containingBlock); } // namespace Vaev::Layout diff --git a/src/vaev-layout/table.cpp b/src/vaev-layout/table.cpp index ce958d8..280fcdf 100644 --- a/src/vaev-layout/table.cpp +++ b/src/vaev-layout/table.cpp @@ -519,8 +519,7 @@ struct TableFormatingContext { auto captionOutput = layout( tree, wrapperBox.children()[i], - Input{ - .commit = Commit::NO, + { .intrinsic = IntrinsicSize::MIN_CONTENT, } ); @@ -531,8 +530,7 @@ struct TableFormatingContext { auto cellMinOutput = layout( tree, *cell.box, - Input{ - .commit = Commit::NO, + { .intrinsic = IntrinsicSize::MIN_CONTENT, } ); @@ -540,8 +538,7 @@ struct TableFormatingContext { auto cellMaxOutput = layout( tree, *cell.box, - Input{ - .commit = Commit::NO, + { .intrinsic = IntrinsicSize::MAX_CONTENT, } ); @@ -752,12 +749,8 @@ struct TableFormatingContext { tree, *cell.box, { - .commit = Commit::NO, .intrinsic = IntrinsicSize::MIN_CONTENT, - .knownSize = { - colWidth[j], - NONE, - }, + .knownSize = {colWidth[j], NONE}, } ); @@ -809,7 +802,7 @@ Output tableLayout(Tree &tree, Box &wrapper, Input input) { tree, wrapper.children()[i], { - .commit = input.commit, + .fragment = input.fragment, .position = {input.position.x, currPositionY}, } ); @@ -844,7 +837,7 @@ Output tableLayout(Tree &tree, Box &wrapper, Input input) { table.spacing.y * Px{table.grid.size.y + 1}, }; - if (input.commit == Commit::YES) { + if (input.fragment) { Px currPositionX{input.position.x}; // table box @@ -852,7 +845,7 @@ Output tableLayout(Tree &tree, Box &wrapper, Input input) { tree, table.tableBox, { - .commit = Commit::YES, + .fragment = input.fragment, .knownSize = { tableBoxSize.x + table.boxBorder.horizontal(), tableBoxSize.y + table.boxBorder.vertical(), @@ -870,7 +863,7 @@ Output tableLayout(Tree &tree, Box &wrapper, Input input) { tree, group.el, { - .commit = Commit::YES, + .fragment = input.fragment, .knownSize = { queryPref(colWidthPref, group.start, group.end), tableBoxSize.y, @@ -889,7 +882,7 @@ Output tableLayout(Tree &tree, Box &wrapper, Input input) { tree, col.el, { - .commit = Commit::YES, + .fragment = input.fragment, .knownSize = { queryPref(colWidthPref, col.start, col.end), tableBoxSize.y, @@ -908,7 +901,7 @@ Output tableLayout(Tree &tree, Box &wrapper, Input input) { tree, group.el, { - .commit = Commit::YES, + .fragment = input.fragment, .knownSize = { tableBoxSize.x, queryPref(rowHeightPref, group.start, group.end), @@ -927,7 +920,7 @@ Output tableLayout(Tree &tree, Box &wrapper, Input input) { tree, row.el, { - .commit = Commit::YES, + .fragment = input.fragment, .knownSize = { tableBoxSize.x, queryPref(rowHeightPref, row.start, row.end), @@ -963,7 +956,7 @@ Output tableLayout(Tree &tree, Box &wrapper, Input input) { tree, *cell.box, { - .commit = Commit::YES, + .fragment = input.fragment, .knownSize = { queryPref(colWidthPref, j, j + colSpan - 1) + table.spacing.x * Px{colSpan - 1}, queryPref(rowHeightPref, i, i + rowSpan - 1) + table.spacing.y * Px{rowSpan - 1} @@ -981,7 +974,7 @@ Output tableLayout(Tree &tree, Box &wrapper, Input input) { tree, wrapper.children()[i], { - .commit = input.commit, + .fragment = input.fragment, .knownSize = {tableUsedWidth, NONE}, .position = {input.position.x, currPositionY}, } diff --git a/src/vaev-layout/table.h b/src/vaev-layout/table.h index 6044525..86691a7 100644 --- a/src/vaev-layout/table.h +++ b/src/vaev-layout/table.h @@ -1,6 +1,6 @@ #pragma once -#include "base.h" +#include "input_output.h" namespace Vaev::Layout { diff --git a/src/vaev-layout/tree.h b/src/vaev-layout/tree.h new file mode 100644 index 0000000..de481ce --- /dev/null +++ b/src/vaev-layout/tree.h @@ -0,0 +1,12 @@ +#pragma once + +#include "box.h" +#include "fragmentainer.h" + +namespace Vaev::Layout { +struct Tree { + Box root; + Viewport viewport; + FragmentationContext fc = {}; +}; +} // namespace Vaev::Layout diff --git a/src/vaev-layout/values.cpp b/src/vaev-layout/values.cpp index 2f001d3..9829d64 100644 --- a/src/vaev-layout/values.cpp +++ b/src/vaev-layout/values.cpp @@ -1,14 +1,16 @@ #include "values.h" -#include "box.h" +#include "tree.h" #include "writing.h" namespace Vaev::Layout { Resolver Resolver::from(Tree const &tree, Box const &box) { + Px fontSize{16}; + Resolver resolver; - resolver.rootFont = Text::Font{tree.root.fontFace, tree.root.layout.fontSize.cast()}; - resolver.boxFont = Text::Font{box.fontFace, box.layout.fontSize.cast()}; + resolver.rootFont = Text::Font{tree.root.fontFace, fontSize.cast()}; + resolver.boxFont = Text::Font{box.fontFace, fontSize.cast()}; resolver.viewport = tree.viewport; resolver.boxAxis = mainAxis(box); return resolver; diff --git a/src/vaev-tools/main.cpp b/src/vaev-tools/main.cpp index d34b1ab..12b5706 100644 --- a/src/vaev-tools/main.cpp +++ b/src/vaev-tools/main.cpp @@ -99,12 +99,13 @@ Res<> markupDumpTokens(Mime::Url const &url) { return Ok(); } -Vaev::Style::Media constructMediaForPrint(Print::PaperStock paper) { +Vaev::Style::Media constructMediaForPrint(Vec2Px paperDimensions) { return { .type = Vaev::MediaType::PRINT, - .width = Vaev::Px{paper.width}, - .height = Vaev::Px{paper.height}, - .aspectRatio = paper.width / paper.height, + // FIXME: paper dimensions is in mm not in px + .width = paperDimensions.width, + .height = paperDimensions.height, + .aspectRatio = (double)paperDimensions.width / (double)paperDimensions.height, .orientation = Vaev::Orientation::PORTRAIT, .resolution = Vaev::Resolution::fromDpi(96), @@ -138,12 +139,30 @@ struct PrintOption { bool dumpDom = false; bool dumpLayout = false; bool dumpPaint = false; + + Opt size = NONE; + Opt paper = NONE; }; Res<> print(Mime::Url const &, Strong dom, Io::Writer &output, PrintOption options = {}) { auto paper = Print::A4; - auto media = constructMediaForPrint(paper); - auto [style, layout, paint] = Vaev::Driver::render(*dom, media, paper); + + Vec2Px dimensions = {}; + if (options.size) { + dimensions = Vec2Px{Px{options.size.unwrap().x}, Px{options.size.unwrap().y}}; + } else { + dimensions = Vec2Px{ + Px{(paper.width / Print::INCH_TO_MM) * (Karm::Print::Density::DEFAULT.toDpi())}, + Px{(paper.width / Print::INCH_TO_MM) * (Karm::Print::Density::DEFAULT.toDpi())}, + }; + } + + auto media = constructMediaForPrint(dimensions); + auto [style, layout, pages, frag] = Vaev::Driver::print( + *dom, + media, + dimensions + ); if (options.dumpDom) Sys::println("--- START OF DOM ---\n{}\n--- END OF DOM ---\n", dom); @@ -155,10 +174,12 @@ Res<> print(Mime::Url const &, Strong dom, Io::Writer &output, Sys::println("--- START OF LAYOUT ---\n{}\n--- END OF LAYOUT ---\n", layout); if (options.dumpPaint) - Sys::println("--- START OF PAINT ---\n{}\n--- END OF PAINT ---\n", paint); + Sys::println("--- START OF PAINT ---\n{}\n--- END OF PAINT ---\n", pages); Print::PdfPrinter printer{Print::A4, Print::Density::DEFAULT}; - paint->print(printer); + for (auto &page : pages) { + page->print(printer); + } Io::TextEncoder<> encoder{output}; Io::Emit e{encoder}; @@ -223,7 +244,7 @@ struct RenderOption { Res<> render(Mime::Url const &, Strong dom, Io::Writer &output, RenderOption options = {}) { auto media = constructMediaForRender(options.size); - auto [style, layout, paint] = Vaev::Driver::render(*dom, media, options.size.cast()); + auto [style, layout, paint, frag] = Vaev::Driver::render(*dom, media, options.size.cast()); if (options.dumpDom) Sys::println("--- START OF DOM ---\n{}\n--- END OF DOM ---\n", dom); @@ -235,13 +256,13 @@ Res<> render(Mime::Url const &, Strong dom, Io::Writer &output Sys::println("--- START OF LAYOUT ---\n{}\n--- END OF LAYOUT ---\n", layout); if (options.dumpPaint) - Sys::println("--- START OF PAINT ---\n{}\n--- END OF PAINT ---\n", paint); + Sys::println("--- START OF PAINT ---\n{}\n--- END OF PAINT ---\n", paint[0]); auto image = Gfx::Surface::alloc(options.size, Gfx::RGBA8888); Gfx::CpuCanvas g; g.begin(*image); g.clear(Gfx::WHITE); - paint->paint(g); + paint[0]->paint(g); g.end(); try$(Image::save(image->pixels(), output)); @@ -362,6 +383,8 @@ Async::Task<> entryPointAsync(Sys::Context &ctx) { Cli::Flag dumpDomArg = Cli::flag('d', "dump-dom"s, "Dump the DOM tree"s); Cli::Flag dumpLayoutArg = Cli::flag('l', "dump-layout"s, "Dump the layout tree"s); Cli::Flag dumpPaintArg = Cli::flag('p', "dump-paint"s, "Dump the paint tree"s); + Cli::Option widthArg = Cli::option('w', "width"s, "Width of the output image"s, 800); + Cli::Option heightArg = Cli::option('h', "height"s, "Height of the output image"s, 600); cmd.subCommand( "print"s, @@ -374,6 +397,8 @@ Async::Task<> entryPointAsync(Sys::Context &ctx) { dumpDomArg, dumpLayoutArg, dumpPaintArg, + widthArg, + heightArg, }, [=](Sys::Context &) -> Async::Task<> { Vaev::Tools::PrintOption options{ @@ -381,6 +406,7 @@ Async::Task<> entryPointAsync(Sys::Context &ctx) { .dumpDom = dumpDomArg, .dumpLayout = dumpLayoutArg, .dumpPaint = dumpPaintArg, + .size = Opt{{widthArg, heightArg}}, }; Mime::Url inputUrl = "about:stdin"_url; @@ -405,9 +431,6 @@ Async::Task<> entryPointAsync(Sys::Context &ctx) { } ); - Cli::Option widthArg = Cli::option('w', "width"s, "Width of the output image"s, 800); - Cli::Option heightArg = Cli::option('h', "height"s, "Height of the output image"s, 600); - cmd.subCommand( "render"s, 'r', diff --git a/src/vaev-view/view.cpp b/src/vaev-view/view.cpp index d88d434..e52dfa2 100644 --- a/src/vaev-view/view.cpp +++ b/src/vaev-view/view.cpp @@ -63,14 +63,14 @@ struct View : public Ui::View { g.origin(bound().xy.cast()); g.clip(viewport); - auto [_, layout, paint] = *_renderResult; + auto [_, layout, paint, frag] = *_renderResult; g.clear(rect, Gfx::WHITE); - paint->paint(g); + paint[0]->paint(g); if (Ui::debugShowLayoutBounds) { logDebug("layout tree: {}", layout); - logDebug("paint tree: {}", paint); - Layout::wireframe(*layout, g); + logDebug("paint tree: {}", paint[0]); + Layout::wireframe(*frag, g); } g.pop(); @@ -84,11 +84,11 @@ struct View : public Ui::View { Math::Vec2i size(Math::Vec2i size, Ui::Hint) override { // FIXME: This is wasteful, we should cache the result auto media = _constructMedia(size); - auto [_, layout, _] = Driver::render(*_dom, media, size.cast()); + auto [_, layout, _, frag] = Driver::render(*_dom, media, size.cast()); return { - layout->layout.borderBox().width.cast(), - layout->layout.borderBox().height.cast(), + frag->metrics.borderBox().width.cast(), + frag->metrics.borderBox().height.cast(), }; } };