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.
|
LibWeb: Make layout nodes refcounted
Move the layout tree from GC allocation to refcounted ownership so
removed layout and paint subtrees are destroyed synchronously instead
of waiting for the next GC sweep. This dramatically reduces GC memory
usage peaks after layout tree churn and makes it easier for memory use
to fall back after large document updates.
Update layout factories, tree traversal, SVG layout node creation,
paintable back-pointers, and pseudo-element layout links to use RefPtr
ownership.
Make display: contents follow the same shape as Blink and WebKit: the
element itself does not create a layout node, and its children are
flattened into the nearest layout parent. Wrap direct non-whitespace
text in an anonymous inline node when the boxless element contributes
inherited style to that text.
Use an internal inline wrapper for display: contents pseudo-elements
so generated content can still participate in layout, painting, hit
testing, and pseudo-element queries. Keep CSSOM reporting the computed
display value from the pseudo style, not the internal wrapper.
Remove the retained out-of-tree layout node list and its testing hook,
since the flattened model does not need a side owner for boxless
elements. Add coverage for inherited text style, dynamic insertion
order, pseudo-element hit testing, and computed style queries.
2026-06-07 17:50:33 +02: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) {
|
2026-05-22 14:23:31 +02:00
|
|
|
col_group.template for_each_in_subtree_of_type<Box>([&](auto& descendant_box) {
|
|
|
|
|
if (descendant_box.display().is_table_column()) {
|
2026-02-11 16:02:34 +00:00
|
|
|
u32 span = 1;
|
2026-05-22 14:23:31 +02:00
|
|
|
if (auto const* col_element = as_if<HTML::HTMLTableColElement>(descendant_box.dom_node()))
|
2026-02-11 16:02:34 +00:00
|
|
|
span = col_element->span();
|
|
|
|
|
x_width += span;
|
|
|
|
|
}
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|