2023-08-20 03:08:38 +00:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2023, the SerenityOS developers.
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <LibWeb/HTML/HTMLTableCellElement.h>
|
2023-11-04 04:08:59 +01:00
|
|
|
#include <LibWeb/HTML/HTMLTableColElement.h>
|
2023-08-20 03:08:38 +00:00
|
|
|
#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;
|
|
|
|
|
|
2025-11-27 12:49:51 +00:00
|
|
|
size_t x_width = 0;
|
|
|
|
|
size_t y_height = 0;
|
|
|
|
|
size_t y_current = 0;
|
|
|
|
|
size_t max_cell_x = 0;
|
|
|
|
|
size_t max_cell_y = 0;
|
2023-08-20 03:08:38 +00:00
|
|
|
|
|
|
|
|
// Implements https://html.spec.whatwg.org/multipage/tables.html#algorithm-for-processing-rows
|
2025-11-27 12:49:51 +00:00
|
|
|
auto process_row = [&table_grid, &cells, &rows, &x_width, &y_height, &y_current, &max_cell_x, &max_cell_y](Box const& row, Optional<Box&> row_group = {}) {
|
|
|
|
|
// 1. If yheight is equal to ycurrent, then increase yheight by 1. (ycurrent is never greater than yheight.)
|
2023-08-20 03:08:38 +00:00
|
|
|
if (y_height == y_current)
|
|
|
|
|
y_height++;
|
|
|
|
|
|
2025-11-27 12:49:51 +00:00
|
|
|
// 2. Let xcurrent be 0.
|
|
|
|
|
size_t x_current = 0;
|
2023-08-20 03:08:38 +00:00
|
|
|
|
2025-11-27 12:49:51 +00:00
|
|
|
// FIXME: 3. Run the algorithm for growing downward-growing cells.
|
|
|
|
|
|
|
|
|
|
// 4. If the tr element being processed has no td or th element children, then increase ycurrent by 1, abort
|
|
|
|
|
// this set of steps, and return to the algorithm above.
|
|
|
|
|
// NB: The remaining steps already accomplish the same thing in this case.
|
|
|
|
|
|
|
|
|
|
// 5. Let current cell be the first td or th element child in the tr element being processed.
|
2023-08-20 03:08:38 +00:00
|
|
|
for (auto* child = row.first_child(); child; child = child->next_sibling()) {
|
2025-11-27 12:49:51 +00:00
|
|
|
// NB: This actually applies to children with `display: table-cell`, not just td/th elements.
|
|
|
|
|
if (!child->display().is_table_cell())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
auto& current_cell = as<Box>(*child);
|
|
|
|
|
|
|
|
|
|
// 6. 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++;
|
|
|
|
|
|
|
|
|
|
// 7. If xcurrent is equal to xwidth, increase xwidth by 1. (xcurrent is never greater than xwidth.)
|
|
|
|
|
if (x_current == x_width)
|
|
|
|
|
x_width++;
|
|
|
|
|
|
|
|
|
|
// NB: Steps 8 and 9 are implemented in HTMLTableCellElement.col_span() and HTMLTableCellElement.row_spam() respectively.
|
|
|
|
|
size_t colspan = 1;
|
|
|
|
|
size_t rowspan = 1;
|
|
|
|
|
if (auto* table_cell = as_if<HTML::HTMLTableCellElement>(current_cell.dom_node())) {
|
|
|
|
|
colspan = table_cell->col_span();
|
|
|
|
|
rowspan = table_cell->row_span();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 10. Let cell grows downward be false.
|
|
|
|
|
auto cell_grows_downward = false;
|
|
|
|
|
|
|
|
|
|
// 11. If rowspan is zero, then set cell grows downward to true and set rowspan to 1.
|
|
|
|
|
if (rowspan == 0) {
|
|
|
|
|
cell_grows_downward = true;
|
|
|
|
|
rowspan = 1;
|
2023-08-20 03:08:38 +00:00
|
|
|
}
|
2025-11-27 12:49:51 +00:00
|
|
|
|
|
|
|
|
// 12. If xwidth < xcurrent+colspan, then let xwidth be xcurrent+colspan.
|
|
|
|
|
if (x_width < x_current + colspan)
|
|
|
|
|
x_width = x_current + colspan;
|
|
|
|
|
|
|
|
|
|
// 13. If yheight < ycurrent+rowspan, then let yheight be ycurrent+rowspan.
|
|
|
|
|
if (y_height < y_current + rowspan)
|
|
|
|
|
y_height = y_current + rowspan;
|
|
|
|
|
|
|
|
|
|
// 14. Let the slots with coordinates (x, y) such that xcurrent ≤ x < xcurrent+colspan and
|
|
|
|
|
// ycurrent ≤ y < ycurrent+rowspan be covered by a new cell c, anchored at (xcurrent, ycurrent),
|
|
|
|
|
// which has width colspan and height rowspan, corresponding to the current cell element.
|
|
|
|
|
// If the current cell element is a th element, let this new cell c be a header cell;
|
|
|
|
|
// otherwise, let it be a data cell.
|
|
|
|
|
// To establish which header cells apply to the current cell element, use the algorithm for
|
|
|
|
|
// assigning header cells described in the next section.
|
|
|
|
|
// If any of the slots involved already had a cell covering them, then this is a table model error.
|
|
|
|
|
// Those slots now have two cells overlapping.
|
|
|
|
|
// NB: We don't distinguish between header and data cells here.
|
|
|
|
|
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 { current_cell, x_current, y_current, colspan, rowspan });
|
|
|
|
|
max_cell_x = max(x_current, max_cell_x);
|
|
|
|
|
max_cell_y = max(y_current, max_cell_y);
|
|
|
|
|
|
|
|
|
|
// 15. If cell grows downward is true, then add the tuple {c, xcurrent, colspan} to the list of downward-growing cells.
|
|
|
|
|
if (cell_grows_downward) {
|
|
|
|
|
// FIXME: Add the tuple.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 16. Increase xcurrent by colspan.
|
|
|
|
|
x_current += colspan;
|
|
|
|
|
|
|
|
|
|
// NB: Step 17 is handled below, outside of this loop.
|
|
|
|
|
|
|
|
|
|
// 18. Let current cell be the next td or th element child in the tr element being processed.
|
|
|
|
|
// 19. Return to the step labeled cells.
|
|
|
|
|
// NB: Handled by the loop.
|
2023-08-20 03:08:38 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-27 12:49:51 +00:00
|
|
|
// 17. If current cell is the last td or th element child in the tr element being processed, then increase
|
|
|
|
|
// ycurrent by 1, abort this set of steps, and return to the algorithm above.
|
2025-08-04 13:34:59 +01:00
|
|
|
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),
|
|
|
|
|
});
|
2023-08-20 03:08:38 +00:00
|
|
|
y_current++;
|
|
|
|
|
};
|
|
|
|
|
|
2023-11-04 04:08:59 +01:00
|
|
|
auto process_col_group = [&](auto& col_group) {
|
|
|
|
|
auto dom_node = col_group.dom_node();
|
2025-07-01 16:32:56 +10:00
|
|
|
dom_node->for_each_in_subtree([&](auto& descendant) {
|
|
|
|
|
if (descendant.layout_node() && descendant.layout_node()->display().is_table_column())
|
|
|
|
|
x_width += 1;
|
2024-05-04 14:47:04 +01:00
|
|
|
return TraversalDecision::Continue;
|
2023-11-04 04:08:59 +01:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for_each_child_box_matching(box, is_table_column_group, [&](auto& column_group_box) {
|
|
|
|
|
process_col_group(column_group_box);
|
|
|
|
|
});
|
|
|
|
|
|
2025-02-26 12:23:05 +01:00
|
|
|
auto process_row_group = [&](auto& row_group) {
|
|
|
|
|
for_each_child_box_matching(row_group, is_table_row, [&](auto& row_box) {
|
2025-08-04 13:34:59 +01:00
|
|
|
process_row(row_box, row_group);
|
2023-08-20 03:08:38 +00:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
});
|
2025-02-26 12:23:05 +01:00
|
|
|
};
|
2023-08-20 03:08:38 +00:00
|
|
|
|
2025-02-26 12:23:05 +01:00
|
|
|
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);
|
2023-08-20 03:08:38 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|