ladybird/Libraries/LibWeb/Layout/TableGrid.cpp
Sam Atkins 7c8c05ee82 LibWeb/Layout: Support visibility:collapse on table-row elements
When a table row (or its group) is set to collapse, the row takes up no
vertical space in the layout.

We have to account for this in multiple places, so I've cached whether a
row is collapsed in the TableGrid::Row.
2025-08-11 11:07:47 +01:00

116 lines
4.2 KiB
C++

/*
* Copyright (c) 2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/HTML/HTMLTableCellElement.h>
#include <LibWeb/HTML/HTMLTableColElement.h>
#include <LibWeb/Layout/TableGrid.h>
namespace Web::Layout {
TableGrid TableGrid::calculate_row_column_grid(Box const& box, Vector<Cell>& cells, Vector<Row>& rows)
{
// Implements https://html.spec.whatwg.org/multipage/tables.html#forming-a-table
TableGrid table_grid;
size_t x_width = 0, y_height = 0;
size_t x_current = 0, y_current = 0;
size_t max_cell_x = 0, max_cell_y = 0;
// Implements https://html.spec.whatwg.org/multipage/tables.html#algorithm-for-processing-rows
auto process_row = [&](auto& row, Optional<Box&> row_group = {}) {
if (y_height == y_current)
y_height++;
x_current = 0;
for (auto* child = row.first_child(); child; child = child->next_sibling()) {
if (child->display().is_table_cell()) {
// Cells: While x_current is less than x_width and the slot with coordinate (x_current, y_current) already has a cell assigned to it, increase x_current by 1.
while (x_current < x_width && table_grid.m_occupancy_grid.contains(GridPosition { x_current, y_current }))
x_current++;
Box const* box = static_cast<Box const*>(child);
if (x_current == x_width)
x_width++;
size_t colspan = 1, rowspan = 1;
if (box->dom_node() && is<HTML::HTMLTableCellElement>(*box->dom_node())) {
auto const& node = static_cast<HTML::HTMLTableCellElement const&>(*box->dom_node());
colspan = node.col_span();
rowspan = node.row_span();
}
if (x_width < x_current + colspan)
x_width = x_current + colspan;
if (y_height < y_current + rowspan)
y_height = y_current + rowspan;
for (size_t y = y_current; y < y_current + rowspan; y++)
for (size_t x = x_current; x < x_current + colspan; x++)
table_grid.m_occupancy_grid.set(GridPosition { x, y }, true);
cells.append(Cell { *box, x_current, y_current, colspan, rowspan });
max_cell_x = max(x_current, max_cell_x);
max_cell_y = max(y_current, max_cell_y);
x_current += colspan;
}
}
rows.append(Row {
.box = row,
.is_collapsed = row.computed_values().visibility() == CSS::Visibility::Collapse
|| (row_group.has_value() && row_group->computed_values().visibility() == CSS::Visibility::Collapse),
});
y_current++;
};
auto process_col_group = [&](auto& col_group) {
auto dom_node = col_group.dom_node();
dom_node->for_each_in_subtree([&](auto& descendant) {
if (descendant.layout_node() && descendant.layout_node()->display().is_table_column())
x_width += 1;
return TraversalDecision::Continue;
});
};
for_each_child_box_matching(box, is_table_column_group, [&](auto& column_group_box) {
process_col_group(column_group_box);
});
auto process_row_group = [&](auto& row_group) {
for_each_child_box_matching(row_group, is_table_row, [&](auto& row_box) {
process_row(row_box, row_group);
return IterationDecision::Continue;
});
};
box.for_each_child_of_type<Box>([&](auto& child) {
if (is_table_row_group(child))
process_row_group(child);
else if (is_table_row(child))
process_row(child);
return IterationDecision::Continue;
});
table_grid.m_column_count = x_width;
for (auto& cell : cells) {
// Clip spans to the end of the table.
cell.row_span = min(cell.row_span, rows.size() - cell.row_index);
cell.column_span = min(cell.column_span, table_grid.m_column_count - cell.column_index);
}
return table_grid;
}
TableGrid TableGrid::calculate_row_column_grid(Box const& box)
{
Vector<Cell> cells;
Vector<Row> rows;
return calculate_row_column_grid(box, cells, rows);
}
}