From 268355c759f03e93d57d1abe69099ce821f9af6e Mon Sep 17 00:00:00 2001 From: Andi Gallo Date: Mon, 17 Jul 2023 01:22:11 +0000 Subject: [PATCH] LibWeb: Implement iterative percentage size for spanning table cells Follow the computing column measures section of the specification, which gives an algorithm for setting intrinsic percentage widths when spanning columns are involved. --- .../table/colspan-percentage-width.txt | 53 +++++ .../input/table/colspan-percentage-width.html | 20 ++ .../LibWeb/Layout/TableFormattingContext.cpp | 187 ++++++++++++++++-- .../LibWeb/Layout/TableFormattingContext.h | 14 ++ 4 files changed, 260 insertions(+), 14 deletions(-) create mode 100644 Tests/LibWeb/Layout/expected/table/colspan-percentage-width.txt create mode 100644 Tests/LibWeb/Layout/input/table/colspan-percentage-width.html diff --git a/Tests/LibWeb/Layout/expected/table/colspan-percentage-width.txt b/Tests/LibWeb/Layout/expected/table/colspan-percentage-width.txt new file mode 100644 index 0000000000..232a1cb1ce --- /dev/null +++ b/Tests/LibWeb/Layout/expected/table/colspan-percentage-width.txt @@ -0,0 +1,53 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x600 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x44.9375 children: not-inline + TableWrapper <(anonymous)> at (8,8) content-size 420x44.9375 [BFC] children: not-inline + Box at (9,9) content-size 418x42.9375 table-box [TFC] children: not-inline + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + Box at (9,9) content-size 418x42.9375 table-row-group children: not-inline + Box at (9,9) content-size 418x21.46875 table-row children: not-inline + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + BlockContainer at (9,30.46875) content-size 418x21.46875 table-row children: not-inline + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + BlockContainer
at (11,11) content-size 79.6x17.46875 table-cell [BFC] children: inline + line 0 width: 14.265625, height: 17.46875, bottom: 17.46875, baseline: 13.53125 + frag 0 from TextNode start: 0, length: 1, rect: [44,11 14.265625x17.46875] + "A" + TextNode <#text> + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + BlockContainer at (94.6,11) content-size 157.343276x17.46875 table-cell [BFC] children: inline + line 0 width: 9.34375, height: 17.46875, bottom: 17.46875, baseline: 13.53125 + frag 0 from TextNode start: 0, length: 1, rect: [168.6,11 9.34375x17.46875] + "B" + TextNode <#text> + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + BlockContainer at (255.943276,11) content-size 169.056723x17.46875 table-cell [BFC] children: inline + line 0 width: 10.3125, height: 17.46875, bottom: 17.46875, baseline: 13.53125 + frag 0 from TextNode start: 0, length: 1, rect: [334.943276,11 10.3125x17.46875] + "C" + TextNode <#text> + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + Box
at (11,32.46875) content-size 79.6x17.46875 table-cell [BFC] children: inline + line 0 width: 11.140625, height: 17.46875, bottom: 17.46875, baseline: 13.53125 + frag 0 from TextNode start: 0, length: 1, rect: [45,32.46875 11.140625x17.46875] + "D" + TextNode <#text> + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + BlockContainer at (94.6,32.46875) content-size 330.399999x17.46875 table-cell [BFC] children: inline + line 0 width: 11.859375, height: 17.46875, bottom: 17.46875, baseline: 13.53125 + frag 0 from TextNode start: 0, length: 1, rect: [253.6,32.46875 11.859375x17.46875] + "E" + TextNode <#text> + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> diff --git a/Tests/LibWeb/Layout/input/table/colspan-percentage-width.html b/Tests/LibWeb/Layout/input/table/colspan-percentage-width.html new file mode 100644 index 0000000000..33cd0835f1 --- /dev/null +++ b/Tests/LibWeb/Layout/input/table/colspan-percentage-width.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + +
ABC
DE
\ No newline at end of file diff --git a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp index c379086afb..e4514fa585 100644 --- a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp @@ -226,20 +226,6 @@ void TableFormattingContext::compute_cell_measures(AvailableSpace const& availab compute_constrainedness(); - for (auto& cell : m_cells) { - auto const& computed_values = cell.box->computed_values(); - - if (computed_values.width().is_percentage()) { - m_columns[cell.column_index].has_intrinsic_percentage = true; - m_columns[cell.column_index].intrinsic_percentage = max(m_columns[cell.column_index].intrinsic_percentage, computed_values.width().percentage().value()); - } - - if (computed_values.height().is_percentage()) { - m_rows[cell.row_index].has_intrinsic_percentage = true; - m_rows[cell.row_index].intrinsic_percentage = max(m_rows[cell.row_index].intrinsic_percentage, computed_values.height().percentage().value()); - } - } - for (auto& cell : m_cells) { auto const& computed_values = cell.box->computed_values(); CSSPixels padding_top = computed_values.padding().top().to_px(cell.box, containing_block.content_height()); @@ -364,6 +350,86 @@ void TableFormattingContext::initialize_table_measures +void TableFormattingContext::compute_intrinsic_percentage(size_t max_cell_span) +{ + auto& rows_or_columns = table_rows_or_columns(); + + // https://www.w3.org/TR/css-tables-3/#intrinsic-percentage-width-of-a-column-based-on-cells-of-span-up-to-1 + initialize_intrinsic_percentages_from_rows_or_columns(); + initialize_intrinsic_percentages_from_cells(); + + // Stores intermediate values for intrinsic percentage based on cells of span up to N for the iterative algorithm, to store them back at the end of the step. + Vector intrinsic_percentage_contribution_by_index; + intrinsic_percentage_contribution_by_index.resize(rows_or_columns.size()); + for (size_t rc_index = 0; rc_index < rows_or_columns.size(); ++rc_index) { + intrinsic_percentage_contribution_by_index[rc_index] = rows_or_columns[rc_index].intrinsic_percentage; + } + + for (size_t current_span = 2; current_span <= max_cell_span; current_span++) { + // https://www.w3.org/TR/css-tables-3/#intrinsic-percentage-width-of-a-column-based-on-cells-of-span-up-to-n-n--1 + for (auto& cell : m_cells) { + auto cell_span_value = cell_span(cell); + if (cell_span_value != current_span) { + continue; + } + auto cell_start_rc_index = cell_index(cell); + auto cell_end_rc_index = cell_start_rc_index + cell_span_value; + // 1. Start with the percentage contribution of the cell. + CSSPixels cell_contribution = cell_percentage_contribution(cell); + // 2. Subtract the intrinsic percentage width of the column based on cells of span up to N-1 of all columns + // that the cell spans. If this gives a negative result, change it to 0%. + for (auto rc_index = cell_start_rc_index; rc_index < cell_end_rc_index; rc_index++) { + cell_contribution -= rows_or_columns[rc_index].intrinsic_percentage; + cell_contribution = max(cell_contribution, 0); + } + // Compute the sum of the non-spanning max-content sizes of all rows / columns spanned by the cell that have an intrinsic percentage + // size of the row / column based on cells of span up to N-1 equal to 0%, to be used in step 3 of the cell contribution algorithm. + CSSPixels width_sum_of_columns_with_zero_intrinsic_percentage = 0; + size_t number_of_columns_with_zero_intrinsic_percentage = 0; + for (auto rc_index = cell_start_rc_index; rc_index < cell_end_rc_index; rc_index++) { + if (rows_or_columns[rc_index].intrinsic_percentage == 0) { + width_sum_of_columns_with_zero_intrinsic_percentage += rows_or_columns[rc_index].max_size; + ++number_of_columns_with_zero_intrinsic_percentage; + } + } + for (size_t rc_index = cell_start_rc_index; rc_index < cell_end_rc_index; rc_index++) { + // If the intrinsic percentage width of a column based on cells of span up to N-1 is greater than 0%, then the intrinsic percentage width of + // the column based on cells of span up to N is the same as the intrinsic percentage width of the column based on cells of span up to N-1. + if (rows_or_columns[rc_index].intrinsic_percentage > 0) { + continue; + } + // Otherwise, it is the largest of the contributions of the cells in the column whose colSpan is N, + // where the contribution of a cell is the result of taking the following steps: + // 1. Start with the percentage contribution of the cell. + // 2. Subtract the intrinsic percentage width of the column based on cells of span up to N-1 of all columns + // that the cell spans. If this gives a negative result, change it to 0%. + // 3. Multiply by the ratio of the column’s non-spanning max-content width to the sum of the non-spanning max-content widths of all + // columns spanned by the cell that have an intrinsic percentage width of the column based on cells of span up to N-1 equal to 0%. + CSSPixels ajusted_cell_contribution; + if (width_sum_of_columns_with_zero_intrinsic_percentage != 0) { + ajusted_cell_contribution = cell_contribution * rows_or_columns[rc_index].max_size / width_sum_of_columns_with_zero_intrinsic_percentage; + } else { + // However, if this ratio is undefined because the denominator is zero, instead use the 1 divided by the number of columns + // spanned by the cell that have an intrinsic percentage width of the column based on cells of span up to N-1 equal to zero. + ajusted_cell_contribution = cell_contribution * 1 / number_of_columns_with_zero_intrinsic_percentage; + } + intrinsic_percentage_contribution_by_index[rc_index] = max(ajusted_cell_contribution.to_double(), intrinsic_percentage_contribution_by_index[rc_index]); + } + } + for (size_t rc_index = 0; rc_index < rows_or_columns.size(); ++rc_index) { + rows_or_columns[rc_index].intrinsic_percentage = intrinsic_percentage_contribution_by_index[rc_index]; + } + } + + // Clamp total intrinsic percentage to 100%: https://www.w3.org/TR/css-tables-3/#intrinsic-percentage-width-of-a-column + double total_intrinsic_percentage = 0; + for (auto& rows_or_column : rows_or_columns) { + rows_or_column.intrinsic_percentage = max(min(100 - total_intrinsic_percentage, rows_or_column.intrinsic_percentage), 0); + total_intrinsic_percentage += rows_or_column.intrinsic_percentage; + } +} + template void TableFormattingContext::compute_table_measures() { @@ -376,6 +442,10 @@ void TableFormattingContext::compute_table_measures() max_cell_span = max(max_cell_span, cell_span(cell)); } + // Since the intrinsic percentage specification uses non-spanning max-content size for the iterative algorithm, + // run it before we compute the spanning max-content size with its own iterative algorithm for span up to N. + compute_intrinsic_percentage(max_cell_span); + for (size_t current_span = 2; current_span <= max_cell_span; current_span++) { // https://www.w3.org/TR/css-tables-3/#min-content-width-of-a-column-based-on-cells-of-span-up-to-n-n--1 Vector> cell_min_contributions_by_rc_index; @@ -1520,6 +1590,95 @@ CSSPixels TableFormattingContext::cell_max_size( return cell.outer_max_width; } +template<> +double TableFormattingContext::cell_percentage_contribution(TableFormattingContext::Cell const& cell) +{ + // Definition of percentage contribution: https://www.w3.org/TR/css-tables-3/#percentage-contribution + auto const& computed_values = cell.box->computed_values(); + auto max_height_percentage = computed_values.max_height().is_percentage() ? computed_values.max_height().percentage().value() : static_cast(INFINITY); + auto height_percentage = computed_values.height().is_percentage() ? computed_values.height().percentage().value() : 0; + return min(height_percentage, max_height_percentage); +} + +template<> +double TableFormattingContext::cell_percentage_contribution(TableFormattingContext::Cell const& cell) +{ + // Definition of percentage contribution: https://www.w3.org/TR/css-tables-3/#percentage-contribution + auto const& computed_values = cell.box->computed_values(); + auto max_width_percentage = computed_values.max_width().is_percentage() ? computed_values.max_width().percentage().value() : static_cast(INFINITY); + auto width_percentage = computed_values.width().is_percentage() ? computed_values.width().percentage().value() : 0; + return min(width_percentage, max_width_percentage); +} + +template<> +bool TableFormattingContext::cell_has_intrinsic_percentage(TableFormattingContext::Cell const& cell) +{ + return cell.box->computed_values().height().is_percentage(); +} + +template<> +bool TableFormattingContext::cell_has_intrinsic_percentage(TableFormattingContext::Cell const& cell) +{ + return cell.box->computed_values().width().is_percentage(); +} + +template<> +void TableFormattingContext::initialize_intrinsic_percentages_from_rows_or_columns() +{ + for (auto& row : m_rows) { + auto const& computed_values = row.box->computed_values(); + // Definition of percentage contribution: https://www.w3.org/TR/css-tables-3/#percentage-contribution + auto max_height_percentage = computed_values.max_height().is_percentage() ? computed_values.max_height().percentage().value() : static_cast(INFINITY); + auto height_percentage = computed_values.height().is_percentage() ? computed_values.height().percentage().value() : 0; + row.has_intrinsic_percentage = computed_values.max_height().is_percentage() || computed_values.height().is_percentage(); + row.intrinsic_percentage = min(height_percentage, max_height_percentage); + } +} + +template<> +void TableFormattingContext::initialize_intrinsic_percentages_from_rows_or_columns() +{ + size_t column_index = 0; + for_each_child_box_matching(table_box(), is_table_column_group, [&](auto& column_group_box) { + for_each_child_box_matching(column_group_box, is_table_column, [&](auto& column_box) { + auto const& computed_values = column_box.computed_values(); + // Definition of percentage contribution: https://www.w3.org/TR/css-tables-3/#percentage-contribution + auto max_width_percentage = computed_values.max_width().is_percentage() ? computed_values.max_width().percentage().value() : static_cast(INFINITY); + auto width_percentage = computed_values.width().is_percentage() ? computed_values.width().percentage().value() : 0; + m_columns[column_index].has_intrinsic_percentage = computed_values.max_width().is_percentage() || computed_values.width().is_percentage(); + m_columns[column_index].intrinsic_percentage = min(width_percentage, max_width_percentage); + auto const& col_node = static_cast(*column_box.dom_node()); + unsigned span = col_node.attribute(HTML::AttributeNames::span).to_uint().value_or(1); + column_index += span; + }); + }); +} + +template +void TableFormattingContext::initialize_intrinsic_percentages_from_cells() +{ + auto& rows_or_columns = table_rows_or_columns(); + + for (auto& cell : m_cells) { + auto cell_span_value = cell_span(cell); + auto cell_start_rc_index = cell_index(cell); + auto cell_end_rc_index = cell_start_rc_index + cell_span_value; + if (cell_has_intrinsic_percentage(cell)) { + for (auto rc_index = cell_start_rc_index; rc_index < cell_end_rc_index; rc_index++) { + rows_or_columns[rc_index].has_intrinsic_percentage = true; + } + if (cell_span_value != 1) { + continue; + } + } else { + continue; + } + size_t rc_index = cell_index(cell); + rows_or_columns[rc_index].has_intrinsic_percentage = true; + rows_or_columns[rc_index].intrinsic_percentage = max(cell_percentage_contribution(cell), rows_or_columns[rc_index].intrinsic_percentage); + } +} + template<> CSSPixels TableFormattingContext::border_spacing() { diff --git a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h index 8be55edd9f..c3e0b9b489 100644 --- a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h @@ -45,6 +45,8 @@ private: void initialize_table_measures(); template void compute_table_measures(); + template + void compute_intrinsic_percentage(size_t max_cell_span); void compute_table_width(); void distribute_width_to_columns(); void distribute_excess_width_to_columns(CSSPixels available_width); @@ -126,6 +128,18 @@ private: template static CSSPixels cell_max_size(Cell const& cell); + template + static double cell_percentage_contribution(Cell const& cell); + + template + static bool cell_has_intrinsic_percentage(Cell const& cell); + + template + void initialize_intrinsic_percentages_from_rows_or_columns(); + + template + void initialize_intrinsic_percentages_from_cells(); + template CSSPixels border_spacing();