Serenity Operating System
at master 550 lines 25 kB view raw
1/* 2 * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <LibWeb/DOM/Node.h> 8#include <LibWeb/HTML/BrowsingContext.h> 9#include <LibWeb/Layout/Box.h> 10#include <LibWeb/Layout/InlineFormattingContext.h> 11#include <LibWeb/Layout/TableBox.h> 12#include <LibWeb/Layout/TableCellBox.h> 13#include <LibWeb/Layout/TableFormattingContext.h> 14#include <LibWeb/Layout/TableRowBox.h> 15#include <LibWeb/Layout/TableRowGroupBox.h> 16 17struct GridPosition { 18 size_t x; 19 size_t y; 20}; 21 22inline bool operator==(GridPosition const& a, GridPosition const& b) 23{ 24 return a.x == b.x && a.y == b.y; 25} 26 27namespace AK { 28template<> 29struct Traits<GridPosition> : public GenericTraits<GridPosition> { 30 static unsigned hash(GridPosition const& key) 31 { 32 return pair_int_hash(key.x, key.y); 33 } 34}; 35} 36 37namespace Web::Layout { 38 39TableFormattingContext::TableFormattingContext(LayoutState& state, TableBox const& root, FormattingContext* parent) 40 : FormattingContext(Type::Table, state, root, parent) 41{ 42} 43 44TableFormattingContext::~TableFormattingContext() = default; 45 46void TableFormattingContext::calculate_row_column_grid(Box const& box) 47{ 48 // Implements https://html.spec.whatwg.org/multipage/tables.html#forming-a-table 49 HashMap<GridPosition, bool> grid; 50 51 size_t x_width = 0, y_height = 0; 52 size_t x_current = 0, y_current = 0; 53 54 auto process_row = [&](auto& row) { 55 if (y_height == y_current) 56 y_height++; 57 58 x_current = 0; 59 while (x_current < x_width && grid.contains(GridPosition { x_current, y_current })) 60 x_current++; 61 62 for (auto* child = row.first_child(); child; child = child->next_sibling()) { 63 if (is<TableCellBox>(*child)) { 64 Box* box = static_cast<Box*>(child); 65 if (x_current == x_width) 66 x_width++; 67 68 const size_t colspan = static_cast<TableCellBox*>(child)->colspan(); 69 const size_t rowspan = static_cast<TableCellBox*>(child)->rowspan(); 70 71 if (x_width < x_current + colspan) 72 x_width = x_current + colspan; 73 if (y_height < y_current + rowspan) 74 y_height = y_current + rowspan; 75 76 for (size_t y = y_current; y < y_current + rowspan; y++) 77 for (size_t x = x_current; x < x_current + colspan; x++) 78 grid.set(GridPosition { x, y }, true); 79 m_cells.append(Cell { *box, x_current, y_current, colspan, rowspan }); 80 81 x_current += colspan; 82 } 83 } 84 85 m_rows.append(Row { row }); 86 y_current++; 87 }; 88 89 box.template for_each_child_of_type<TableRowGroupBox>([&](auto& row_group_box) { 90 row_group_box.template for_each_child_of_type<TableRowBox>([&](auto& row) { 91 process_row(row); 92 return IterationDecision::Continue; 93 }); 94 }); 95 96 box.template for_each_child_of_type<TableRowBox>([&](auto& row) { 97 process_row(row); 98 return IterationDecision::Continue; 99 }); 100 101 m_columns.resize(x_width); 102} 103 104void TableFormattingContext::compute_table_measures() 105{ 106 size_t max_cell_column_span = 1; 107 108 for (auto& cell : m_cells) { 109 auto width_of_containing_block = m_state.get(*table_wrapper().containing_block()).content_width(); 110 auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); 111 auto& computed_values = cell.box.computed_values(); 112 CSSPixels padding_left = computed_values.padding().left().resolved(cell.box, width_of_containing_block_as_length).to_px(cell.box); 113 CSSPixels padding_right = computed_values.padding().right().resolved(cell.box, width_of_containing_block_as_length).to_px(cell.box); 114 115 auto is_left_most_cell = cell.column_index == 0; 116 auto is_right_most_cell = cell.column_index == m_columns.size() - 1; 117 auto should_hide_borders = cell.box.computed_values().border_collapse() == CSS::BorderCollapse::Collapse; 118 CSSPixels border_left = should_hide_borders && !is_left_most_cell ? 0 : computed_values.border_left().width; 119 CSSPixels border_right = should_hide_borders && !is_right_most_cell ? 0 : computed_values.border_right().width; 120 121 CSSPixels width = computed_values.width().resolved(cell.box, width_of_containing_block_as_length).to_px(cell.box); 122 auto cell_intrinsic_offsets = padding_left + padding_right + border_left + border_right; 123 auto min_content_width = calculate_min_content_width(cell.box); 124 auto max_content_width = calculate_max_content_width(cell.box); 125 126 CSSPixels min_width = min_content_width; 127 if (!computed_values.min_width().is_auto()) 128 min_width = max(min_width, computed_values.min_width().resolved(cell.box, width_of_containing_block_as_length).to_px(cell.box)); 129 130 CSSPixels max_width = computed_values.width().is_auto() ? max_content_width.value() : width; 131 if (!computed_values.max_width().is_none()) 132 max_width = min(max_width, computed_values.max_width().resolved(cell.box, width_of_containing_block_as_length).to_px(cell.box)); 133 134 auto computed_width = computed_values.width(); 135 if (computed_width.is_percentage()) { 136 m_columns[cell.column_index].type = ColumnType::Percent; 137 m_columns[cell.column_index].percentage_width = max(m_columns[cell.column_index].percentage_width, computed_width.percentage().value()); 138 } else if (computed_width.is_length()) { 139 m_columns[cell.column_index].type = ColumnType::Pixel; 140 } 141 142 cell.min_width = min_width + cell_intrinsic_offsets; 143 cell.max_width = max(max(width, min_width), max_width) + cell_intrinsic_offsets; 144 145 max_cell_column_span = max(max_cell_column_span, cell.column_span); 146 } 147 148 for (auto& cell : m_cells) { 149 if (cell.column_span == 1) { 150 m_columns[cell.column_index].min_width = max(m_columns[cell.column_index].min_width, cell.min_width); 151 m_columns[cell.column_index].max_width = max(m_columns[cell.column_index].max_width, cell.max_width); 152 } 153 } 154 155 for (size_t current_column_span = 2; current_column_span < max_cell_column_span; current_column_span++) { 156 // https://www.w3.org/TR/css-tables-3/#min-content-width-of-a-column-based-on-cells-of-span-up-to-n-n--1 157 Vector<Vector<CSSPixels>> cell_min_contributions_by_column_index; 158 cell_min_contributions_by_column_index.resize(m_columns.size()); 159 // https://www.w3.org/TR/css-tables-3/#max-content-width-of-a-column-based-on-cells-of-span-up-to-n-n--1 160 Vector<Vector<CSSPixels>> cell_max_contributions_by_column_index; 161 cell_max_contributions_by_column_index.resize(m_columns.size()); 162 for (auto& cell : m_cells) { 163 if (cell.column_span == current_column_span) { 164 // Define the baseline max-content width as the sum of the max-content widths based on cells of span up to N-1 of all columns that the cell spans. 165 CSSPixels baseline_max_content_width = 0; 166 auto cell_start_column_index = cell.column_index; 167 auto cell_end_column_index = cell.column_index + cell.column_span; 168 for (auto column_index = cell_start_column_index; column_index < cell_end_column_index; column_index++) { 169 baseline_max_content_width += m_columns[column_index].max_width; 170 } 171 172 // The contribution of the cell is the sum of: 173 // the min-content width of the column based on cells of span up to N-1 174 auto cell_min_contribution = m_columns[cell.column_index].min_width; 175 // and the product of: 176 // - the ratio of the max-content width based on cells of span up to N-1 of the column to the baseline max-content width 177 // - the outer min-content width of the cell minus the baseline max-content width and baseline border spacing, or 0 if this is negative 178 cell_min_contribution += (m_columns[cell.column_index].max_width / baseline_max_content_width) * max(CSSPixels(0), cell.min_width - baseline_max_content_width); 179 180 // The contribution of the cell is the sum of: 181 // the max-content width of the column based on cells of span up to N-1 182 auto cell_max_contribution = m_columns[cell.column_index].max_width; 183 // and the product of: 184 // - the ratio of the max-content width based on cells of span up to N-1 of the column to the baseline max-content width 185 // - the outer max-content width of the cell minus the baseline max-content width and the baseline border spacing, or 0 if this is negative 186 cell_max_contribution += (m_columns[cell.column_index].max_width / baseline_max_content_width) * max(CSSPixels(0), cell.max_width - baseline_max_content_width); 187 188 cell_min_contributions_by_column_index[cell.column_index].append(cell_min_contribution); 189 cell_max_contributions_by_column_index[cell.column_index].append(cell_max_contribution); 190 } 191 } 192 193 for (size_t column_index = 0; column_index < m_columns.size(); column_index++) { 194 // min-content width of a column based on cells of span up to N (N > 1) is 195 // the largest of the min-content width of the column based on cells of span up to N-1 and the contributions of the cells in the column whose colSpan is N 196 for (auto min_contribution : cell_min_contributions_by_column_index[column_index]) 197 m_columns[column_index].min_width = max(m_columns[column_index].min_width, min_contribution); 198 199 // max-content width of a column based on cells of span up to N (N > 1) is 200 // the largest of the max-content width based on cells of span up to N-1 and the contributions of the cells in the column whose colSpan is N 201 for (auto max_contribution : cell_max_contributions_by_column_index[column_index]) 202 m_columns[column_index].max_width = max(m_columns[column_index].max_width, max_contribution); 203 } 204 } 205} 206 207void TableFormattingContext::compute_table_width() 208{ 209 auto& table_box_state = m_state.get_mutable(table_box()); 210 211 auto& computed_values = table_box().computed_values(); 212 213 // Percentages on 'width' and 'height' on the table are relative to the table wrapper box's containing block, 214 // not the table wrapper box itself. 215 CSSPixels width_of_table_containing_block = m_state.get(*table_wrapper().containing_block()).content_width(); 216 217 // The row/column-grid width minimum (GRIDMIN) width is the sum of the min-content width 218 // of all the columns plus cell spacing or borders. 219 CSSPixels grid_min = 0.0f; 220 for (auto& column : m_columns) { 221 grid_min += column.min_width; 222 } 223 224 // The row/column-grid width maximum (GRIDMAX) width is the sum of the max-content width 225 // of all the columns plus cell spacing or borders. 226 CSSPixels grid_max = 0.0f; 227 for (auto& column : m_columns) { 228 grid_max += column.max_width; 229 } 230 231 // The used min-width of a table is the greater of the resolved min-width, CAPMIN, and GRIDMIN. 232 auto used_min_width = grid_min; 233 if (!computed_values.min_width().is_auto()) { 234 used_min_width = max(used_min_width, computed_values.min_width().resolved(table_box(), CSS::Length::make_px(width_of_table_containing_block)).to_px(table_box())); 235 } 236 237 CSSPixels used_width; 238 if (computed_values.width().is_auto()) { 239 // If the table-root has 'width: auto', the used width is the greater of 240 // min(GRIDMAX, the table’s containing block width), the used min-width of the table. 241 used_width = max(min(grid_max, width_of_table_containing_block), used_min_width); 242 table_box_state.set_content_width(used_width); 243 } else { 244 // If the table-root’s width property has a computed value (resolving to 245 // resolved-table-width) other than auto, the used width is the greater 246 // of resolved-table-width, and the used min-width of the table. 247 CSSPixels resolved_table_width = computed_values.width().resolved(table_box(), CSS::Length::make_px(width_of_table_containing_block)).to_px(table_box()); 248 used_width = max(resolved_table_width, used_min_width); 249 table_box_state.set_content_width(used_width); 250 } 251} 252 253void TableFormattingContext::distribute_width_to_columns() 254{ 255 // Implements https://www.w3.org/TR/css-tables-3/#width-distribution-algorithm 256 257 CSSPixels available_width = m_state.get(table_box()).content_width(); 258 259 auto columns_total_used_width = [&]() { 260 CSSPixels total_used_width = 0; 261 for (auto& column : m_columns) { 262 total_used_width += column.used_width; 263 } 264 return total_used_width; 265 }; 266 267 auto column_preferred_width = [&](Column& column) { 268 switch (column.type) { 269 case ColumnType::Percent: { 270 return max(column.min_width, column.percentage_width / 100 * available_width); 271 } 272 case ColumnType::Pixel: 273 case ColumnType::Auto: { 274 return column.max_width; 275 } 276 default: { 277 VERIFY_NOT_REACHED(); 278 } 279 } 280 }; 281 282 auto expand_columns_to_fill_available_width = [&](ColumnType column_type) { 283 CSSPixels remaining_available_width = available_width; 284 CSSPixels total_preferred_width_increment = 0; 285 for (auto& column : m_columns) { 286 remaining_available_width -= column.used_width; 287 if (column.type == column_type) { 288 total_preferred_width_increment += column_preferred_width(column) - column.min_width; 289 } 290 } 291 292 if (total_preferred_width_increment == 0) 293 return; 294 295 for (auto& column : m_columns) { 296 if (column.type == column_type) { 297 CSSPixels preferred_width_increment = column_preferred_width(column) - column.min_width; 298 column.used_width += preferred_width_increment * remaining_available_width / total_preferred_width_increment; 299 } 300 } 301 }; 302 303 auto shrink_columns_to_fit_available_width = [&](ColumnType column_type) { 304 for (auto& column : m_columns) { 305 if (column.type == column_type) 306 column.used_width = column.min_width; 307 } 308 309 expand_columns_to_fill_available_width(column_type); 310 }; 311 312 for (auto& column : m_columns) { 313 column.used_width = column.min_width; 314 } 315 316 for (auto& column : m_columns) { 317 if (column.type == ColumnType::Percent) { 318 column.used_width = max(column.min_width, column.percentage_width / 100 * available_width); 319 } 320 } 321 322 if (columns_total_used_width() > available_width) { 323 shrink_columns_to_fit_available_width(ColumnType::Percent); 324 return; 325 } 326 327 for (auto& column : m_columns) { 328 if (column.type == ColumnType::Pixel) { 329 column.used_width = column.max_width; 330 } 331 } 332 333 if (columns_total_used_width() > available_width) { 334 shrink_columns_to_fit_available_width(ColumnType::Pixel); 335 return; 336 } 337 338 if (columns_total_used_width() < available_width) { 339 expand_columns_to_fill_available_width(ColumnType::Auto); 340 } 341 342 if (columns_total_used_width() < available_width) { 343 expand_columns_to_fill_available_width(ColumnType::Pixel); 344 } 345 346 if (columns_total_used_width() < available_width) { 347 expand_columns_to_fill_available_width(ColumnType::Percent); 348 } 349 350 if (columns_total_used_width() < available_width) { 351 // NOTE: if all columns got their max width and there is still width to distribute left 352 // it should be assigned to columns proportionally to their max width 353 CSSPixels grid_max = 0.0f; 354 for (auto& column : m_columns) { 355 grid_max += column.max_width; 356 } 357 358 auto width_to_distribute = available_width - columns_total_used_width(); 359 for (auto& column : m_columns) { 360 column.used_width += width_to_distribute * column.max_width / grid_max; 361 } 362 } 363} 364 365void TableFormattingContext::determine_intrisic_size_of_table_container(AvailableSpace const& available_space) 366{ 367 auto& table_box_state = m_state.get_mutable(table_box()); 368 369 if (available_space.width.is_min_content()) { 370 // The min-content width of a table is the width required to fit all of its columns min-content widths and its undistributable spaces. 371 CSSPixels grid_min = 0.0f; 372 for (auto& column : m_columns) 373 grid_min += column.min_width; 374 table_box_state.set_content_width(grid_min); 375 } 376 377 if (available_space.width.is_max_content()) { 378 // The max-content width of a table is the width required to fit all of its columns max-content widths and its undistributable spaces. 379 CSSPixels grid_max = 0.0f; 380 for (auto& column : m_columns) 381 grid_max += column.max_width; 382 table_box_state.set_content_width(grid_max); 383 } 384} 385 386void TableFormattingContext::calculate_row_heights(LayoutMode layout_mode) 387{ 388 for (auto& cell : m_cells) { 389 auto& cell_state = m_state.get_mutable(cell.box); 390 391 CSSPixels span_width = 0; 392 for (size_t i = 0; i < cell.column_span; ++i) 393 span_width += m_columns[cell.column_index + i].used_width; 394 395 auto width_of_containing_block = m_state.get(*cell.box.containing_block()).content_width(); 396 auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); 397 398 cell_state.padding_top = cell.box.computed_values().padding().top().resolved(cell.box, width_of_containing_block_as_length).to_px(cell.box); 399 cell_state.padding_bottom = cell.box.computed_values().padding().bottom().resolved(cell.box, width_of_containing_block_as_length).to_px(cell.box); 400 cell_state.padding_left = cell.box.computed_values().padding().left().resolved(cell.box, width_of_containing_block_as_length).to_px(cell.box); 401 cell_state.padding_right = cell.box.computed_values().padding().right().resolved(cell.box, width_of_containing_block_as_length).to_px(cell.box); 402 403 auto is_top_most_cell = cell.row_index == 0; 404 auto is_left_most_cell = cell.column_index == 0; 405 auto is_right_most_cell = cell.column_index == m_columns.size() - 1; 406 auto is_bottom_most_cell = cell.row_index == m_rows.size() - 1; 407 auto should_hide_borders = cell.box.computed_values().border_collapse() == CSS::BorderCollapse::Collapse; 408 409 cell_state.border_top = (should_hide_borders && is_top_most_cell) ? 0 : cell.box.computed_values().border_top().width; 410 cell_state.border_bottom = (should_hide_borders && is_bottom_most_cell) ? 0 : cell.box.computed_values().border_bottom().width; 411 cell_state.border_left = (should_hide_borders && is_left_most_cell) ? 0 : cell.box.computed_values().border_left().width; 412 cell_state.border_right = (should_hide_borders && is_right_most_cell) ? 0 : cell.box.computed_values().border_right().width; 413 414 cell_state.set_content_width((span_width - cell_state.border_box_left() - cell_state.border_box_right())); 415 if (auto independent_formatting_context = layout_inside(cell.box, layout_mode, cell_state.available_inner_space_or_constraints_from(*m_available_space))) { 416 cell_state.set_content_height(independent_formatting_context->automatic_content_height()); 417 independent_formatting_context->parent_context_did_dimension_child_root_box(); 418 } 419 420 cell.baseline = box_baseline(m_state, cell.box); 421 422 auto& row = m_rows[cell.row_index]; 423 row.used_height = max(row.used_height, cell_state.border_box_height()); 424 row.baseline = max(row.baseline, cell.baseline); 425 } 426} 427 428void TableFormattingContext::position_row_boxes(CSSPixels& total_content_height) 429{ 430 auto const& table_state = m_state.get(table_box()); 431 432 CSSPixels row_top_offset = table_state.border_top + table_state.padding_top; 433 CSSPixels row_left_offset = table_state.border_left + table_state.padding_left; 434 for (size_t y = 0; y < m_rows.size(); y++) { 435 auto& row = m_rows[y]; 436 auto& row_state = m_state.get_mutable(row.box); 437 CSSPixels row_width = 0.0f; 438 for (auto& column : m_columns) { 439 row_width += column.used_width; 440 } 441 442 row_state.set_content_height(row.used_height); 443 row_state.set_content_width(row_width); 444 row_state.set_content_x(row_left_offset); 445 row_state.set_content_y(row_top_offset); 446 row_top_offset += row_state.content_height(); 447 } 448 449 CSSPixels row_group_top_offset = table_state.border_top + table_state.padding_top; 450 CSSPixels row_group_left_offset = table_state.border_left + table_state.padding_left; 451 table_box().for_each_child_of_type<TableRowGroupBox>([&](auto& row_group_box) { 452 CSSPixels row_group_height = 0.0f; 453 CSSPixels row_group_width = 0.0f; 454 455 auto& row_group_box_state = m_state.get_mutable(row_group_box); 456 row_group_box_state.set_content_x(row_group_left_offset); 457 row_group_box_state.set_content_y(row_group_top_offset); 458 459 row_group_box.template for_each_child_of_type<TableRowBox>([&](auto& row) { 460 auto const& row_state = m_state.get(row); 461 row_group_height += row_state.border_box_height(); 462 row_group_width = max(row_group_width, row_state.border_box_width()); 463 }); 464 465 row_group_box_state.set_content_height(row_group_height); 466 row_group_box_state.set_content_width(row_group_width); 467 468 row_group_top_offset += row_group_height; 469 }); 470 471 total_content_height = max(row_top_offset, row_group_top_offset) - table_state.border_top - table_state.padding_top; 472} 473 474void TableFormattingContext::position_cell_boxes() 475{ 476 CSSPixels left_column_offset = 0; 477 for (auto& column : m_columns) { 478 column.left_offset = left_column_offset; 479 left_column_offset += column.used_width; 480 } 481 482 for (auto& cell : m_cells) { 483 auto& cell_state = m_state.get_mutable(cell.box); 484 auto& row_state = m_state.get(m_rows[cell.row_index].box); 485 CSSPixels const cell_border_box_height = cell_state.content_height() + cell_state.border_box_top() + cell_state.border_box_bottom(); 486 CSSPixels const row_content_height = row_state.content_height(); 487 auto const& vertical_align = cell.box.computed_values().vertical_align(); 488 if (vertical_align.has<CSS::VerticalAlign>()) { 489 switch (vertical_align.get<CSS::VerticalAlign>()) { 490 case CSS::VerticalAlign::Middle: { 491 cell_state.padding_top += (row_content_height - cell_border_box_height) / 2; 492 cell_state.padding_bottom += (row_content_height - cell_border_box_height) / 2; 493 break; 494 } 495 // FIXME: implement actual 'top' and 'bottom' support instead of fall back to 'baseline' 496 case CSS::VerticalAlign::Top: 497 case CSS::VerticalAlign::Bottom: 498 case CSS::VerticalAlign::Baseline: { 499 cell_state.padding_top += m_rows[cell.row_index].baseline - cell.baseline; 500 cell_state.padding_bottom += row_content_height - cell_border_box_height; 501 break; 502 } 503 default: 504 VERIFY_NOT_REACHED(); 505 } 506 } 507 508 cell_state.offset = row_state.offset.translated(cell_state.border_box_left() + m_columns[cell.column_index].left_offset, cell_state.border_box_top()); 509 } 510} 511 512void TableFormattingContext::run(Box const& box, LayoutMode layout_mode, AvailableSpace const& available_space) 513{ 514 m_available_space = available_space; 515 516 CSSPixels total_content_height = 0; 517 518 // Determine the number of rows/columns the table requires. 519 calculate_row_column_grid(box); 520 521 // Compute the minimum width of each column. 522 compute_table_measures(); 523 524 if (available_space.width.is_intrinsic_sizing_constraint()) { 525 determine_intrisic_size_of_table_container(available_space); 526 return; 527 } 528 529 // Compute the width of the table. 530 compute_table_width(); 531 532 // Distribute the width of the table among columns. 533 distribute_width_to_columns(); 534 535 calculate_row_heights(layout_mode); 536 position_row_boxes(total_content_height); 537 position_cell_boxes(); 538 539 m_state.get_mutable(table_box()).set_content_height(total_content_height); 540 541 // FIXME: This is a total hack, we should respect the 'height' property. 542 m_automatic_content_height = total_content_height; 543} 544 545CSSPixels TableFormattingContext::automatic_content_height() const 546{ 547 return m_automatic_content_height; 548} 549 550}