diff options
author | Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com> | 2023-04-29 00:43:05 +0300 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2023-04-29 06:46:45 +0200 |
commit | d9f0c2a806b02f4081f04da61284b4ded9c810a1 (patch) | |
tree | f817a77edf76510bb040ae34039c1287d85eb715 | |
parent | 9fd51a59ffcd6e7982af86991a870324dff2eb4b (diff) | |
download | serenity-d9f0c2a806b02f4081f04da61284b4ded9c810a1.zip |
LibWeb: Implement "distribute height to rows" step in TFC
This commit implements following missing steps in table layout:
- Calculate final table height
- Resolve percentage height of cells and rows using final table height
- Distribute avilable height to table rows
10 files changed, 320 insertions, 12 deletions
diff --git a/Tests/LibWeb/Layout/expected/table/rows-height-distribution-1.txt b/Tests/LibWeb/Layout/expected/table/rows-height-distribution-1.txt new file mode 100644 index 0000000000..edfe78b7af --- /dev/null +++ b/Tests/LibWeb/Layout/expected/table/rows-height-distribution-1.txt @@ -0,0 +1,9 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer <html> at (0,0) content-size 800x166 children: not-inline + BlockContainer <body> at (8,8) content-size 784x150 children: not-inline + TableWrapper <(anonymous)> at (8,8) content-size 200x150 children: not-inline + TableBox <div.table> at (8,8) content-size 200x150 children: not-inline + TableRowBox <div.row.a> at (8,8) content-size 200x50 children: not-inline + TableCellBox <div.cell> at (8,8) content-size 200x0 children: not-inline + TableRowBox <div.row.b> at (8,58) content-size 200x100 children: not-inline + TableCellBox <div.cell> at (8,58) content-size 200x0 children: not-inline diff --git a/Tests/LibWeb/Layout/expected/table/rows-height-distribution-2.txt b/Tests/LibWeb/Layout/expected/table/rows-height-distribution-2.txt new file mode 100644 index 0000000000..ccd7ce3662 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/table/rows-height-distribution-2.txt @@ -0,0 +1,9 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer <html> at (0,0) content-size 800x316 children: not-inline + BlockContainer <body> at (8,8) content-size 784x300 children: not-inline + TableWrapper <(anonymous)> at (8,8) content-size 200x300 children: not-inline + TableBox <div.table> at (8,8) content-size 200x300 children: not-inline + TableRowBox <div.row.a> at (8,8) content-size 200x150 children: not-inline + TableCellBox <div.cell> at (8,8) content-size 200x0 children: not-inline + TableRowBox <div.row.b> at (8,158) content-size 200x150 children: not-inline + TableCellBox <div.cell> at (8,158) content-size 200x0 children: not-inline diff --git a/Tests/LibWeb/Layout/expected/table/rows-height-distribution-3.txt b/Tests/LibWeb/Layout/expected/table/rows-height-distribution-3.txt new file mode 100644 index 0000000000..2b7902da0d --- /dev/null +++ b/Tests/LibWeb/Layout/expected/table/rows-height-distribution-3.txt @@ -0,0 +1,17 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer <html> at (0,0) content-size 800x316 children: not-inline + BlockContainer <body> at (8,8) content-size 784x300 children: not-inline + TableWrapper <(anonymous)> at (8,8) content-size 200x300 children: not-inline + TableBox <div.table> at (8,8) content-size 200x300 children: not-inline + TableRowBox <div.row.a> at (8,8) content-size 200x150 children: not-inline + TableCellBox <div.cell> at (8,8) content-size 200x17.46875 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: [8,8 9.34375x17.46875] + "a" + TextNode <#text> + TableRowBox <div.row.b> at (8,158) content-size 200x150 children: not-inline + TableCellBox <div.cell> at (8,158) content-size 200x17.46875 children: inline + line 0 width: 9.46875, height: 17.46875, bottom: 17.46875, baseline: 13.53125 + frag 0 from TextNode start: 0, length: 1, rect: [8,158 9.46875x17.46875] + "b" + TextNode <#text> diff --git a/Tests/LibWeb/Layout/expected/table/rows-height-distribution-4.txt b/Tests/LibWeb/Layout/expected/table/rows-height-distribution-4.txt new file mode 100644 index 0000000000..36f6d4670b --- /dev/null +++ b/Tests/LibWeb/Layout/expected/table/rows-height-distribution-4.txt @@ -0,0 +1,17 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer <html> at (0,0) content-size 800x316 children: not-inline + BlockContainer <body> at (8,8) content-size 784x300 children: not-inline + TableWrapper <(anonymous)> at (8,8) content-size 200x300 children: not-inline + TableBox <div.table> at (8,8) content-size 200x300 children: not-inline + TableRowBox <div.row.a> at (8,8) content-size 200x100 children: not-inline + TableCellBox <div.cell> at (8,8) content-size 200x17.46875 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: [8,8 9.34375x17.46875] + "a" + TextNode <#text> + TableRowBox <div.row.b> at (8,108) content-size 200x200 children: not-inline + TableCellBox <div.cell> at (8,108) content-size 200x17.46875 children: inline + line 0 width: 9.46875, height: 17.46875, bottom: 17.46875, baseline: 13.53125 + frag 0 from TextNode start: 0, length: 1, rect: [8,108 9.46875x17.46875] + "b" + TextNode <#text> diff --git a/Tests/LibWeb/Layout/input/table/rows-height-distribution-1.html b/Tests/LibWeb/Layout/input/table/rows-height-distribution-1.html new file mode 100644 index 0000000000..94fd026786 --- /dev/null +++ b/Tests/LibWeb/Layout/input/table/rows-height-distribution-1.html @@ -0,0 +1,30 @@ +<style> +* { + font-family: 'SerenitySans'; +} + +.table { + display: table; + width: 200px; + background-color: darkgoldenrod; +} + +.row { + display: table-row; +} + +.cell { + display: table-cell; +} + +.a { + height: 50px; + background-color: darkcyan; +} + +.b { + height: 100px; + background-color: orangered; +} +</style> +<div class="table"><div class="row a"><div class="cell"></div></div><div class="row b"><div class="cell"></div></div></div>
\ No newline at end of file diff --git a/Tests/LibWeb/Layout/input/table/rows-height-distribution-2.html b/Tests/LibWeb/Layout/input/table/rows-height-distribution-2.html new file mode 100644 index 0000000000..366ed4c181 --- /dev/null +++ b/Tests/LibWeb/Layout/input/table/rows-height-distribution-2.html @@ -0,0 +1,30 @@ +<style> +* { + font-family: 'SerenitySans'; +} + +.table { + display: table; + width: 200px; + height: 300px; + background-color: darkgoldenrod; +} + +.row { + display: table-row; +} + +.cell { + display: table-cell; +} + +.a { + height: 50%; + background-color: darkcyan; +} + +.b { + background-color: orangered; +} +</style> +<div class="table"><div class="row a"><div class="cell"></div></div><div class="row b"><div class="cell"></div></div></div>
\ No newline at end of file diff --git a/Tests/LibWeb/Layout/input/table/rows-height-distribution-3.html b/Tests/LibWeb/Layout/input/table/rows-height-distribution-3.html new file mode 100644 index 0000000000..a97c49c712 --- /dev/null +++ b/Tests/LibWeb/Layout/input/table/rows-height-distribution-3.html @@ -0,0 +1,29 @@ +<style> +* { + font-family: 'SerenitySans'; +} + +.table { + display: table; + width: 200px; + height: 300px; + background-color: darkgoldenrod; +} + +.row { + display: table-row; +} + +.cell { + display: table-cell; +} + +.a { + background-color: darkcyan; +} + +.b { + background-color: orangered; +} +</style> +<div class="table"><div class="row a"><div class="cell">a</div></div><div class="row b"><div class="cell">b</div></div></div>
\ No newline at end of file diff --git a/Tests/LibWeb/Layout/input/table/rows-height-distribution-4.html b/Tests/LibWeb/Layout/input/table/rows-height-distribution-4.html new file mode 100644 index 0000000000..aec8c166d6 --- /dev/null +++ b/Tests/LibWeb/Layout/input/table/rows-height-distribution-4.html @@ -0,0 +1,31 @@ +<style> +* { + font-family: 'SerenitySans'; +} + +.table { + display: table; + width: 200px; + height: 300px; + background-color: darkgoldenrod; +} + +.row { + display: table-row; +} + +.cell { + display: table-cell; +} + +.a { + background-color: darkcyan; + height: 50px; +} + +.b { + background-color: orangered; + height: 50%; +} +</style> +<div class="table"><div class="row a"><div class="cell">a</div></div><div class="row b"><div class="cell">b</div></div></div>
\ No newline at end of file diff --git a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp index c3c565f24c..318db942aa 100644 --- a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp @@ -394,8 +394,20 @@ void TableFormattingContext::determine_intrisic_size_of_table_container(Availabl } } -void TableFormattingContext::calculate_row_heights(LayoutMode layout_mode) +void TableFormattingContext::compute_table_height(LayoutMode layout_mode) { + // First pass of row height calculation: + for (auto& row : m_rows) { + auto row_computed_height = row.box->computed_values().height(); + if (row_computed_height.is_length()) { + auto height_of_containing_block = m_state.get(*row.box->containing_block()).content_height(); + auto height_of_containing_block_as_length = CSS::Length::make_px(height_of_containing_block); + auto row_used_height = row_computed_height.resolved(row.box, height_of_containing_block_as_length).to_px(row.box); + row.base_height = max(row.base_height, row_used_height); + } + } + + // First pass of cells layout: for (auto& cell : m_cells) { auto& row = m_rows[cell.row_index]; auto& cell_state = m_state.get_mutable(cell.box); @@ -430,7 +442,7 @@ void TableFormattingContext::calculate_row_heights(LayoutMode layout_mode) auto cell_used_height = cell_computed_height.resolved(cell.box, height_of_containing_block_as_length).to_px(cell.box); cell_state.set_content_height(cell_used_height - cell_state.border_box_top() - cell_state.border_box_bottom()); - row.used_height = max(row.used_height, cell_used_height); + row.base_height = max(row.base_height, cell_used_height); } cell_state.set_content_width((span_width - cell_state.border_box_left() - cell_state.border_box_right())); @@ -441,17 +453,134 @@ void TableFormattingContext::calculate_row_heights(LayoutMode layout_mode) cell.baseline = box_baseline(m_state, cell.box); - row.used_height = max(row.used_height, cell_state.border_box_height()); + row.base_height = max(row.base_height, cell_state.border_box_height()); row.baseline = max(row.baseline, cell.baseline); } + CSSPixels sum_rows_height = 0; + for (auto& row : m_rows) { + sum_rows_height += row.base_height; + } + + // The height of a table is the sum of the row heights plus any cell spacing or borders. + m_table_height = sum_rows_height; + + if (!table_box().computed_values().height().is_auto()) { + // If the table has a height property with a value other than auto, it is treated as a minimum height for the + // table grid, and will eventually be distributed to the height of the rows if their collective minimum height + // ends up smaller than this number. + CSSPixels height_of_table_containing_block = m_state.get(*table_wrapper().containing_block()).content_height(); + auto specified_table_height = table_box().computed_values().height().resolved(table_box(), CSS::Length::make_px(height_of_table_containing_block)).to_px(table_box()); + if (m_table_height < specified_table_height) { + m_table_height = specified_table_height; + } + } + + for (auto& row : m_rows) { + // Reference size is the largest of + // - its initial base height and + // - its new base height (the one evaluated during the second layout pass, where percentages used in + // rowgroups/rows/cells' specified heights were resolved according to the table height, instead of + // being ignored as 0px). + + // Assign reference size to base size. Later reference size might change to largee value during + // second pass of rows layout. + row.reference_height = row.base_height; + } + + // Second pass of rows height calculation: + // At this point percentage row height can be resolved because final table height is calculated. for (auto& row : m_rows) { auto row_computed_height = row.box->computed_values().height(); - if (row_computed_height.is_length()) { - auto height_of_containing_block = m_state.get(*row.box->containing_block()).content_height(); - auto height_of_containing_block_as_length = CSS::Length::make_px(height_of_containing_block); - auto row_used_height = row_computed_height.resolved(row.box, height_of_containing_block_as_length).to_px(row.box); - row.used_height = max(row.used_height, row_used_height); + if (row_computed_height.is_percentage()) { + auto row_used_height = row_computed_height.resolved(row.box, CSS::Length::make_px(m_table_height)).to_px(row.box); + row.reference_height = max(row.reference_height, row_used_height); + } else { + continue; + } + } + + // Second pass cells layout: + // At this point percantage cell height can be resolved because final table heigh is calculated. + for (auto& cell : m_cells) { + auto& row = m_rows[cell.row_index]; + auto& cell_state = m_state.get_mutable(cell.box); + + CSSPixels span_width = 0; + for (size_t i = 0; i < cell.column_span; ++i) + span_width += m_columns[cell.column_index + i].used_width; + + auto height_of_containing_block_as_length = CSS::Length::make_px(m_table_height); + + auto cell_computed_height = cell.box->computed_values().height(); + if (cell_computed_height.is_percentage()) { + auto cell_used_height = cell_computed_height.resolved(cell.box, height_of_containing_block_as_length).to_px(cell.box); + cell_state.set_content_height(cell_used_height - cell_state.border_box_top() - cell_state.border_box_bottom()); + + row.reference_height = max(row.reference_height, cell_used_height); + } else { + continue; + } + + cell_state.set_content_width((span_width - cell_state.border_box_left() - cell_state.border_box_right())); + if (auto independent_formatting_context = layout_inside(cell.box, layout_mode, cell_state.available_inner_space_or_constraints_from(*m_available_space))) { + independent_formatting_context->parent_context_did_dimension_child_root_box(); + } + + cell.baseline = box_baseline(m_state, cell.box); + + row.reference_height = max(row.reference_height, cell_state.border_box_height()); + row.baseline = max(row.baseline, cell.baseline); + } +} + +void TableFormattingContext::distribute_height_to_rows() +{ + CSSPixels sum_reference_height = 0; + for (auto& row : m_rows) { + sum_reference_height += row.reference_height; + } + + if (sum_reference_height == 0) + return; + + Vector<Row&> rows_with_auto_height; + for (auto& row : m_rows) { + if (row.box->computed_values().height().is_auto()) { + rows_with_auto_height.append(row); + } + } + + if (m_table_height <= sum_reference_height) { + // If the table height is equal or smaller than sum of reference sizes, the final height assigned to each row + // will be the weighted mean of the base and the reference size that yields the correct total height. + + for (auto& row : m_rows) { + auto weight = row.reference_height / sum_reference_height; + auto final_height = m_table_height * weight; + row.final_height = final_height; + } + } else if (rows_with_auto_height.size() > 0) { + // Else, if the table owns any “auto-height” row (a row whose size is only determined by its content size and + // none of the specified heights), each non-auto-height row receives its reference height and auto-height rows + // receive their reference size plus some increment which is equal to the height missing to amount to the + // specified table height divided by the amount of such rows. + + for (auto& row : m_rows) { + row.final_height = row.reference_height; + } + + auto auto_height_rows_increment = (m_table_height - sum_reference_height) / rows_with_auto_height.size(); + for (auto& row : rows_with_auto_height) { + row.final_height += auto_height_rows_increment; + } + } else { + // Else, all rows receive their reference size plus some increment which is equal to the height missing to + // amount to the specified table height divided by the amount of rows. + + auto increment = (m_table_height - sum_reference_height) / m_rows.size(); + for (auto& row : m_rows) { + row.final_height = row.reference_height + increment; } } } @@ -470,7 +599,7 @@ void TableFormattingContext::position_row_boxes(CSSPixels& total_content_height) row_width += column.used_width; } - row_state.set_content_height(row.used_height); + row_state.set_content_height(row.final_height); row_state.set_content_width(row_width); row_state.set_content_x(row_left_offset); row_state.set_content_y(row_top_offset); @@ -563,7 +692,10 @@ void TableFormattingContext::run(Box const& box, LayoutMode layout_mode, Availab // Distribute the width of the table among columns. distribute_width_to_columns(); - calculate_row_heights(layout_mode); + compute_table_height(layout_mode); + + distribute_height_to_rows(); + position_row_boxes(total_content_height); position_cell_boxes(); diff --git a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h index 6de167e692..f8ade2a8e2 100644 --- a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h @@ -34,10 +34,12 @@ private: void compute_table_width(); void distribute_width_to_columns(); void determine_intrisic_size_of_table_container(AvailableSpace const& available_space); - void calculate_row_heights(LayoutMode layout_mode); + void compute_table_height(LayoutMode layout_mode); + void distribute_height_to_rows(); void position_row_boxes(CSSPixels&); void position_cell_boxes(); + CSSPixels m_table_height { 0 }; CSSPixels m_automatic_content_height { 0 }; Optional<AvailableSpace> m_available_space; @@ -59,7 +61,9 @@ private: struct Row { JS::NonnullGCPtr<Box> box; - CSSPixels used_height { 0 }; + CSSPixels base_height { 0 }; + CSSPixels reference_height { 0 }; + CSSPixels final_height { 0 }; CSSPixels baseline { 0 }; }; |