blob: 22a29c1b457b51e315d5367d07b2ecef58abfea1 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.h"
#include "third_party/blink/renderer/core/layout/geometry/logical_size.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_node.h"
#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table.h"
#include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_cell.h"
#include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_column.h"
#include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_column_visitor.h"
#include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_row.h"
#include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_section.h"
#include "third_party/blink/renderer/core/layout/ng/table/ng_table_borders.h"
#include "third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.h"
namespace blink {
namespace {
// Mergeable columns cannot be distributed to.
// Make at least one spanned column is distributable.
void EnsureDistributableColumnExists(
wtf_size_t start_column_index,
wtf_size_t span,
NGTableTypes::Columns* column_constraints) {
if (span == 0)
return;
DCHECK_LT(start_column_index, column_constraints->data.size());
wtf_size_t effective_span =
std::min(span, column_constraints->data.size() - start_column_index);
if (effective_span == 0)
return;
NGTableTypes::Column* start_column =
&column_constraints->data[start_column_index];
NGTableTypes::Column* end_column = start_column + effective_span;
NGTableTypes::Column* first_mergeable_column = nullptr;
for (NGTableTypes::Column* column = start_column; column != end_column;
++column) {
if (!column->is_collapsed) {
if (!column->is_mergeable) {
// Found non-collapsed, non mergeable column, nothing to do.
return;
} else if (!first_mergeable_column) {
// Found first non-collapsed, mergeable column.
first_mergeable_column = column;
}
}
}
// The interesting problem being solved here is interaction between
// collapsed and mergeable columns.
// All columns that are created by colspanned cell are mergeable by
// default. Without collapsing, the first column would always be
// marked as !mergeable.
// What to do if the first column collapses? If that was the only
// non-mergeable column, the entire cell would merge into first column,
// and collapse.
// To prevent "whole cell hidden if 1st cell is collapsed",
// we try to make first non-collapsed column mergeable.
// If all columns collapse, first cell is marked as meargable.
if (first_mergeable_column) {
// Some columns were not collapsed, mark first as mergeable.
first_mergeable_column->is_mergeable = false;
} else {
start_column->is_mergeable = false;
}
}
// Applies cell/wide cell constraints to columns.
// Guarantees columns min/max widths have non-empty values.
void ApplyCellConstraintsToColumnConstraints(
const NGTableTypes::CellInlineConstraints& cell_constraints,
LayoutUnit inline_border_spacing,
bool is_fixed_layout,
NGTableTypes::ColspanCells* colspan_cell_constraints,
NGTableTypes::Columns* column_constraints) {
// Satisfy prerequisites for cell merging:
if (column_constraints->data.size() < cell_constraints.size()) {
// Column constraint must exist for each cell.
NGTableTypes::Column default_column;
default_column.is_table_fixed = is_fixed_layout;
default_column.is_mergeable = !is_fixed_layout;
wtf_size_t column_count =
cell_constraints.size() - column_constraints->data.size();
// Must loop because WTF::Vector does not support resize with default value.
for (wtf_size_t i = 0; i < column_count; ++i)
column_constraints->data.push_back(default_column);
DCHECK_EQ(column_constraints->data.size(), cell_constraints.size());
} else if (column_constraints->data.size() > cell_constraints.size()) {
// Trim mergeable columns off the end.
wtf_size_t last_non_merged_column = column_constraints->data.size() - 1;
while (last_non_merged_column + 1 > cell_constraints.size() &&
column_constraints->data[last_non_merged_column].is_mergeable) {
--last_non_merged_column;
}
column_constraints->data.resize(last_non_merged_column + 1);
DCHECK_GE(column_constraints->data.size(), cell_constraints.size());
}
// Make sure there exists a non-mergeable column for each colspanned cell.
for (const NGTableTypes::ColspanCell& colspan_cell :
*colspan_cell_constraints) {
EnsureDistributableColumnExists(colspan_cell.start_column,
colspan_cell.span, column_constraints);
}
// Distribute cell constraints to column constraints.
for (wtf_size_t i = 0; i < cell_constraints.size(); ++i) {
column_constraints->data[i].Encompass(cell_constraints[i]);
}
// Wide cell constraints are sorted by span length/starting column.
auto colspan_cell_less_than = [](const NGTableTypes::ColspanCell& lhs,
const NGTableTypes::ColspanCell& rhs) {
if (lhs.span == rhs.span)
return lhs.start_column < rhs.start_column;
return lhs.span < rhs.span;
};
std::stable_sort(colspan_cell_constraints->begin(),
colspan_cell_constraints->end(), colspan_cell_less_than);
NGTableAlgorithmHelpers::DistributeColspanCellsToColumns(
*colspan_cell_constraints, inline_border_spacing, is_fixed_layout,
column_constraints);
// Column total percentage inline-size is clamped to 100%.
// Auto tables: max(0, 100% minus the sum of percentages of all
// prior columns in the table)
// Fixed tables: scale all percentage columns so that total percentage
// is 100%.
float total_percentage = 0;
for (NGTableTypes::Column& column : column_constraints->data) {
if (column.percent) {
if (!is_fixed_layout && (*column.percent + total_percentage > 100.0))
column.percent = 100 - total_percentage;
total_percentage += *column.percent;
}
// A column may have no min/max inline-sizes if there are no cells in this
// column. E.g. a cell has a large colspan which no other cell belongs to.
column.min_inline_size = column.min_inline_size.value_or(LayoutUnit());
column.max_inline_size = column.max_inline_size.value_or(LayoutUnit());
}
if (is_fixed_layout && total_percentage > 100.0) {
for (NGTableTypes::Column& column : column_constraints->data) {
if (column.percent)
column.percent = *column.percent * 100 / total_percentage;
}
}
}
NGTableTypes::Row ComputeMinimumRowBlockSize(
const NGBlockNode& row,
const LayoutUnit cell_percentage_inline_size,
const bool is_table_block_size_specified,
const NGTableTypes::ColumnLocations& column_locations,
const NGTableBorders& table_borders,
wtf_size_t row_index,
wtf_size_t section_index,
bool is_section_collapsed,
NGTableTypes::CellBlockConstraints* cell_block_constraints,
NGTableTypes::RowspanCells* rowspan_cells,
NGColspanCellTabulator* colspan_cell_tabulator) {
const WritingDirectionMode table_writing_direction =
row.Style().GetWritingDirection();
const bool has_collapsed_borders = table_borders.IsCollapsed();
auto CreateCellConstraintSpace = [&column_locations, &table_writing_direction,
&is_table_block_size_specified,
&has_collapsed_borders,
&cell_percentage_inline_size](
const NGBlockNode& cell,
wtf_size_t start_column_index,
const NGBoxStrut& cell_borders) {
const wtf_size_t start_column = start_column_index;
DCHECK_LT(start_column, column_locations.size());
const wtf_size_t end_column =
std::min(start_column + cell.TableCellColspan() - 1,
column_locations.size() - 1);
const LayoutUnit cell_inline_size = column_locations[end_column].offset +
column_locations[end_column].size -
column_locations[start_column].offset;
// Typically we want these values to match the "layout" pass as close as
// possible. The one exception is "is_hidden_for_paint". This is set to
// true if a cell should be hidden within a collapsed column. If this is
// the case, the size is almost certainly different causing a second layout.
return NGTableAlgorithmUtils::CreateTableCellConstraintSpace(
table_writing_direction, cell, cell_borders,
{cell_inline_size, kIndefiniteSize}, cell_percentage_inline_size,
/* alignment_baseline */ base::nullopt, start_column,
/* is_fixed_block_size_indefinite */ false,
is_table_block_size_specified,
/* is_hidden_for_paint */ false, has_collapsed_borders,
NGCacheSlot::kMeasure);
};
// TODO(layout-ng) Scrollbars should be frozen when computing row sizes.
// This cannot be done today, because fragments with frozen scrollbars
// will be cached. Needs to be fixed in NG framework.
LayoutUnit max_cell_block_size;
base::Optional<float> row_percent;
bool is_constrained = false;
bool is_empty = true;
bool has_rowspan_start = false;
wtf_size_t start_cell_index = cell_block_constraints->size();
NGRowBaselineTabulator row_baseline_tabulator;
// Gather block sizes of all cells.
for (NGBlockNode cell = To<NGBlockNode>(row.FirstChild()); cell;
cell = To<NGBlockNode>(cell.NextSibling())) {
is_empty = false;
colspan_cell_tabulator->FindNextFreeColumn();
const ComputedStyle& cell_style = cell.Style();
const NGBoxStrut cell_borders = table_borders.CellBorder(
cell, row_index, colspan_cell_tabulator->CurrentColumn(), section_index,
table_writing_direction);
const NGConstraintSpace cell_constraint_space = CreateCellConstraintSpace(
cell, colspan_cell_tabulator->CurrentColumn(), cell_borders);
scoped_refptr<const NGLayoutResult> layout_result =
cell.Layout(cell_constraint_space);
const NGBoxFragment fragment(
table_writing_direction,
To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment()));
bool is_parallel =
IsParallelWritingMode(table_writing_direction.GetWritingMode(),
cell.Style().GetWritingMode());
const wtf_size_t rowspan = cell.TableCellRowspan();
NGTableTypes::CellBlockConstraint cell_block_constraint =
NGTableTypes::CreateCellBlockConstraint(
cell, fragment.BlockSize(), fragment.FirstBaselineOrSynthesize(),
cell_borders, row_index, colspan_cell_tabulator->CurrentColumn(),
rowspan);
colspan_cell_tabulator->ProcessCell(cell);
cell_block_constraints->push_back(cell_block_constraint);
is_constrained |= cell_block_constraint.is_constrained && rowspan == 1;
row_baseline_tabulator.ProcessCell(
fragment, cell_block_constraint.min_block_size,
NGTableAlgorithmUtils::IsBaseline(cell_style.VerticalAlign()),
is_parallel,
layout_result->HasDescendantThatDependsOnPercentageBlockSize());
// Compute cell's css block size.
base::Optional<LayoutUnit> cell_css_block_size;
base::Optional<float> cell_css_percent;
const Length& cell_specified_block_length =
is_parallel ? cell_style.LogicalHeight() : cell_style.LogicalWidth();
// TODO(1105272) Handle cell_specified_block_length.IsCalculated()
if (cell_specified_block_length.IsPercent()) {
cell_css_percent = cell_specified_block_length.Percent();
} else if (cell_specified_block_length.IsFixed()) {
// NOTE: Ignore min/max-height for determining the |cell_css_block_size|.
NGBoxStrut cell_padding =
ComputePadding(cell_constraint_space, cell_style);
NGBoxStrut border_padding = cell_borders + cell_padding;
// https://quirks.spec.whatwg.org/#the-table-cell-height-box-sizing-quirk
if (cell.GetDocument().InQuirksMode() ||
cell_style.BoxSizing() == EBoxSizing::kBorderBox) {
cell_css_block_size =
std::max(border_padding.BlockSum(),
LayoutUnit(cell_specified_block_length.Value()));
} else {
cell_css_block_size = border_padding.BlockSum() +
LayoutUnit(cell_specified_block_length.Value());
}
}
if (rowspan == 1) {
if (cell_css_block_size || cell_css_percent)
is_constrained = true;
if (cell_css_percent)
row_percent = std::max(row_percent.value_or(0), *cell_css_percent);
// Cell's block layout ignores CSS block size properties. Row must use it
// to compute it's minimum block size.
max_cell_block_size =
std::max({max_cell_block_size, cell_block_constraint.min_block_size,
cell_css_block_size.value_or(LayoutUnit())});
} else {
has_rowspan_start = true;
rowspan_cells->push_back(NGTableTypes::CreateRowspanCell(
row_index, rowspan, &cell_block_constraint, cell_css_block_size));
}
}
// Apply row's CSS block size.
const Length& row_specified_block_length = row.Style().LogicalHeight();
if (row_specified_block_length.IsPercent()) {
is_constrained = true;
row_percent =
std::max(row_percent.value_or(0), row_specified_block_length.Percent());
} else if (row_specified_block_length.IsFixed()) {
is_constrained = true;
max_cell_block_size = std::max(
LayoutUnit(row_specified_block_length.Value()), max_cell_block_size);
}
const LayoutUnit row_block_size =
row_baseline_tabulator.ComputeRowBlockSize(max_cell_block_size);
const LayoutUnit row_baseline =
row_baseline_tabulator.ComputeBaseline(row_block_size);
return NGTableTypes::Row{
row_block_size,
row_baseline,
row_percent,
start_cell_index,
cell_block_constraints->size() - start_cell_index,
is_constrained,
row_baseline_tabulator
.ComputeBaselineDependsOnPercentageBlockDescendant(),
has_rowspan_start,
/* is_collapsed */ is_section_collapsed ||
row.Style().Visibility() == EVisibility::kCollapse};
}
// Computes inline constraints for COLGROUP/COLs.
class ColumnConstraintsBuilder {
public:
void VisitCol(const NGLayoutInputNode& column,
wtf_size_t start_column_index,
wtf_size_t span) {
// COL creates SPAN constraints. Its width is col css width, or enclosing
// colgroup css width.
NGTableTypes::Column col_constraint =
NGTableTypes::CreateColumn(column.Style(),
!is_fixed_layout_ && colgroup_constraint_
? colgroup_constraint_->max_inline_size
: base::nullopt,
is_fixed_layout_);
for (wtf_size_t i = 0; i < span; ++i)
column_constraints_->data.push_back(col_constraint);
column.GetLayoutBox()->ClearNeedsLayout();
}
void EnterColgroup(const NGLayoutInputNode& colgroup,
wtf_size_t start_column_index) {
colgroup_constraint_ = NGTableTypes::CreateColumn(
colgroup.Style(), base::nullopt, is_fixed_layout_);
}
void LeaveColgroup(const NGLayoutInputNode& colgroup,
wtf_size_t start_column_index,
wtf_size_t span,
bool has_children) {
if (!has_children) {
for (wtf_size_t i = 0; i < span; ++i)
column_constraints_->data.push_back(*colgroup_constraint_);
}
colgroup_constraint_.reset();
colgroup.GetLayoutBox()->ClearNeedsLayout();
To<LayoutNGTableColumn>(colgroup.GetLayoutBox())
->ClearNeedsLayoutForChildren();
}
ColumnConstraintsBuilder(NGTableTypes::Columns* column_constraints,
bool is_fixed_layout)
: column_constraints_(column_constraints),
is_fixed_layout_(is_fixed_layout) {}
private:
NGTableTypes::Columns* column_constraints_;
bool is_fixed_layout_;
base::Optional<NGTableTypes::Column> colgroup_constraint_;
};
// Computes constraints specified on column elements.
void ComputeColumnElementConstraints(
const Vector<NGBlockNode>& columns,
bool is_fixed_layout,
NGTableTypes::Columns* column_constraints) {
ColumnConstraintsBuilder constraints_builder(column_constraints,
is_fixed_layout);
// |table_column_count| is UINT_MAX because columns will get trimmed later.
VisitLayoutNGTableColumn(columns, UINT_MAX, &constraints_builder);
}
void ComputeSectionInlineConstraints(
const NGBlockNode& section,
bool is_fixed_layout,
bool is_first_section,
WritingMode table_writing_mode,
const NGTableBorders& table_borders,
wtf_size_t section_index,
wtf_size_t* row_index,
NGTableTypes::CellInlineConstraints* cell_inline_constraints,
NGTableTypes::ColspanCells* colspan_cell_inline_constraints) {
WritingDirectionMode table_writing_direction =
section.Style().GetWritingDirection();
NGColspanCellTabulator colspan_cell_tabulator;
bool is_first_row = true;
for (NGBlockNode row = To<NGBlockNode>(section.FirstChild()); row;
row = To<NGBlockNode>(row.NextSibling())) {
colspan_cell_tabulator.StartRow();
// Gather constraints for each cell, and merge them into
// CellInlineConstraints.
for (NGBlockNode cell = To<NGBlockNode>(row.FirstChild()); cell;
cell = To<NGBlockNode>(cell.NextSibling())) {
colspan_cell_tabulator.FindNextFreeColumn();
wtf_size_t colspan = cell.TableCellColspan();
bool ignore_because_of_fixed_layout =
is_fixed_layout && (!is_first_section || !is_first_row);
wtf_size_t max_column = NGTableAlgorithmHelpers::ComputeMaxColumn(
colspan_cell_tabulator.CurrentColumn(), colspan, is_fixed_layout);
if (max_column >= cell_inline_constraints->size())
cell_inline_constraints->Grow(max_column);
if (!ignore_because_of_fixed_layout) {
NGBoxStrut cell_border = table_borders.CellBorder(
cell, *row_index, colspan_cell_tabulator.CurrentColumn(),
section_index, table_writing_direction);
NGBoxStrut cell_padding = table_borders.CellPaddingForMeasure(
cell.Style(), table_writing_direction);
NGTableTypes::CellInlineConstraint cell_constraint =
NGTableTypes::CreateCellInlineConstraint(
cell, table_writing_mode, is_fixed_layout, cell_border,
cell_padding, table_borders.IsCollapsed());
if (colspan == 1) {
base::Optional<NGTableTypes::CellInlineConstraint>& constraint =
(*cell_inline_constraints)[colspan_cell_tabulator
.CurrentColumn()];
// Standard cell, update final column inline size values.
if (constraint.has_value()) {
constraint->Encompass(cell_constraint);
} else {
constraint = cell_constraint;
}
} else {
colspan_cell_inline_constraints->emplace_back(
cell_constraint, colspan_cell_tabulator.CurrentColumn(), colspan);
}
}
colspan_cell_tabulator.ProcessCell(cell);
}
is_first_row = false;
*row_index += 1;
colspan_cell_tabulator.EndRow();
}
}
} // namespace
// static
NGConstraintSpace NGTableAlgorithmUtils::CreateTableCellConstraintSpace(
const WritingDirectionMode table_writing_direction,
const NGBlockNode cell,
const NGBoxStrut& cell_borders,
LogicalSize cell_size,
LayoutUnit percentage_inline_size,
base::Optional<LayoutUnit> alignment_baseline,
wtf_size_t column_index,
bool is_fixed_block_size_indefinite,
bool is_table_block_size_specified,
bool is_hidden_for_paint,
bool has_collapsed_borders,
NGCacheSlot cache_slot) {
const auto& cell_style = cell.Style();
const auto table_writing_mode = table_writing_direction.GetWritingMode();
NGConstraintSpaceBuilder builder(table_writing_mode,
cell_style.GetWritingDirection(),
/* is_new_fc */ true);
builder.SetIsTableCell(true, /* is_legacy_table_cell */ false);
if (!IsParallelWritingMode(table_writing_mode, cell_style.GetWritingMode())) {
const PhysicalSize icb_size = cell.InitialContainingBlockSize();
builder.SetOrthogonalFallbackInlineSize(
table_writing_direction.IsHorizontal() ? icb_size.height
: icb_size.width);
}
builder.SetAvailableSize(cell_size);
builder.SetIsFixedInlineSize(true);
if (cell_size.block_size != kIndefiniteSize) {
builder.SetIsFixedBlockSize(true);
builder.SetIsFixedBlockSizeIndefinite(is_fixed_block_size_indefinite);
}
// Standard:
// https://www.w3.org/TR/css-tables-3/#computing-the-table-height "the
// computed height (if definite, percentages being considered 0px)"
builder.SetPercentageResolutionSize(
{percentage_inline_size, kIndefiniteSize});
builder.SetTableCellBorders(cell_borders);
builder.SetTableCellAlignmentBaseline(alignment_baseline);
builder.SetTableCellColumnIndex(column_index);
builder.SetIsRestrictedBlockSizeTableCell(is_table_block_size_specified);
builder.SetIsTableCellHiddenForPaint(is_hidden_for_paint);
builder.SetIsTableCellWithCollapsedBorders(has_collapsed_borders);
builder.SetHideTableCellIfEmpty(
!has_collapsed_borders && cell_style.EmptyCells() == EEmptyCells::kHide);
builder.SetCacheSlot(cache_slot);
return builder.ToConstraintSpace();
}
scoped_refptr<NGTableTypes::Columns>
NGTableAlgorithmUtils::ComputeColumnConstraints(
const NGBlockNode& table,
const NGTableGroupedChildren& grouped_children,
const NGTableBorders& table_borders,
const NGBoxStrut& border_padding) {
bool is_fixed_layout = table.Style().IsFixedTableLayout();
WritingMode table_writing_mode = table.Style().GetWritingMode();
LogicalSize border_spacing = table.Style().TableBorderSpacing();
NGTableTypes::CellInlineConstraints cell_inline_constraints;
NGTableTypes::ColspanCells colspan_cell_constraints;
scoped_refptr<NGTableTypes::Columns> column_constraints =
base::MakeRefCounted<NGTableTypes::Columns>();
ComputeColumnElementConstraints(grouped_children.columns, is_fixed_layout,
column_constraints.get());
// Collect section constraints
bool is_first_section = true;
wtf_size_t row_index = 0;
wtf_size_t section_index = 0;
for (const NGBlockNode& section : grouped_children) {
if (!section.IsEmptyTableSection()) {
ComputeSectionInlineConstraints(
section, is_fixed_layout, is_first_section, table_writing_mode,
table_borders, section_index, &row_index, &cell_inline_constraints,
&colspan_cell_constraints);
is_first_section = false;
}
section_index++;
}
ApplyCellConstraintsToColumnConstraints(
cell_inline_constraints, border_spacing.inline_size, is_fixed_layout,
&colspan_cell_constraints, column_constraints.get());
return column_constraints;
}
void NGTableAlgorithmUtils::ComputeSectionMinimumRowBlockSizes(
const NGBlockNode& section,
const LayoutUnit cell_percentage_inline_size,
const bool is_table_block_size_restricted,
const NGTableTypes::ColumnLocations& column_locations,
const NGTableBorders& table_borders,
const LayoutUnit block_border_spacing,
wtf_size_t section_index,
bool treat_section_as_tbody,
NGTableTypes::Sections* sections,
NGTableTypes::Rows* rows,
NGTableTypes::CellBlockConstraints* cell_block_constraints) {
wtf_size_t start_row = rows->size();
wtf_size_t current_row = start_row;
NGTableTypes::RowspanCells rowspan_cells;
LayoutUnit section_block_size;
// Used to compute column index.
NGColspanCellTabulator colspan_cell_tabulator;
// total_row_percent must be under 100%
float total_row_percent = 0;
// Get minimum block size of each row.
for (NGBlockNode row = To<NGBlockNode>(section.FirstChild()); row;
row = To<NGBlockNode>(row.NextSibling())) {
colspan_cell_tabulator.StartRow();
NGTableTypes::Row row_constraint = ComputeMinimumRowBlockSize(
row, cell_percentage_inline_size, is_table_block_size_restricted,
column_locations, table_borders, current_row++, section_index,
/* is_section_collapsed */ section.Style().Visibility() ==
EVisibility::kCollapse,
cell_block_constraints, &rowspan_cells, &colspan_cell_tabulator);
if (row_constraint.percent.has_value()) {
row_constraint.percent =
std::min(100.0f - total_row_percent, *row_constraint.percent);
total_row_percent += *row_constraint.percent;
}
rows->push_back(row_constraint);
section_block_size += row_constraint.block_size;
colspan_cell_tabulator.EndRow();
}
const wtf_size_t block_spacing_count =
current_row == start_row ? 0 : current_row - start_row - 1;
const LayoutUnit border_spacing_total =
block_border_spacing * block_spacing_count;
section_block_size += border_spacing_total;
// Redistribute rowspanned cell block sizes.
std::stable_sort(rowspan_cells.begin(), rowspan_cells.end());
for (NGTableTypes::RowspanCell& rowspan_cell : rowspan_cells) {
// Spec: rowspan of 0 means all remaining rows.
if (rowspan_cell.span == 0)
rowspan_cell.span = current_row - rowspan_cell.start_row;
// Truncate rows that are too long.
rowspan_cell.span =
std::min(current_row - rowspan_cell.start_row, rowspan_cell.span);
NGTableAlgorithmHelpers::DistributeRowspanCellToRows(
rowspan_cell, block_border_spacing, rows);
}
// Redistribute section's css block size.
const Length& section_specified_block_length =
section.Style().LogicalHeight();
// TODO(1105272) Handle section_specified_block_length.IsCalculated()
if (section_specified_block_length.IsFixed()) {
LayoutUnit section_fixed_block_size =
LayoutUnit(section_specified_block_length.Value());
if (section_fixed_block_size > section_block_size) {
NGTableAlgorithmHelpers::DistributeSectionFixedBlockSizeToRows(
start_row, current_row - start_row, section_fixed_block_size,
block_border_spacing, section_fixed_block_size, rows);
section_block_size = section_fixed_block_size;
}
}
sections->push_back(
NGTableTypes::CreateSection(section, start_row, current_row - start_row,
section_block_size, treat_section_as_tbody));
}
void NGColspanCellTabulator::StartRow() {
current_column_ = 0;
}
// Remove colspanned cells that are not spanning any more rows.
void NGColspanCellTabulator::EndRow() {
for (wtf_size_t i = 0; i < colspanned_cells_.size();) {
colspanned_cells_[i].remaining_rows--;
if (colspanned_cells_[i].remaining_rows == 0)
colspanned_cells_.EraseAt(i);
else
++i;
}
std::sort(colspanned_cells_.begin(), colspanned_cells_.end(),
[](const NGColspanCellTabulator::Cell& a,
const NGColspanCellTabulator::Cell& b) {
return a.column_start < b.column_start;
});
}
// Advance current column to position not occupied by colspanned cells.
void NGColspanCellTabulator::FindNextFreeColumn() {
for (const Cell& colspanned_cell : colspanned_cells_) {
if (colspanned_cell.column_start <= current_column_ &&
colspanned_cell.column_start + colspanned_cell.span > current_column_) {
current_column_ = colspanned_cell.column_start + colspanned_cell.span;
}
}
}
void NGColspanCellTabulator::ProcessCell(const NGBlockNode& cell) {
wtf_size_t colspan = cell.TableCellColspan();
wtf_size_t rowspan = cell.TableCellRowspan();
if (rowspan > 1)
colspanned_cells_.emplace_back(current_column_, colspan, rowspan);
current_column_ += colspan;
}
void NGRowBaselineTabulator::ProcessCell(
const NGBoxFragment& fragment,
const LayoutUnit cell_min_block_size,
const bool is_baseline_aligned,
const bool is_parallel,
const bool descendant_depends_on_percentage_block_size) {
if (is_parallel && is_baseline_aligned &&
fragment.HasDescendantsForTablePart()) {
max_cell_baseline_depends_on_percentage_block_descendant_ |=
descendant_depends_on_percentage_block_size;
const LayoutUnit cell_baseline = fragment.FirstBaselineOrSynthesize();
max_cell_ascent_ =
std::max(max_cell_ascent_.value_or(LayoutUnit::Min()), cell_baseline);
max_cell_descent_ = std::max(max_cell_descent_.value_or(LayoutUnit::Min()),
cell_min_block_size - cell_baseline);
}
// https://www.w3.org/TR/css-tables-3/#row-layout "If there is no such
// line box or table-row, the baseline is the bottom of content edge of
// the cell box."
if (!max_cell_ascent_) {
fallback_cell_depends_on_percentage_block_descendant_ |=
descendant_depends_on_percentage_block_size;
const LayoutUnit cell_block_end_border_padding =
fragment.Padding().block_end + fragment.Borders().block_end;
fallback_cell_descent_ =
std::min(fallback_cell_descent_.value_or(LayoutUnit::Max()),
cell_block_end_border_padding);
}
}
LayoutUnit NGRowBaselineTabulator::ComputeRowBlockSize(
const LayoutUnit max_cell_block_size) {
if (max_cell_ascent_) {
return std::max(max_cell_block_size,
*max_cell_ascent_ + *max_cell_descent_);
}
return max_cell_block_size;
}
LayoutUnit NGRowBaselineTabulator::ComputeBaseline(
const LayoutUnit row_block_size) {
if (max_cell_ascent_)
return *max_cell_ascent_;
if (fallback_cell_descent_)
return (row_block_size - *fallback_cell_descent_).ClampNegativeToZero();
// Empty row's baseline is top.
return LayoutUnit();
}
bool NGRowBaselineTabulator::
ComputeBaselineDependsOnPercentageBlockDescendant() {
if (max_cell_ascent_)
return max_cell_baseline_depends_on_percentage_block_descendant_;
if (fallback_cell_descent_)
return fallback_cell_depends_on_percentage_block_descendant_;
return false;
}
} // namespace blink