diff --git a/src/web/vaev-driver/print.cpp b/src/web/vaev-driver/print.cpp index b4117a5..766403e 100644 --- a/src/web/vaev-driver/print.cpp +++ b/src/web/vaev-driver/print.cpp @@ -286,7 +286,10 @@ Vec> print(Markup::Document const &dom, Print::Settings cons Layout::build(computer, dom), }; - Layout::Breakpoint prevBreakpoint{.endIdx = 0}, currBreakpoint; + // ideally prev breakpoint would be an OPT here or a pointer for the first iteration, where it doesnt exist + Layout::Breakpoint currBreakpoint, + prevBreakpoint{.endIdx = 0, .advanceCase = Layout::Breakpoint::ADVANCE_CASE::ADVANCE_WITHOUT_CHILDREN}; + auto rootFragment = Layout::Frag(); while (true) { Layout::Resolver resolver{}; diff --git a/src/web/vaev-layout/input_output.h b/src/web/vaev-layout/input_output.h index d8cb017..5f9f72c 100644 --- a/src/web/vaev-layout/input_output.h +++ b/src/web/vaev-layout/input_output.h @@ -22,7 +22,13 @@ struct Breakpoint { // attribution could should be encapsulated in this class, instead of exposed to code usize appeal = 0; - Vec child = {}; + Vec> children = {}; + + enum struct ADVANCE_CASE { + NOT_ADVANCE, // keeping children + ADVANCE_WITH_CHILDREN, + ADVANCE_WITHOUT_CHILDREN + } advanceCase; void overrideIfBetter(Breakpoint &&BPWithMoreContent) { if (appeal == 0 and BPWithMoreContent.appeal == 0) @@ -33,26 +39,57 @@ struct Breakpoint { } void repr(Io::Emit &e) const { - e("(end: {} appeal: {}", endIdx, appeal); - if (child.len() == 0) + e("(end: {} appeal: {} advance case: {}", endIdx, appeal, advanceCase); + if (children.len() == 0) e("; no child)"); else - e("; child : {})", child[0]); + e("; children : {})", children); } static Breakpoint buildForced(usize endIdx) { return Breakpoint{ .endIdx = endIdx, // since this is a FORCED break, it will have maximum appeal - .appeal = Limits::MAX + .appeal = Limits::MAX, + .advanceCase = ADVANCE_CASE::ADVANCE_WITHOUT_CHILDREN, }; } + // NOTE: a bit inconsistent with the rest of the API + void applyAvoid() { + appeal = min(appeal, AVOID_APPEAL); + } + static Breakpoint buildFromChild(Breakpoint &&childBreakpoint, usize endIdx, bool isAvoid) { Breakpoint b{ .endIdx = endIdx, .appeal = childBreakpoint.appeal, - .child = {std::move(childBreakpoint)} + .children = {std::move(childBreakpoint)}, + .advanceCase = ADVANCE_CASE::NOT_ADVANCE, + }; + + if (isAvoid) + b.appeal = min(b.appeal, AVOID_APPEAL); + + return b; + } + + static Breakpoint buildFromChildren(Vec> childrenBreakpoints, usize endIdx, bool isAvoid, bool advance) { + usize appeal = Limits::MAX; + for (auto &breakpoint : childrenBreakpoints) { + if (not breakpoint) + continue; + appeal = min(appeal, breakpoint.unwrap().appeal); + } + + if (appeal == Limits::MAX) + panic(""); + + Breakpoint b{ + .endIdx = endIdx, + .appeal = appeal, + .children = {std::move(childrenBreakpoints)}, + .advanceCase = advance ? ADVANCE_CASE::ADVANCE_WITH_CHILDREN : ADVANCE_CASE::NOT_ADVANCE }; if (isAvoid) @@ -64,7 +101,8 @@ struct Breakpoint { static Breakpoint buildClassB(usize endIdx, bool isAvoid) { Breakpoint b{ .endIdx = endIdx, - .appeal = isAvoid ? AVOID_APPEAL : CLASS_B_APPEAL + .appeal = isAvoid ? AVOID_APPEAL : CLASS_B_APPEAL, + .advanceCase = ADVANCE_CASE::ADVANCE_WITHOUT_CHILDREN }; return b; @@ -74,7 +112,8 @@ struct Breakpoint { // this is a placeholder breakpoint and should be overriden return { .endIdx = 0, - .appeal = 0 + .appeal = 0, + .advanceCase = ADVANCE_CASE::NOT_ADVANCE }; } }; @@ -87,23 +126,44 @@ struct BreakpointTraverser { MutCursor curr = nullptr ) : prevIteration(prev), currIteration(curr) {} - BreakpointTraverser traverseInsideUsingIthChild(usize i) { - BreakpointTraverser deeperBPT; - if (prevIteration and prevIteration->child.len() > 0 and i + 1 == prevIteration->endIdx) { - deeperBPT.prevIteration = &prevIteration->child[0]; + bool isDeactivated() { + return prevIteration == nullptr and currIteration == nullptr; + } + + MutCursor traversePrev(usize i, usize j) { + if (prevIteration and prevIteration->children.len() > 0 and + (i + 1 == prevIteration->endIdx or + (prevIteration->advanceCase == Breakpoint::ADVANCE_CASE::ADVANCE_WITH_CHILDREN and i == prevIteration->endIdx) + )) { + if (prevIteration->children[j]) + return &prevIteration->children[j].unwrap(); } + return nullptr; + } - if (currIteration and currIteration->child.len() > 0 and i + 1 == currIteration->endIdx) { - deeperBPT.currIteration = &currIteration->child[0]; + MutCursor traverseCurr(usize i, usize j) { + if (currIteration and currIteration->children.len() > 0 and i + 1 == currIteration->endIdx) { + if (currIteration->children[j]) + return &currIteration->children[j].unwrap(); } + return nullptr; + } + BreakpointTraverser traverseInsideUsingIthChildToJthParallelFlow(usize i, usize j) { + BreakpointTraverser deeperBPT; + deeperBPT.prevIteration = traversePrev(i, j); + deeperBPT.currIteration = traverseCurr(i, j); return deeperBPT; } + BreakpointTraverser traverseInsideUsingIthChild(usize i) { + return traverseInsideUsingIthChildToJthParallelFlow(i, 0); + } + Opt getStart() { if (prevIteration == nullptr) return NONE; - return prevIteration->endIdx - (prevIteration->child.len() ? 1 : 0); + return prevIteration->endIdx - (prevIteration->advanceCase == Breakpoint::ADVANCE_CASE::NOT_ADVANCE); } Opt getEnd() { @@ -173,6 +233,12 @@ struct Input { copy.breakpointTraverser = bpt; return copy; } + + Input addPendingVerticalSize(Px newPendingVerticalSize) const { + auto copy = *this; + copy.pendingVerticalSizes += newPendingVerticalSize; + return copy; + } }; struct Output { diff --git a/src/web/vaev-layout/layout.cpp b/src/web/vaev-layout/layout.cpp index 5c0c4f9..636ea07 100644 --- a/src/web/vaev-layout/layout.cpp +++ b/src/web/vaev-layout/layout.cpp @@ -48,7 +48,7 @@ Output _contentLayout(Tree &tree, Box &box, Input input, usize startAt, Optdisplay == Display::Inside::FLEX or - box.style->display == Display::Inside::TABLE or box.style->display == Display::Inside::GRID; if (isMonolticDisplay) @@ -249,7 +249,9 @@ Output layout(Tree &tree, Box &box, Input input) { auto size = out.size; size.width = input.knownSize.width.unwrapOr(size.width); - size.height = input.knownSize.height.unwrapOr(size.height); + if (out.completelyLaidOut and not input.breakpointTraverser.prevIteration) { + size.height = input.knownSize.height.unwrapOr(size.height); + } // TODO: Class C breakpoint diff --git a/src/web/vaev-layout/table.cpp b/src/web/vaev-layout/table.cpp index c4fa509..7f7e393 100644 --- a/src/web/vaev-layout/table.cpp +++ b/src/web/vaev-layout/table.cpp @@ -74,7 +74,9 @@ struct TableGrid { template struct PrefixSum { - Vec pref; + Vec pref = {}; + + PrefixSum() {} PrefixSum(Vec const &v) : pref(v) { for (usize i = 1; i < v.len(); ++i) @@ -364,8 +366,7 @@ struct TableFormatingContext { panic("current element should be thead or tbody"); } - if (indexOfHeaderGroup and - indexOfHeaderGroup.unwrap() == (usize)(tableBoxCursor - tableBoxChildren.begin())) { + if (indexOfHeaderGroup == (usize)(tableBoxCursor - tableBoxChildren.begin())) { // table header was already processed in the beggining of the Rows section of the algorithm tableBoxCursor.next(); continue; @@ -824,7 +825,7 @@ struct TableFormatingContext { } } - struct AxisHelper { + struct AxisHelper { // FIXME: find me a better name pls Opt groupIdx = NONE; Opt axisIdx = NONE; }; @@ -842,8 +843,11 @@ struct TableFormatingContext { return helper; }; - Vec2Px tableBoxSize; + Vec2Px tableBoxSize, headerSize = {}, footerSize = {}; Vec rowHelper, colHelper; + PrefixSum colWidthPref, rowHeightPref; + Math::Vec2u dataRowsInterval; + Vec startPositionOfRow; void build(Tree &tree, Input input) { buildHTMLTable(); @@ -868,73 +872,397 @@ struct TableFormatingContext { computeRowHeights(tree); + colWidthPref = PrefixSum{colWidth}; + rowHeightPref = PrefixSum{rowHeight}; + + dataRowsInterval = {numOfHeaderRows, grid.size.y - numOfFooterRows - 1}; + tableBoxSize = Vec2Px{ iter(colWidth).sum() + spacing.x * Px{grid.size.x + 1}, iter(rowHeight).sum() + spacing.y * Px{grid.size.y + 1}, }; + + if (numOfHeaderRows) { + headerSize = Vec2Px{ + tableBoxSize.x, + rowHeightPref.query(0, numOfHeaderRows - 1) + spacing.y * Px{numOfHeaderRows + 1}, + }; + } + + if (numOfFooterRows) { + footerSize = Vec2Px{ + tableBoxSize.x, + rowHeightPref.query(grid.size.y - numOfFooterRows, grid.size.y - 1) + + spacing.y * Px{numOfHeaderRows + 1}, + }; + } + + startPositionOfRow = Buf::init(grid.size.y, 0_px); } - void runTableBox(Tree &tree, Input input, Px &currPositionY) { - PrefixSum colWidthPref{colWidth}, rowHeightPref{rowHeight}; - Px currPositionX{input.position.x}; + Tuple layoutCell(Tree &tree, Input &input, TableCell &cell, MutCursor cellBox, usize startFrag, usize i, usize j, Px currPositionX, usize breakpointIndexOffset) { + + // breakpoint traversing for a cell that started in the previous fragmentainer is not trivial + // since it started in the previous fragmentainer, its breakpoint must be of type ADVANCE_WITH_CHILDREN and thus + // children info will be available at startFrag + // however, breakpoints set in the new iteration can be at i, in case of a cell with 3 or more rows (first row + // in prev frag, second row is startFrag, and third row is i); thus, we need to detach the traversing from the + // previous frag iteration from the current + BreakpointTraverser breakpointsForCell; + if (not input.breakpointTraverser.isDeactivated()) { + // in case of headers or footers, breakpoints would have been deactivated + if (startFrag < cell.anchorIdx.y) + breakpointsForCell = input.breakpointTraverser.traverseInsideUsingIthChildToJthParallelFlow(i - breakpointIndexOffset, j); + else + breakpointsForCell = BreakpointTraverser{ + input.breakpointTraverser.traversePrev(startFrag - breakpointIndexOffset, j), + input.breakpointTraverser.traverseCurr(i - breakpointIndexOffset, j), + }; + } - currPositionX += spacing.x; - currPositionY += spacing.y; - // cells - for (usize i = 0; i < grid.size.y; currPositionY += rowHeight[i] + spacing.y, i++) { - Px innnerCurrPositionX = Px{currPositionX}; - for (usize j = 0; j < grid.size.x; innnerCurrPositionX += colWidth[j] + spacing.x, j++) { - auto cell = grid.get(j, i); + // if the box started being rendered in the previous fragment, + // - its started position must be row starting the fragment + // - its size cant be the one computed in the build phase, thus is NONE + auto rowSpan = cell.box->attrs.rowSpan; + bool boxStartedInPrevFragment = tree.fc.allowBreak() and breakpointsForCell.prevIteration; + Px startPositionY = startPositionOfRow[boxStartedInPrevFragment ? startFrag : cell.anchorIdx.y]; + Opt verticalSize = + boxStartedInPrevFragment + ? Opt{NONE} + : rowHeightPref.query(cell.anchorIdx.y, cell.anchorIdx.y + rowSpan - 1) + spacing.y * Px{rowSpan - 1}; + + // TODO: In CSS 2.2, the height of a cell box is the minimum + // height required by the content. + // The table cell's 'height' property can influence + // the height of the row (see above), but it does not + // increase the height of the cell box. + // + // (See https://www.w3.org/TR/CSS22/tables.html#height-layout) + auto colSpan = cell.box->attrs.colSpan; + auto outputCell = layout( + tree, + *cell.box, + { + .fragment = input.fragment, + .knownSize = { + colWidthPref.query(j, j + colSpan - 1) + spacing.x * Px{colSpan - 1}, + verticalSize, + }, + .position = {currPositionX, startPositionY}, + .breakpointTraverser = breakpointsForCell, + .pendingVerticalSizes = input.pendingVerticalSizes, + } + ); - if (cell.anchorIdx != Math::Vec2u{j, i}) - continue; + if (tree.fc.isDiscoveryMode()) { + if (cellBox->style->break_->inside == BreakInside::AVOID) { + outputCell.breakpoint->applyAvoid(); + } + } - auto colSpan = cell.box->attrs.colSpan; - auto rowSpan = cell.box->attrs.rowSpan; + return { + outputCell, + startPositionY + outputCell.height() - startPositionOfRow[i] + }; + } - // TODO: In CSS 2.2, the height of a cell box is the minimum - // height required by the content. - // The table cell's 'height' property can influence - // the height of the row (see above), but it does not - // increase the height of the cell box. - // - // (See https://www.w3.org/TR/CSS22/tables.html#height-layout) - layout( - tree, - *cell.box, - { - .fragment = input.fragment, - .knownSize = { - colWidthPref.query(j, j + colSpan - 1) + spacing.x * Px{colSpan - 1}, - rowHeightPref.query(i, i + rowSpan - 1) + spacing.y * Px{rowSpan - 1} - }, - .position = {innnerCurrPositionX, currPositionY}, - } - ); - }; + struct RowOutput { + Px sizeY = 0_px; + + bool allBottomsAndCompletelyLaidOut = true; + bool someBottomsUncompleteLaidOut = false; + + Vec> breakpoints = {}; + Vec isBottom = {}; + }; + + RowOutput layoutRow(Tree &tree, Input input, usize startFrag, usize i, Vec2Px currPosition, bool isBreakpointedRow, usize breakpointIndexOffset = 0) { + startPositionOfRow[i] = currPosition.y; + + RowOutput outputRow; + if (tree.fc.isDiscoveryMode()) { + outputRow.breakpoints = Buf>::init(grid.size.x, NONE); + outputRow.isBottom = Buf::init(grid.size.x, false); } + + currPosition.x += spacing.x; + for (usize j = 0; j < grid.size.x; currPosition.x += colWidth[j] + spacing.x, j++) { + auto cell = grid.get(j, i); + auto cellBox = grid.get(cell.anchorIdx.x, cell.anchorIdx.y).box; + + if (cell.anchorIdx.x != j) + continue; + + bool isBottomCell = cell.anchorIdx.y + cellBox->attrs.rowSpan - 1 == i; + + if (not tree.fc.isDiscoveryMode() and not(isBottomCell or isBreakpointedRow)) { + continue; + } + + auto [outputCell, cellHeight] = layoutCell(tree, input, cell, cellBox, startFrag, i, j, currPosition.x, breakpointIndexOffset); + + if (tree.fc.isDiscoveryMode()) { + if (isBottomCell) + outputRow.sizeY = max(outputRow.sizeY, cellHeight); + + outputRow.breakpoints[j] = outputCell.breakpoint; + outputRow.isBottom[j] = isBottomCell; + outputRow.allBottomsAndCompletelyLaidOut &= isBottomCell and outputCell.completelyLaidOut; + } else { + outputRow.sizeY = max(outputRow.sizeY, cellHeight); + } + outputRow.someBottomsUncompleteLaidOut |= isBottomCell and not outputCell.completelyLaidOut; + }; + + return outputRow; } - Output run(Tree &tree, Input input) { - Px currPositionY{input.position.y}; - if (input.fragment) { - runTableBox(tree, input, currPositionY); + // https://www.w3.org/TR/css-tables-3/#freely-fragmentable + bool isFreelyFragmentableRow(usize i, Vec2Px fragmentainerSize) { + /* + NOT freely fragmentable if (AND): + - if the cells spanning the row do not span any subsequent row + - all cells are row span = 1 + - their height is at least twice smaller than both the fragmentainer height and width + - height <= min(frag.height, frag.width) / 2 + + it is freely fragmentable if at least one cell has row span > 1 OR height > min(frag.height, frag.width) / 2 + */ + + bool isSelfContainedRow = true; + for (usize j = 0; j < grid.size.x; ++j) { + if (grid.get(j, i).anchorIdx != Math::Vec2u{j, i} or grid.get(j, i).box->attrs.rowSpan != 1) { + isSelfContainedRow = false; + break; + } } - return Output::fromSize({ - tableUsedWidth, - tableBoxSize.y, - }); + return not isSelfContainedRow or + rowHeight[i] * 2_px > min(fragmentainerSize.x, fragmentainerSize.y); + } + + bool handlePossibleForcedBreakpointAfterRow(Breakpoint ¤tBreakpoint, bool allBottomsAndCompletelyLaidOut, bool isLastRow, usize i) { + if (allBottomsAndCompletelyLaidOut and not isLastRow) { + // if row is self contained, the it belongs to has size 1 + bool forcedBreakAfterCurrRow = + rowHelper[i].axisIdx and + rows[rowHelper[i].axisIdx.unwrap()].el.style->break_->after == BreakBetween::PAGE; + + bool forcedBreakBeforeNextRow = + i + 1 <= dataRowsInterval.y and + rowHelper[i + 1].axisIdx and + rows[rowHelper[i + 1].axisIdx.unwrap()].el.style->break_->before == BreakBetween::PAGE; + + bool limitOfCurrRowGroup = i + 1 <= dataRowsInterval.y and rowHelper[i].groupIdx != rowHelper[i + 1].groupIdx; + + bool forcedBreakAfterCurrRowGroup = + limitOfCurrRowGroup and + rowHelper[i].groupIdx and + rowGroups[rowHelper[i].groupIdx.unwrap()].el.style->break_->after == BreakBetween::PAGE; + + bool forcedBreakBeforeNextRowGroup = + limitOfCurrRowGroup and + i + 1 <= dataRowsInterval.y and + rowHelper[i + 1].groupIdx and + rowGroups[rowHelper[i + 1].groupIdx.unwrap()].el.style->break_->before == BreakBetween::PAGE; + + if (forcedBreakAfterCurrRow or forcedBreakBeforeNextRow or + forcedBreakAfterCurrRowGroup or forcedBreakBeforeNextRowGroup) { + + currentBreakpoint = Breakpoint::buildForced(i + 1); + + return false; + } + } + + return true; + } + + bool handleUnforcedBreakpointsInsideAndAfterRow(Breakpoint ¤tBreakpoint, RowOutput outputRow, usize i, Vec2Px fragmentainerSize) { + bool rowIsFreelyFragmentable = isFreelyFragmentableRow(i, fragmentainerSize); + + bool avoidBreakInsideTable = tableBox.style->break_->inside == BreakInside::AVOID; + + bool avoidBreakInsideRow = + rowHelper[i].axisIdx and + rows[rowHelper[i].axisIdx.unwrap()].el.style->break_->inside == BreakInside::AVOID; + + bool avoidBreakInsideRowGroup = + rowHelper[i].groupIdx and + rowGroups[rowHelper[i].groupIdx.unwrap()].el.style->break_->inside == BreakInside::AVOID; + + if (rowIsFreelyFragmentable) { + // breakpoint inside of row, take in consideration ALL breakpoints + // should stay in this row next fragmentation + currentBreakpoint.overrideIfBetter(Breakpoint::buildFromChildren( + outputRow.breakpoints, + i + 1, + avoidBreakInsideRow or avoidBreakInsideRowGroup or avoidBreakInsideTable, + false + )); + } + + // we need to abort layout if we cannot fit cells on their last row + if (outputRow.someBottomsUncompleteLaidOut) + return false; + + if (not outputRow.allBottomsAndCompletelyLaidOut) { + // breakpoint outside of row, but taking into consideration ONLY breakpoints of cells which are not + // in their bottom row + // since someBottomsUncompleteLaidOut is False, all bottom cells were able to be completed and their + // computed breakpoints can be disregarded + for (usize j = 0; j < grid.size.x; j++) { + if (outputRow.isBottom[j]) + outputRow.breakpoints[j] = NONE; + } + + currentBreakpoint.overrideIfBetter(Breakpoint::buildFromChildren( + outputRow.breakpoints, + i + 1, + avoidBreakInsideRow or avoidBreakInsideRowGroup or avoidBreakInsideTable, + true + )); + + } else { + + // no cells are being split + currentBreakpoint.overrideIfBetter(Breakpoint::buildClassB( + i + 1, + avoidBreakInsideTable + )); + } + + return true; + } + + Tuple> layoutRows(Tree &tree, Input input, usize startAt, usize stopAt, Px currPositionX, Px &currPositionY, bool shouldRepeatHeaderAndFooter) { + bool completelyLaidOut = false; + Opt rowBreakpoint = NONE; + + if (tree.fc.isDiscoveryMode()) { + completelyLaidOut = true; + rowBreakpoint = Breakpoint(); + + if (shouldRepeatHeaderAndFooter) + input = input.addPendingVerticalSize(footerSize.y); + } + + for (usize i = startAt; i < stopAt; i++) { + auto rowOutput = layoutRow( + tree, input, startAt, + i, + Vec2Px{currPositionX, currPositionY}, + i + 1 == stopAt, + shouldRepeatHeaderAndFooter ? dataRowsInterval.x : 0 + ); + + currPositionY += rowOutput.sizeY + spacing.y; + + if (tree.fc.isDiscoveryMode()) { + if ( + not handleUnforcedBreakpointsInsideAndAfterRow(rowBreakpoint.unwrap(), rowOutput, i, tree.fc.defaultSize) or + not handlePossibleForcedBreakpointAfterRow(rowBreakpoint.unwrap(), rowOutput.allBottomsAndCompletelyLaidOut, (i + 1 == stopAt), i) + ) { + completelyLaidOut = false; + break; + } + } else if (i == (shouldRepeatHeaderAndFooter ? dataRowsInterval.y : grid.size.y - 1)) { + completelyLaidOut = not rowOutput.someBottomsUncompleteLaidOut; + } + } + return {completelyLaidOut, rowBreakpoint}; + } + + void layoutHeaderFooterRows(Tree &tree, Input input, usize startFrag, Px currPositionX, Px &currPositionY, usize start, usize len) { + for (usize i = 0; i < len; i++) { + auto _ = layoutRow( + tree, + input.withBreakpointTraverser(BreakpointTraverser()), + startFrag, start + i, + Vec2Px{currPositionX, currPositionY}, false + ); + currPositionY += rowHeight[i] + spacing.y; + } + } + + Tuple computeLayoutIntervals(Tree &tree, bool shouldRepeatHeaderAndFooter, usize startAtTable, Opt stopAtTable) { + usize startAt = startAtTable + (shouldRepeatHeaderAndFooter ? dataRowsInterval.x : 0); + usize stopAt; + if (tree.fc.isDiscoveryMode()) { + stopAt = shouldRepeatHeaderAndFooter + ? dataRowsInterval.y + 1 + : grid.size.y; + } else { + stopAt = shouldRepeatHeaderAndFooter + ? dataRowsInterval.x + stopAtTable.unwrapOr(dataRowsInterval.y - dataRowsInterval.x + 1) + : stopAtTable.unwrapOr(grid.size.y); + } + + return {startAt, stopAt}; + } + + Output run(Tree &tree, Input input, usize startAtTable, Opt stopAtTable) { + + // TODO: in every row, at least one cell must be an anchor, or else this row is 'skipable' + + // if shouldRepeatHeaderAndFooter, header and footer are never alone in the fragmentainer and we wont set + // breakpoints on them; + // otherwise, they only appear once, might be alone in the fragmentainer and can be broken into pages + bool shouldRepeatHeaderAndFooter = + tree.fc.allowBreak() and + max(headerSize.y, footerSize.y) * 4_px <= tree.fc.defaultSize.y and + headerSize.y + footerSize.y * 2_px <= tree.fc.defaultSize.y; + + Px currPositionX{input.position.x}, currPositionY{input.position.y}; + Px startingPositionY = currPositionY; + currPositionY += spacing.y; + + auto [startAt, stopAt] = computeLayoutIntervals( + tree, shouldRepeatHeaderAndFooter, startAtTable, stopAtTable + ); + + if (shouldRepeatHeaderAndFooter) + layoutHeaderFooterRows( + tree, input, + startAt, + currPositionX, currPositionY, + 0, numOfHeaderRows + ); + + auto [completelyLaidOut, breakpoint] = layoutRows( + tree, input, + startAt, stopAt, + currPositionX, currPositionY, + shouldRepeatHeaderAndFooter + ); + + if (tree.fc.isDiscoveryMode() and shouldRepeatHeaderAndFooter) { + breakpoint.unwrap().endIdx -= dataRowsInterval.x; + } + + if (shouldRepeatHeaderAndFooter) + layoutHeaderFooterRows( + tree, input, + startAt, + currPositionX, currPositionY, + grid.size.y - numOfFooterRows, numOfFooterRows + ); + + return Output{ + .size = {tableUsedWidth, currPositionY - startingPositionY}, + .completelyLaidOut = completelyLaidOut, + .breakpoint = tree.fc.isDiscoveryMode() ? Opt{breakpoint} : NONE, + }; } }; -Output tableLayout(Tree &tree, Box &box, Input input) { +Output tableLayout(Tree &tree, Box &box, Input input, usize startAt, Opt stopAt) { // TODO: - vertical and horizontal alignment // - borders collapse TableFormatingContext table(tree, box); table.build(tree, input); - return table.run(tree, input); + return table.run(tree, input, startAt, stopAt); } } // namespace Vaev::Layout diff --git a/src/web/vaev-layout/table.h b/src/web/vaev-layout/table.h index 86691a7..437d4ef 100644 --- a/src/web/vaev-layout/table.h +++ b/src/web/vaev-layout/table.h @@ -2,8 +2,10 @@ #include "input_output.h" +#include "tree.h" + namespace Vaev::Layout { -Output tableLayout(Tree &tree, Box &box, Input input); +Output tableLayout(Tree &tree, Box &box, Input input, usize startAt, Opt stopAt); } // namespace Vaev::Layout