TableFormattingContext.cpp [plain text]
#include "config.h"
#include "TableFormattingContext.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "BlockFormattingState.h"
#include "DisplayBox.h"
#include "FloatingState.h"
#include "InlineFormattingState.h"
#include "InvalidationState.h"
#include "LayoutBox.h"
#include "LayoutChildIterator.h"
#include "LayoutContext.h"
#include "LayoutInitialContainingBlock.h"
#include "TableFormattingState.h"
#include <wtf/IsoMallocInlines.h>
namespace WebCore {
namespace Layout {
WTF_MAKE_ISO_ALLOCATED_IMPL(TableFormattingContext);
TableFormattingContext::TableFormattingContext(const ContainerBox& formattingContextRoot, TableFormattingState& formattingState)
: FormattingContext(formattingContextRoot, formattingState)
{
}
void TableFormattingContext::layoutInFlowContent(InvalidationState&, const ConstraintsForInFlowContent& constraints)
{
auto availableHorizontalSpace = constraints.horizontal.logicalWidth;
auto availableVerticalSpace = constraints.vertical.logicalHeight;
computeAndDistributeExtraSpace(availableHorizontalSpace, availableVerticalSpace);
setUsedGeometryForCells(availableHorizontalSpace);
setUsedGeometryForRows(availableHorizontalSpace);
setUsedGeometryForSections(constraints);
}
void TableFormattingContext::setUsedGeometryForCells(LayoutUnit availableHorizontalSpace)
{
auto& grid = formattingState().tableGrid();
auto& columnList = grid.columns().list();
auto& rowList = grid.rows().list();
auto sectionOffset = LayoutUnit { };
auto* currentSection = &rowList.first().box().parent();
for (auto& cell : grid.cells()) {
auto& cellBox = cell->box();
auto& cellDisplayBox = formattingState().displayBox(cellBox);
auto& section = rowList[cell->startRow()].box().parent();
if (§ion != currentSection) {
currentSection = §ion;
sectionOffset = rowList[cell->startRow()].logicalTop();
}
cellDisplayBox.setTop(rowList[cell->startRow()].logicalTop() - sectionOffset);
cellDisplayBox.setLeft(columnList[cell->startColumn()].logicalLeft());
auto availableVerticalSpace = rowList[cell->startRow()].logicalHeight();
for (size_t rowIndex = cell->startRow() + 1; rowIndex < cell->endRow(); ++rowIndex)
availableVerticalSpace += rowList[rowIndex].logicalHeight();
availableVerticalSpace += (cell->rowSpan() - 1) * grid.verticalSpacing();
layoutCell(*cell, availableHorizontalSpace, availableVerticalSpace);
auto paddingTop = cellDisplayBox.paddingTop().valueOr(LayoutUnit { });
auto paddingBottom = cellDisplayBox.paddingBottom().valueOr(LayoutUnit { });
auto intrinsicPaddingTop = LayoutUnit { };
auto intrinsicPaddingBottom = LayoutUnit { };
switch (cellBox.style().verticalAlign()) {
case VerticalAlign::Middle: {
auto intrinsicVerticalPadding = std::max(0_lu, availableVerticalSpace - cellDisplayBox.verticalMarginBorderAndPadding() - cellDisplayBox.contentBoxHeight());
intrinsicPaddingTop = intrinsicVerticalPadding / 2;
intrinsicPaddingBottom = intrinsicVerticalPadding / 2;
break;
}
case VerticalAlign::Baseline: {
auto rowBaselineOffset = LayoutUnit { rowList[cell->startRow()].baselineOffset() };
auto cellBaselineOffset = LayoutUnit { cell->baselineOffset() };
intrinsicPaddingTop = std::max(0_lu, rowBaselineOffset - cellBaselineOffset - cellDisplayBox.borderTop());
intrinsicPaddingBottom = std::max(0_lu, availableVerticalSpace - cellDisplayBox.verticalMarginBorderAndPadding() - intrinsicPaddingTop - cellDisplayBox.contentBoxHeight());
break;
}
default:
ASSERT_NOT_IMPLEMENTED_YET();
break;
}
if (intrinsicPaddingTop && cellBox.hasInFlowOrFloatingChild()) {
auto adjustCellContentWithInstrinsicPaddingBefore = [&] {
auto& formattingState = layoutState().establishedFormattingState(cellBox);
for (auto* child = cellBox.firstInFlowOrFloatingChild(); child; child = child->nextInFlowOrFloatingSibling()) {
if (child->isAnonymous() || child->isLineBreakBox())
continue;
formattingState.displayBox(*child).moveVertically(intrinsicPaddingTop);
}
if (cellBox.establishesInlineFormattingContext()) {
auto& displayContent = layoutState().establishedInlineFormattingState(cellBox).ensureDisplayInlineContent();
for (auto& run : displayContent.runs)
run.moveVertically(intrinsicPaddingTop);
for (auto& lineBox : displayContent.lineBoxes)
lineBox.moveVertically(intrinsicPaddingTop);
}
};
adjustCellContentWithInstrinsicPaddingBefore();
}
cellDisplayBox.setVerticalPadding({ paddingTop + intrinsicPaddingTop, paddingBottom + intrinsicPaddingBottom });
}
}
void TableFormattingContext::setUsedGeometryForRows(LayoutUnit availableHorizontalSpace)
{
auto& grid = formattingState().tableGrid();
auto& rows = grid.rows().list();
auto rowLogicalTop = grid.verticalSpacing();
const ContainerBox* previousRow = nullptr;
for (size_t rowIndex = 0; rowIndex < rows.size(); ++rowIndex) {
auto& row = rows[rowIndex];
auto& rowBox = row.box();
auto& rowDisplayBox = formattingState().displayBox(rowBox);
rowDisplayBox.setPadding(geometry().computedPadding(rowBox, availableHorizontalSpace));
rowDisplayBox.setHorizontalMargin({ });
rowDisplayBox.setVerticalMargin({ });
auto computedRowBorder = [&] {
auto border = geometry().computedBorder(rowBox);
if (!grid.collapsedBorder())
return border;
border.horizontal = { };
if (!rowIndex)
border.vertical.top = { };
if (rowIndex == rows.size() - 1)
border.vertical.bottom = { };
return border;
}();
if (computedRowBorder.height() > row.logicalHeight()) {
computedRowBorder.vertical.top = { };
computedRowBorder.vertical.bottom = { };
}
rowDisplayBox.setContentBoxHeight(row.logicalHeight() - computedRowBorder.height());
auto rowLogicalWidth = grid.columns().logicalWidth() + 2 * grid.horizontalSpacing();
if (computedRowBorder.width() > rowLogicalWidth) {
computedRowBorder.horizontal.left = { };
computedRowBorder.horizontal.right = { };
}
rowDisplayBox.setContentBoxWidth(rowLogicalWidth - computedRowBorder.width());
rowDisplayBox.setBorder(computedRowBorder);
if (previousRow && &previousRow->parent() != &rowBox.parent()) {
rowLogicalTop = { };
}
rowDisplayBox.setTop(rowLogicalTop);
rowDisplayBox.setLeft({ });
rowLogicalTop += row.logicalHeight() + grid.verticalSpacing();
previousRow = &rowBox;
}
auto& columns = grid.columns();
Vector<InlineLayoutUnit> rowBaselines(rows.size(), 0);
for (size_t rowIndex = 0; rowIndex < rows.size(); ++rowIndex) {
for (size_t columnIndex = 0; columnIndex < columns.size(); ++columnIndex) {
auto& slot = *grid.slot({ columnIndex, rowIndex });
if (slot.isRowSpanned())
continue;
if (slot.hasRowSpan())
continue;
auto& cell = slot.cell();
rowBaselines[rowIndex] = std::max(rowBaselines[rowIndex], cell.baselineOffset());
}
}
for (size_t rowIndex = 0; rowIndex < rows.size(); ++rowIndex)
rows[rowIndex].setBaselineOffset(rowBaselines[rowIndex]);
}
void TableFormattingContext::setUsedGeometryForSections(const ConstraintsForInFlowContent& constraints)
{
auto& grid = formattingState().tableGrid();
auto& tableBox = root();
auto sectionWidth = grid.columns().logicalWidth() + 2 * grid.horizontalSpacing();
auto logicalTop = constraints.vertical.logicalTop;
auto verticalSpacing = grid.verticalSpacing();
auto paddingBefore = Optional<LayoutUnit> { verticalSpacing };
auto paddingAfter = verticalSpacing;
for (auto& sectionBox : childrenOfType<ContainerBox>(tableBox)) {
auto& sectionDisplayBox = formattingState().displayBox(sectionBox);
sectionDisplayBox.setBorder({ });
sectionDisplayBox.setPadding(Edges { { }, { paddingBefore.valueOr(0_lu), paddingAfter } });
paddingBefore = WTF::nullopt;
sectionDisplayBox.setHorizontalMargin({ });
sectionDisplayBox.setVerticalMargin({ });
sectionDisplayBox.setContentBoxWidth(sectionWidth);
auto sectionContentHeight = LayoutUnit { };
size_t rowCount = 0;
for (auto& rowBox : childrenOfType<ContainerBox>(sectionBox)) {
sectionContentHeight += geometryForBox(rowBox).height();
++rowCount;
}
sectionContentHeight += verticalSpacing * (rowCount - 1);
sectionDisplayBox.setContentBoxHeight(sectionContentHeight);
sectionDisplayBox.setLeft(constraints.horizontal.logicalLeft);
sectionDisplayBox.setTop(logicalTop);
logicalTop += sectionDisplayBox.height();
}
}
void TableFormattingContext::layoutCell(const TableGrid::Cell& cell, LayoutUnit availableHorizontalSpace, Optional<LayoutUnit> usedCellHeight)
{
ASSERT(cell.box().establishesBlockFormattingContext());
auto& grid = formattingState().tableGrid();
auto& cellBox = cell.box();
auto& cellDisplayBox = formattingState().displayBox(cellBox);
cellDisplayBox.setBorder(geometry().computedCellBorder(cell));
cellDisplayBox.setPadding(geometry().computedPadding(cellBox, availableHorizontalSpace));
cellDisplayBox.setHorizontalMargin({ });
cellDisplayBox.setVerticalMargin({ });
auto availableSpaceForContent = [&] {
auto& columnList = grid.columns().list();
auto logicalWidth = LayoutUnit { };
for (auto columnIndex = cell.startColumn(); columnIndex < cell.endColumn(); ++columnIndex)
logicalWidth += columnList.at(columnIndex).logicalWidth();
logicalWidth += (cell.columnSpan() - 1) * grid.horizontalSpacing();
return logicalWidth - cellDisplayBox.horizontalMarginBorderAndPadding();
}();
cellDisplayBox.setContentBoxWidth(availableSpaceForContent);
if (cellBox.hasInFlowOrFloatingChild()) {
auto constraintsForCellContent = geometry().constraintsForInFlowContent(cellBox);
constraintsForCellContent.vertical.logicalHeight = usedCellHeight;
auto invalidationState = InvalidationState { };
auto& floatingStateForCellContent = layoutState().ensureBlockFormattingState(cellBox).floatingState();
floatingStateForCellContent.clear();
LayoutContext::createFormattingContext(cellBox, layoutState())->layoutInFlowContent(invalidationState, constraintsForCellContent);
}
cellDisplayBox.setContentBoxHeight(geometry().cellHeigh(cellBox));
}
FormattingContext::IntrinsicWidthConstraints TableFormattingContext::computedIntrinsicWidthConstraints()
{
auto& grid = formattingState().tableGrid();
if (auto computedWidthConstraints = grid.widthConstraints())
return *computedWidthConstraints;
auto computedWidthConstraints = computedPreferredWidthForColumns();
grid.setWidthConstraints(computedWidthConstraints);
return computedWidthConstraints;
}
UniqueRef<TableGrid> TableFormattingContext::ensureTableGrid(const ContainerBox& tableBox)
{
auto tableGrid = makeUniqueRef<TableGrid>();
auto& tableStyle = tableBox.style();
auto shouldApplyBorderSpacing = tableStyle.borderCollapse() == BorderCollapse::Separate;
tableGrid->setHorizontalSpacing(LayoutUnit { shouldApplyBorderSpacing ? tableStyle.horizontalBorderSpacing() : 0 });
tableGrid->setVerticalSpacing(LayoutUnit { shouldApplyBorderSpacing ? tableStyle.verticalBorderSpacing() : 0 });
auto* firstChild = tableBox.firstChild();
if (!firstChild) {
return tableGrid;
}
const Box* tableCaption = nullptr;
const Box* colgroup = nullptr;
if (firstChild->isTableCaption())
tableCaption = firstChild;
auto* colgroupCandidate = firstChild;
if (tableCaption)
colgroupCandidate = tableCaption->nextSibling();
if (colgroupCandidate->isTableColumnGroup())
colgroup = colgroupCandidate;
if (colgroup) {
auto& columns = tableGrid->columns();
for (auto* column = downcast<ContainerBox>(*colgroup).firstChild(); column; column = column->nextSibling()) {
ASSERT(column->isTableColumn());
auto columnSpanCount = column->columnSpan();
ASSERT(columnSpanCount > 0);
while (columnSpanCount--)
columns.addColumn(downcast<ContainerBox>(*column));
}
}
auto* firstSection = colgroup ? colgroup->nextSibling() : tableCaption ? tableCaption->nextSibling() : firstChild;
for (auto* section = firstSection; section; section = section->nextSibling()) {
ASSERT(section->isTableHeader() || section->isTableBody() || section->isTableFooter());
for (auto* row = downcast<ContainerBox>(*section).firstChild(); row; row = row->nextSibling()) {
ASSERT(row->isTableRow());
for (auto* cell = downcast<ContainerBox>(*row).firstChild(); cell; cell = cell->nextSibling()) {
ASSERT(cell->isTableCell());
tableGrid->appendCell(downcast<ContainerBox>(*cell));
}
}
}
return tableGrid;
}
FormattingContext::IntrinsicWidthConstraints TableFormattingContext::computedPreferredWidthForColumns()
{
auto& formattingState = this->formattingState();
auto& grid = formattingState.tableGrid();
ASSERT(!grid.widthConstraints());
for (auto& cell : grid.cells()) {
auto& cellBox = cell->box();
ASSERT(cellBox.establishesBlockFormattingContext());
auto intrinsicWidth = formattingState.intrinsicWidthConstraintsForBox(cellBox);
if (!intrinsicWidth) {
intrinsicWidth = geometry().intrinsicWidthConstraintsForCell(*cell);
formattingState.setIntrinsicWidthConstraintsForBox(cellBox, *intrinsicWidth);
}
grid.slot(cell->position())->setWidthConstraints(*intrinsicWidth);
}
auto& columnList = grid.columns().list();
Vector<Optional<LayoutUnit>> fixedWidthColumns;
for (auto& column : columnList) {
auto fixedWidth = [&] () -> Optional<LayoutUnit> {
auto* columnBox = column.box();
if (!columnBox) {
return { };
}
if (auto width = columnBox->columnWidth())
return width;
return geometry().computedColumnWidth(*columnBox);
};
fixedWidthColumns.append(fixedWidth());
}
Vector<FormattingContext::IntrinsicWidthConstraints> columnIntrinsicWidths(columnList.size());
Vector<SlotPosition> spanningCellPositionList;
size_t numberOfActualColumns = 0;
for (size_t columnIndex = 0; columnIndex < columnList.size(); ++columnIndex) {
auto columnHasNonSpannedCell = false;
for (size_t rowIndex = 0; rowIndex < grid.rows().size(); ++rowIndex) {
auto& slot = *grid.slot({ columnIndex, rowIndex });
if (slot.isColumnSpanned())
continue;
columnHasNonSpannedCell = true;
if (slot.hasColumnSpan()) {
spanningCellPositionList.append({ columnIndex, rowIndex });
continue;
}
auto columnFixedWidth = fixedWidthColumns[columnIndex];
auto widthConstraints = !columnFixedWidth ? slot.widthConstraints() : FormattingContext::IntrinsicWidthConstraints { *columnFixedWidth, *columnFixedWidth };
columnIntrinsicWidths[columnIndex].minimum = std::max(widthConstraints.minimum, columnIntrinsicWidths[columnIndex].minimum);
columnIntrinsicWidths[columnIndex].maximum = std::max(widthConstraints.maximum, columnIntrinsicWidths[columnIndex].maximum);
}
if (columnHasNonSpannedCell)
++numberOfActualColumns;
}
for (auto spanningCellPosition : spanningCellPositionList) {
auto& slot = *grid.slot(spanningCellPosition);
auto& cell = slot.cell();
ASSERT(slot.hasColumnSpan());
auto widthConstraintsToDistribute = slot.widthConstraints();
for (size_t columnSpanIndex = cell.startColumn(); columnSpanIndex < cell.endColumn(); ++columnSpanIndex)
widthConstraintsToDistribute -= columnIntrinsicWidths[columnSpanIndex];
widthConstraintsToDistribute -= (cell.columnSpan() - 1) * grid.horizontalSpacing();
widthConstraintsToDistribute.minimum = std::max(LayoutUnit { }, widthConstraintsToDistribute.minimum / cell.columnSpan());
widthConstraintsToDistribute.maximum = std::max(LayoutUnit { }, widthConstraintsToDistribute.maximum / cell.columnSpan());
if (widthConstraintsToDistribute.minimum || widthConstraintsToDistribute.maximum) {
for (size_t columnSpanIndex = cell.startColumn(); columnSpanIndex < cell.endColumn(); ++columnSpanIndex)
columnIntrinsicWidths[columnSpanIndex] += widthConstraintsToDistribute;
}
}
auto tableWidthConstraints = IntrinsicWidthConstraints { };
for (auto& columnIntrinsicWidth : columnIntrinsicWidths)
tableWidthConstraints += columnIntrinsicWidth;
tableWidthConstraints += (numberOfActualColumns + 1) * grid.horizontalSpacing();
return tableWidthConstraints;
}
void TableFormattingContext::computeAndDistributeExtraSpace(LayoutUnit availableHorizontalSpace, Optional<LayoutUnit> availableVerticalSpace)
{
auto& grid = formattingState().tableGrid();
auto& columns = grid.columns().list();
auto tableLayout = this->tableLayout();
auto distributedHorizontalSpaces = tableLayout.distributedHorizontalSpace(availableHorizontalSpace);
ASSERT(distributedHorizontalSpaces.size() == columns.size());
auto columnLogicalLeft = grid.horizontalSpacing();
for (size_t columnIndex = 0; columnIndex < columns.size(); ++columnIndex) {
auto& column = columns[columnIndex];
column.setLogicalLeft(columnLogicalLeft);
column.setLogicalWidth(distributedHorizontalSpaces[columnIndex]);
columnLogicalLeft += distributedHorizontalSpaces[columnIndex] + grid.horizontalSpacing();
}
auto& rows = grid.rows().list();
for (size_t rowIndex = 0; rowIndex < rows.size(); ++rowIndex) {
for (size_t columnIndex = 0; columnIndex < columns.size(); ++columnIndex) {
auto& slot = *grid.slot({ columnIndex, rowIndex });
if (slot.isRowSpanned())
continue;
layoutCell(slot.cell(), availableHorizontalSpace);
if (slot.hasRowSpan())
continue;
auto& cell = slot.cell();
cell.setBaselineOffset(geometry().usedBaselineForCell(cell.box()));
}
}
auto distributedVerticalSpaces = tableLayout.distributedVerticalSpace(availableVerticalSpace);
ASSERT(distributedVerticalSpaces.size() == rows.size());
auto rowLogicalTop = grid.verticalSpacing();
for (size_t rowIndex = 0; rowIndex < rows.size(); ++rowIndex) {
auto& row = rows[rowIndex];
row.setLogicalHeight(distributedVerticalSpaces[rowIndex]);
row.setLogicalTop(rowLogicalTop);
rowLogicalTop += distributedVerticalSpaces[rowIndex] + grid.verticalSpacing();
}
}
}
}
#endif