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();