blob: 1b76c2c5479af5a3ff6babbc08341ef9707f7361 [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.h"
#include "third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.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_out_of_flow_layout_part.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_space_utils.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_caption.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_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_constraint_space_data.h"
#include "third_party/blink/renderer/core/layout/ng/table/ng_table_fragment_data.h"
#include "third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.h"
#include "third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.h"
#include "third_party/blink/renderer/core/layout/ng/table/ng_table_node.h"
#include "third_party/blink/renderer/core/layout/text_autosizer.h"
namespace blink {
namespace {
NGTableTypes::Caption ComputeCaptionConstraint(
const ComputedStyle& table_style,
const NGTableGroupedChildren& grouped_children) {
// Caption inline size constraints.
NGTableTypes::Caption caption_min_max;
for (const NGBlockNode& caption : grouped_children.captions) {
// Caption %-block-sizes are treated as auto, as there isn't a reasonable
// block-size to resolve against.
NGBoxStrut margins = ComputeMinMaxMargins(table_style, caption);
MinMaxSizes min_max_size =
ComputeMinAndMaxContentContribution(
table_style, caption,
MinMaxSizesInput(kIndefiniteSize, MinMaxSizesType::kContent))
.sizes;
min_max_size += margins.InlineSum();
caption_min_max.Encompass(min_max_size);
}
return caption_min_max;
}
LayoutUnit ComputeUndistributableTableSpace(
const NGTableTypes::Columns& column_constraints,
LayoutUnit inline_table_border_padding,
LayoutUnit inline_border_spacing) {
unsigned inline_space_count = 2;
bool is_first_column = true;
for (const NGTableTypes::Column& column : column_constraints.data) {
if (!column.is_mergeable) {
if (is_first_column)
is_first_column = false;
else
inline_space_count++;
}
}
return inline_table_border_padding +
inline_space_count * inline_border_spacing;
}
// Empty table sizes have been a source of many inconsistencies
// between browsers.
LayoutUnit ComputeEmptyTableInlineSize(
const NGConstraintSpace& space,
const ComputedStyle& table_style,
const LayoutUnit assignable_table_inline_size,
const LayoutUnit undistributable_space,
const NGTableTypes::Caption& caption_constraint,
const NGBoxStrut& table_border_padding,
const bool has_collapsed_borders) {
// If table has a css inline size, use that.
if (space.IsFixedInlineSize() || !table_style.LogicalWidth().IsAuto() ||
!table_style.LogicalMinWidth().IsAuto()) {
return assignable_table_inline_size + undistributable_space;
}
// If there is a caption, it defines table wrapper inline size.
if (caption_constraint.min_size) {
return std::max(caption_constraint.min_size,
table_border_padding.InlineSum());
}
// Table is defined by its border/padding.
if (has_collapsed_borders)
return LayoutUnit();
return assignable_table_inline_size + table_border_padding.InlineSum();
}
// standard: https://www.w3.org/TR/css-tables-3/#computing-the-table-width
LayoutUnit ComputeAssignableTableInlineSize(
const NGTableNode& table,
const NGConstraintSpace& space,
const NGTableTypes::Columns& column_constraints,
const NGTableTypes::Caption& caption_constraint,
const LayoutUnit undistributable_space,
const NGBoxStrut& table_border_padding,
const bool is_fixed_layout) {
if (space.IsFixedInlineSize()) {
return (space.AvailableSize().inline_size - undistributable_space)
.ClampNegativeToZero();
}
const MinMaxSizes grid_min_max =
NGTableAlgorithmHelpers::ComputeGridInlineMinMax(
table, column_constraints, undistributable_space, is_fixed_layout,
/* is_layout_pass */ true,
/* skip_collapsed_columns */ false);
// Standard: "used width of the table".
LayoutUnit used_table_inline_size = ComputeUsedInlineSizeForTableFragment(
space, table, table_border_padding, grid_min_max);
// Don't allow the inline-size to go below the grid, or caption min-size.
used_table_inline_size =
std::max({used_table_inline_size, caption_constraint.min_size,
grid_min_max.min_size});
// Standard: The assignable table width is the "used width of the table"
// minus the total horizontal border spacing.
const LayoutUnit assignable_table_inline_size =
used_table_inline_size - undistributable_space;
DCHECK_GE(assignable_table_inline_size, LayoutUnit());
return assignable_table_inline_size;
}
// If |shrink_collapsed| is true, collapsed columns have zero width.
void ComputeLocationsFromColumns(
const NGTableTypes::Columns& column_constraints,
const Vector<LayoutUnit>& column_sizes,
LayoutUnit inline_border_spacing,
bool shrink_collapsed,
NGTableTypes::ColumnLocations* column_locations,
bool* has_collapsed_columns) {
*has_collapsed_columns = false;
column_locations->resize(column_constraints.data.size());
if (column_locations->IsEmpty())
return;
LayoutUnit column_offset = inline_border_spacing;
for (wtf_size_t i = 0; i < column_constraints.data.size(); ++i) {
auto& column_location = (*column_locations)[i];
auto& column_constraint = column_constraints.data[i];
*has_collapsed_columns =
*has_collapsed_columns || column_constraint.is_collapsed;
column_location.offset = column_offset;
if (column_constraints.data[i].is_mergeable &&
(column_sizes[i] == kIndefiniteSize ||
column_sizes[i] == LayoutUnit())) {
// Empty mergeable columns are treated as collapsed.
column_location.size = LayoutUnit();
column_location.is_collapsed = true;
} else if (shrink_collapsed && column_constraint.is_collapsed) {
column_location.is_collapsed = true;
column_location.size = LayoutUnit();
} else {
column_location.is_collapsed = false;
column_location.size =
column_sizes[i] != kIndefiniteSize ? column_sizes[i] : LayoutUnit();
column_offset += column_location.size + inline_border_spacing;
}
}
}
scoped_refptr<const NGTableConstraintSpaceData> CreateConstraintSpaceData(
const ComputedStyle& style,
const NGTableTypes::ColumnLocations& column_locations,
const NGTableTypes::Sections& sections,
const NGTableTypes::Rows& rows,
const NGTableTypes::CellBlockConstraints& cell_block_constraints,
const LayoutUnit table_inline_size,
const LogicalSize& border_spacing) {
scoped_refptr<NGTableConstraintSpaceData> data =
base::MakeRefCounted<NGTableConstraintSpaceData>();
data->table_inline_size = table_inline_size;
data->table_writing_direction = style.GetWritingDirection();
data->table_border_spacing = border_spacing;
data->is_table_block_size_specified = !style.LogicalHeight().IsAuto();
data->hide_table_cell_if_empty =
style.EmptyCells() == EEmptyCells::kHide &&
style.BorderCollapse() == EBorderCollapse::kSeparate;
data->has_collapsed_borders =
style.BorderCollapse() == EBorderCollapse::kCollapse;
data->column_locations.ReserveCapacity(column_locations.size());
for (const auto& location : column_locations) {
data->column_locations.emplace_back(location.offset, location.size,
location.is_collapsed);
}
data->sections.ReserveCapacity(sections.size());
for (const auto& section : sections)
data->sections.emplace_back(section.start_row, section.rowspan);
data->rows.ReserveCapacity(rows.size());
for (const auto& row : rows) {
data->rows.emplace_back(
row.baseline, row.block_size, row.start_cell_index, row.cell_count,
row.has_baseline_aligned_percentage_block_size_descendants,
row.is_collapsed);
}
data->cells.ReserveCapacity(cell_block_constraints.size());
// Traversing from section is necessary to limit cell's rowspan to the
// section. The cell does not know what section it is in.
for (const auto& section : sections) {
for (wtf_size_t row_index = section.start_row;
row_index < section.start_row + section.rowspan; ++row_index) {
for (wtf_size_t cell_index = rows[row_index].start_cell_index;
cell_index <
rows[row_index].start_cell_index + rows[row_index].cell_count;
++cell_index) {
wtf_size_t max_rowspan =
section.start_row + section.rowspan - row_index;
wtf_size_t rowspan =
std::min(cell_block_constraints[cell_index].rowspan, max_rowspan);
// Compute cell's size.
LayoutUnit cell_block_size;
for (wtf_size_t i = 0; i < rowspan; ++i) {
if (!rows[row_index + i].is_collapsed) {
cell_block_size += rows[row_index + i].block_size;
if (i != 0)
cell_block_size += border_spacing.block_size;
}
}
data->cells.emplace_back(
cell_block_constraints[cell_index].border_box_borders,
cell_block_size, cell_block_constraints[cell_index].column_index,
cell_block_constraints[cell_index].is_constrained);
}
}
}
return data;
}
// Columns do not generate fragments.
// Column geometry is needed for painting, and is stored
// in NGTableFragmentData. Geometry data is also copied
// back to LayoutObject.
class ColumnGeometriesBuilder {
public:
void VisitCol(const NGLayoutInputNode& col,
wtf_size_t start_column_index,
wtf_size_t span) {
wtf_size_t end_column_index = start_column_index + span - 1;
DCHECK_LE(end_column_index, column_locations.size() - 1);
LayoutUnit column_inline_size = column_locations[end_column_index].offset +
column_locations[end_column_index].size -
column_locations[start_column_index].offset;
col.GetLayoutBox()->SetLogicalWidth(column_inline_size);
col.GetLayoutBox()->SetLogicalHeight(table_grid_block_size);
column_geometries.emplace_back(start_column_index, span,
column_locations[start_column_index].offset -
border_spacing.inline_size,
column_inline_size, col);
}
void EnterColgroup(const NGLayoutInputNode& colgroup,
wtf_size_t start_column_index) {}
void LeaveColgroup(const NGLayoutInputNode& colgroup,
wtf_size_t start_column_index,
wtf_size_t span,
bool has_children) {
if (span == 0)
return;
wtf_size_t last_column_index = start_column_index + span - 1;
LayoutUnit colgroup_size = column_locations[last_column_index].offset +
column_locations[last_column_index].size -
column_locations[start_column_index].offset;
colgroup.GetLayoutBox()->SetLogicalWidth(colgroup_size);
colgroup.GetLayoutBox()->SetLogicalHeight(table_grid_block_size);
column_geometries.emplace_back(start_column_index, span,
column_locations[start_column_index].offset -
border_spacing.inline_size,
colgroup_size, colgroup);
}
void Sort() {
// Geometries need to be sorted because this must be true:
// - parent COLGROUP must come before child COLs.
// - child COLs are in ascending order.
std::sort(column_geometries.begin(), column_geometries.end(),
[](const NGTableFragmentData::ColumnGeometry& a,
const NGTableFragmentData::ColumnGeometry& b) {
if (a.node.IsTableCol() && b.node.IsTableCol()) {
return a.start_column < b.start_column;
}
if (a.node.IsTableColgroup()) {
if (b.node.IsTableColgroup())
return a.start_column < b.start_column;
if (a.start_column <= b.start_column &&
(a.start_column + a.span) > b.start_column) {
return true;
}
return a.start_column < b.start_column;
} else {
DCHECK(b.node.IsTableColgroup());
if (b.start_column <= a.start_column &&
(b.start_column + b.span) > a.start_column) {
return false;
}
return b.start_column >= a.start_column;
}
});
}
ColumnGeometriesBuilder(const NGTableTypes::ColumnLocations& column_locations,
LayoutUnit table_grid_block_size,
const LogicalSize& border_spacing)
: column_locations(column_locations),
table_grid_block_size(table_grid_block_size),
border_spacing(border_spacing) {}
NGTableFragmentData::ColumnGeometries column_geometries;
const NGTableTypes::ColumnLocations& column_locations;
const LayoutUnit table_grid_block_size;
const LogicalSize& border_spacing;
};
LayoutUnit ComputeTableSizeFromColumns(
const NGTableTypes::ColumnLocations column_locations,
const NGBoxStrut& table_border_padding,
const LogicalSize& border_spacing) {
return column_locations.back().offset + column_locations.back().size +
table_border_padding.InlineSum() + border_spacing.inline_size;
}
} // namespace
LayoutUnit NGTableLayoutAlgorithm::ComputeTableInlineSize(
const NGTableNode& table,
const NGConstraintSpace& space,
const NGBoxStrut& table_border_padding) {
const bool is_fixed_layout = table.Style().IsFixedTableLayout();
// Tables need autosizer.
base::Optional<TextAutosizer::TableLayoutScope> text_autosizer;
if (!is_fixed_layout)
text_autosizer.emplace(To<LayoutNGTable>(table.GetLayoutBox()));
const LogicalSize border_spacing = table.Style().TableBorderSpacing();
NGTableGroupedChildren grouped_children(table);
scoped_refptr<const NGTableBorders> table_borders = table.GetTableBorders();
// Compute min/max inline constraints.
const scoped_refptr<const NGTableTypes::Columns> column_constraints =
table.GetColumnConstraints(grouped_children, table_border_padding);
const NGTableTypes::Caption caption_constraint =
ComputeCaptionConstraint(table.Style(), grouped_children);
const LayoutUnit undistributable_space = ComputeUndistributableTableSpace(
*column_constraints, table_border_padding.InlineSum(),
border_spacing.inline_size);
const LayoutUnit assignable_table_inline_size =
ComputeAssignableTableInlineSize(
table, space, *column_constraints, caption_constraint,
undistributable_space, table_border_padding, is_fixed_layout);
if (column_constraints->data.IsEmpty()) {
return ComputeEmptyTableInlineSize(
space, table.Style(), assignable_table_inline_size,
undistributable_space, caption_constraint, table_border_padding,
table_borders->IsCollapsed());
}
const Vector<LayoutUnit> column_sizes =
NGTableAlgorithmHelpers::SynchronizeAssignableTableInlineSizeAndColumns(
assignable_table_inline_size, border_spacing.inline_size,
is_fixed_layout, *column_constraints);
// Final inline size must depend on column locations, because columns can be
// hidden.
NGTableTypes::ColumnLocations column_locations;
bool has_collapsed_columns;
ComputeLocationsFromColumns(
*column_constraints, column_sizes, border_spacing.inline_size,
/* collapse_columns */ true, &column_locations, &has_collapsed_columns);
return std::max(ComputeTableSizeFromColumns(
column_locations, table_border_padding, border_spacing),
caption_constraint.min_size);
}
scoped_refptr<const NGLayoutResult> NGTableLayoutAlgorithm::Layout() {
DCHECK(!BreakToken());
const bool is_fixed_layout = Style().IsFixedTableLayout();
const LogicalSize border_spacing = Style().TableBorderSpacing();
NGTableGroupedChildren grouped_children(Node());
const scoped_refptr<const NGTableBorders> table_borders =
Node().GetTableBorders();
DCHECK(table_borders.get());
const NGBoxStrut border_padding = container_builder_.BorderPadding();
// Algorithm:
// - Compute inline constraints.
// - Redistribute assignble table inline size to inline constraints.
// - Compute column locations.
// - Compute row block sizes.
// - Generate fragment.
const scoped_refptr<const NGTableTypes::Columns> column_constraints =
Node().GetColumnConstraints(grouped_children, border_padding);
const NGTableTypes::Caption caption_constraint =
ComputeCaptionConstraint(Style(), grouped_children);
// Compute assignable table inline size.
// Standard: https://www.w3.org/TR/css-tables-3/#width-distribution
const LayoutUnit undistributable_space = ComputeUndistributableTableSpace(
*column_constraints, border_padding.InlineSum(),
border_spacing.inline_size);
const LayoutUnit assignable_table_inline_size =
ComputeAssignableTableInlineSize(
Node(), ConstraintSpace(), *column_constraints, caption_constraint,
undistributable_space, border_padding, is_fixed_layout);
// Distribute assignable table width.
const Vector<LayoutUnit> column_sizes =
NGTableAlgorithmHelpers::SynchronizeAssignableTableInlineSizeAndColumns(
assignable_table_inline_size, border_spacing.inline_size,
is_fixed_layout, *column_constraints);
NGTableTypes::ColumnLocations column_locations;
bool has_collapsed_columns;
ComputeLocationsFromColumns(
*column_constraints, column_sizes, border_spacing.inline_size,
/* shrink_collapsed */ false, &column_locations, &has_collapsed_columns);
LayoutUnit table_inline_size_before_collapse;
const bool is_grid_empty = column_locations.IsEmpty();
if (is_grid_empty) {
table_inline_size_before_collapse = ComputeEmptyTableInlineSize(
ConstraintSpace(), Style(), assignable_table_inline_size,
undistributable_space, caption_constraint, border_padding,
table_borders->IsCollapsed());
} else {
table_inline_size_before_collapse = ComputeTableSizeFromColumns(
column_locations, border_padding, border_spacing);
}
// Before we can determine the block-size of the sections/rows, we need to
// layout all of our captions.
//
// The block-size taken by the captions, *subtracts* from the available
// block-size given to the table-grid.
Vector<CaptionResult> captions;
LayoutUnit captions_block_size;
ComputeCaptionFragments(grouped_children, container_builder_.InlineSize(),
captions, captions_block_size);
NGTableTypes::Rows rows;
NGTableTypes::CellBlockConstraints cell_block_constraints;
NGTableTypes::Sections sections;
LayoutUnit minimal_table_grid_block_size;
ComputeRows(table_inline_size_before_collapse - border_padding.InlineSum(),
grouped_children, column_locations, *table_borders,
border_spacing, border_padding, captions_block_size,
is_fixed_layout, &rows, &cell_block_constraints, &sections,
&minimal_table_grid_block_size);
if (has_collapsed_columns) {
ComputeLocationsFromColumns(
*column_constraints, column_sizes, border_spacing.inline_size,
/* shrink_collapsed */ true, &column_locations, &has_collapsed_columns);
}
#if DCHECK_IS_ON()
if (!has_collapsed_columns) {
// Colums define table whose inline size equals InitialFragmentGeometry.
DCHECK_EQ(table_inline_size_before_collapse,
container_builder_.InlineSize());
} else if (ConstraintSpace().IsFixedInlineSize()) {
// Collapsed columns + fixed inline size: columns define table whose
// inline size is less or equal InitialFragmentGeometry.
LayoutUnit table_inline_size =
std::max(ComputeTableSizeFromColumns(column_locations, border_padding,
border_spacing),
caption_constraint.min_size);
DCHECK_LE(table_inline_size, container_builder_.InlineSize());
} else {
LayoutUnit table_inline_size =
std::max(ComputeTableSizeFromColumns(column_locations, border_padding,
border_spacing),
caption_constraint.min_size);
DCHECK_EQ(table_inline_size, container_builder_.InlineSize());
}
#endif
return GenerateFragment(container_builder_.InlineSize(),
minimal_table_grid_block_size, grouped_children,
column_locations, rows, cell_block_constraints,
sections, captions, *table_borders,
is_grid_empty ? LogicalSize() : border_spacing);
}
MinMaxSizesResult NGTableLayoutAlgorithm::ComputeMinMaxSizes(
const MinMaxSizesInput& input) const {
const bool is_fixed_layout = Style().IsFixedTableLayout();
// Tables need autosizer.
base::Optional<TextAutosizer::TableLayoutScope> text_autosizer;
if (!is_fixed_layout)
text_autosizer.emplace(To<LayoutNGTable>(Node().GetLayoutBox()));
const LogicalSize border_spacing = Style().TableBorderSpacing();
NGTableGroupedChildren grouped_children(Node());
const scoped_refptr<const NGTableBorders> table_borders =
Node().GetTableBorders();
const NGBoxStrut border_padding = container_builder_.BorderPadding();
const scoped_refptr<const NGTableTypes::Columns> column_constraints =
Node().GetColumnConstraints(grouped_children, border_padding);
const NGTableTypes::Caption caption_constraint =
ComputeCaptionConstraint(Style(), grouped_children);
const LayoutUnit undistributable_space = ComputeUndistributableTableSpace(
*column_constraints, border_padding.InlineSum(),
border_spacing.inline_size);
const MinMaxSizes grid_min_max =
NGTableAlgorithmHelpers::ComputeGridInlineMinMax(
Node(), *column_constraints, undistributable_space, is_fixed_layout,
/* is_layout_pass */ false,
/* skip_collapsed_columns */ false);
MinMaxSizes min_max{
std::max(grid_min_max.min_size, caption_constraint.min_size),
std::max(grid_min_max.max_size, caption_constraint.min_size)};
if (is_fixed_layout && Style().LogicalWidth().IsPercentOrCalc() &&
Node().AllowsInfiniteMaxInlineSize()) {
min_max.max_size = NGTableTypes::kTableMaxInlineSize;
}
DCHECK_LE(min_max.min_size, min_max.max_size);
return MinMaxSizesResult{min_max,
/* depends_on_percentage_block_size */ false};
}
void NGTableLayoutAlgorithm::ComputeRows(
const LayoutUnit table_grid_inline_size,
const NGTableGroupedChildren& grouped_children,
const NGTableTypes::ColumnLocations& column_locations,
const NGTableBorders& table_borders,
const LogicalSize& border_spacing,
const NGBoxStrut& table_border_padding,
const LayoutUnit captions_block_size,
bool is_fixed,
NGTableTypes::Rows* rows,
NGTableTypes::CellBlockConstraints* cell_block_constraints,
NGTableTypes::Sections* sections,
LayoutUnit* minimal_table_grid_block_size) {
DCHECK_EQ(rows->size(), 0u);
DCHECK_EQ(cell_block_constraints->size(), 0u);
const bool is_table_block_size_specified = !Style().LogicalHeight().IsAuto();
LayoutUnit total_table_block_size;
wtf_size_t section_index = 0;
for (auto it = grouped_children.begin(); it != grouped_children.end(); ++it) {
NGTableAlgorithmUtils::ComputeSectionMinimumRowBlockSizes(
*it, table_grid_inline_size, is_table_block_size_specified,
column_locations, table_borders, border_spacing.block_size,
section_index++, it.TreatAsTBody(), sections, rows,
cell_block_constraints);
total_table_block_size += sections->back().block_size;
}
// If we can correctly resolve our min-block-size we want to distribute
// sections/rows into this space. Pass a definite intrinsic block-size into
// |ComputeBlockSizeForFragment| to force it to resolve.
const LayoutUnit intrinsic_block_size =
BlockLengthUnresolvable(ConstraintSpace(), Style().LogicalMinHeight())
? kIndefiniteSize
: table_border_padding.BlockSum();
const LayoutUnit css_table_block_size = ComputeBlockSizeForFragment(
ConstraintSpace(), Style(), table_border_padding, intrinsic_block_size,
table_grid_inline_size, Node().ShouldBeConsideredAsReplaced(),
/* available_block_size_adjustment */ captions_block_size);
// In quirks mode, empty tables ignore any specified block-size.
const bool is_empty_quirks_mode_table =
Node().GetDocument().InQuirksMode() &&
grouped_children.begin() == grouped_children.end();
// Redistribute CSS table block size if necessary.
if (css_table_block_size != kIndefiniteSize && !is_empty_quirks_mode_table) {
*minimal_table_grid_block_size = css_table_block_size;
LayoutUnit distributable_block_size = std::max(
LayoutUnit(), css_table_block_size - table_border_padding.BlockSum());
if (distributable_block_size > total_table_block_size) {
NGTableAlgorithmHelpers::DistributeTableBlockSizeToSections(
border_spacing.block_size, distributable_block_size, sections, rows);
}
}
// Collapsed rows get 0 block-size, and shrink the minimum table size.
for (NGTableTypes::Row& row : *rows) {
if (row.is_collapsed) {
if (*minimal_table_grid_block_size != LayoutUnit()) {
*minimal_table_grid_block_size -= row.block_size;
if (rows->size() > 1)
*minimal_table_grid_block_size -= border_spacing.block_size;
}
row.block_size = LayoutUnit();
}
}
minimal_table_grid_block_size->ClampNegativeToZero();
}
// Method also sets LogicalWidth/Height on columns.
void NGTableLayoutAlgorithm::ComputeTableSpecificFragmentData(
const NGTableGroupedChildren& grouped_children,
const NGTableTypes::ColumnLocations& column_locations,
const NGTableTypes::Rows& rows,
const NGTableBorders& table_borders,
const PhysicalRect& table_grid_rect,
const LogicalSize& border_spacing,
const LayoutUnit table_grid_block_size) {
container_builder_.SetTableGridRect(table_grid_rect);
container_builder_.SetTableColumnCount(column_locations.size());
container_builder_.SetHasCollapsedBorders(table_borders.IsCollapsed());
// Column geometries.
if (grouped_children.columns.size() > 0) {
ColumnGeometriesBuilder geometry_builder(
column_locations, table_grid_block_size, border_spacing);
VisitLayoutNGTableColumn(grouped_children.columns, column_locations.size(),
&geometry_builder);
geometry_builder.Sort();
container_builder_.SetTableColumnGeometries(
geometry_builder.column_geometries);
}
// Collapsed borders.
if (!table_borders.IsEmpty()) {
LayoutUnit grid_inline_start = table_borders.TableBorder().inline_start;
std::unique_ptr<NGTableFragmentData::CollapsedBordersGeometry>
fragment_borders_geometry =
std::make_unique<NGTableFragmentData::CollapsedBordersGeometry>();
for (const auto& column : column_locations) {
fragment_borders_geometry->columns.push_back(column.offset +
grid_inline_start);
}
DCHECK_NE(column_locations.size(), 0u);
fragment_borders_geometry->columns.push_back(
column_locations.back().offset + column_locations.back().size +
grid_inline_start);
LayoutUnit row_offset = table_borders.TableBorder().block_start;
for (const auto& row : rows) {
fragment_borders_geometry->rows.push_back(row_offset);
row_offset += row.block_size;
}
fragment_borders_geometry->rows.push_back(row_offset);
container_builder_.SetTableCollapsedBorders(table_borders);
container_builder_.SetTableCollapsedBordersGeometry(
std::move(fragment_borders_geometry));
}
}
void NGTableLayoutAlgorithm::ComputeCaptionFragments(
const NGTableGroupedChildren& grouped_children,
const LayoutUnit table_inline_size,
Vector<CaptionResult>& captions,
LayoutUnit& captions_block_size) {
const LogicalSize available_size = {table_inline_size, kIndefiniteSize};
for (NGBlockNode caption : grouped_children.captions) {
const auto& caption_style = caption.Style();
NGConstraintSpaceBuilder builder(ConstraintSpace(),
caption_style.GetWritingDirection(),
/* is_new_fc */ true);
SetOrthogonalFallbackInlineSizeIfNeeded(Style(), caption, &builder);
builder.SetAvailableSize(available_size);
builder.SetPercentageResolutionSize(available_size);
builder.SetStretchInlineSizeIfAuto(true);
NGConstraintSpace caption_constraint_space = builder.ToConstraintSpace();
scoped_refptr<const NGLayoutResult> caption_result =
caption.Layout(caption_constraint_space);
NGFragment fragment(ConstraintSpace().GetWritingDirection(),
caption_result->PhysicalFragment());
NGBoxStrut margins = ComputeMarginsFor(caption_constraint_space,
caption_style, ConstraintSpace());
ResolveInlineMargins(caption_style, Style(), table_inline_size,
fragment.InlineSize(), &margins);
captions.push_back(
CaptionResult{caption, std::move(caption_result), margins});
captions_block_size += fragment.BlockSize() + margins.BlockSum();
}
}
// Generated fragment structure
// +---- table wrapper fragment ----+
// | top caption fragments |
// | table border/padding |
// | block_spacing |
// | section |
// | block_spacing |
// | section |
// | block_spacing |
// | table border/padding |
// | bottom caption fragments |
// +--------------------------------+
scoped_refptr<const NGLayoutResult> NGTableLayoutAlgorithm::GenerateFragment(
const LayoutUnit table_inline_size,
const LayoutUnit minimal_table_grid_block_size,
const NGTableGroupedChildren& grouped_children,
const NGTableTypes::ColumnLocations& column_locations,
const NGTableTypes::Rows& rows,
const NGTableTypes::CellBlockConstraints& cell_block_constraints,
const NGTableTypes::Sections& sections,
const Vector<CaptionResult>& captions,
const NGTableBorders& table_borders,
const LogicalSize& border_spacing) {
const auto table_writing_direction = Style().GetWritingDirection();
scoped_refptr<const NGTableConstraintSpaceData> constraint_space_data =
CreateConstraintSpaceData(Style(), column_locations, sections, rows,
cell_block_constraints, table_inline_size,
border_spacing);
const NGBoxStrut border_padding = container_builder_.BorderPadding();
LayoutUnit block_offset;
auto AddCaptionResult = [&](const auto& caption,
LayoutUnit* block_offset) -> void {
NGBlockNode node = caption.node;
node.StoreMargins(
caption.margins.ConvertToPhysical(table_writing_direction));
*block_offset += caption.margins.block_start;
container_builder_.AddResult(
*caption.layout_result,
LogicalOffset(caption.margins.inline_start, *block_offset));
*block_offset += NGFragment(table_writing_direction,
caption.layout_result->PhysicalFragment())
.BlockSize() +
caption.margins.block_end;
};
// Add all the top captions.
for (const auto& caption : captions) {
if (caption.node.Style().CaptionSide() == ECaptionSide::kTop)
AddCaptionResult(caption, &block_offset);
}
// Section setup.
const LayoutUnit section_available_inline_size =
(table_inline_size - border_padding.InlineSum() -
border_spacing.inline_size * 2)
.ClampNegativeToZero();
auto CreateSectionConstraintSpace = [&table_writing_direction,
&section_available_inline_size,
&constraint_space_data,
&sections](wtf_size_t section_index) {
NGConstraintSpaceBuilder section_space_builder(
table_writing_direction.GetWritingMode(), table_writing_direction,
/* is_new_fc */ true);
section_space_builder.SetAvailableSize(
{section_available_inline_size, sections[section_index].block_size});
section_space_builder.SetIsFixedInlineSize(true);
section_space_builder.SetIsFixedBlockSize(true);
section_space_builder.SetPercentageResolutionSize(
{section_available_inline_size, kIndefiniteSize});
section_space_builder.SetTableSectionData(constraint_space_data,
section_index);
return section_space_builder.ToConstraintSpace();
};
// Generate section fragments.
LogicalOffset section_offset;
section_offset.inline_offset =
border_padding.inline_start + border_spacing.inline_size;
section_offset.block_offset = block_offset + border_padding.block_start;
base::Optional<LayoutUnit> table_baseline;
wtf_size_t section_index = 0;
bool needs_end_border_spacing = false;
for (NGBlockNode section : grouped_children) {
scoped_refptr<const NGLayoutResult> section_result =
section.Layout(CreateSectionConstraintSpace(section_index++));
const NGPhysicalBoxFragment& physical_fragment =
To<NGPhysicalBoxFragment>(section_result->PhysicalFragment());
NGBoxFragment fragment(table_writing_direction, physical_fragment);
if (fragment.HasDescendantsForTablePart()) {
section_offset.block_offset += border_spacing.block_size;
needs_end_border_spacing = true;
}
container_builder_.AddResult(*section_result, section_offset);
if (!table_baseline) {
if (const auto& section_baseline = fragment.Baseline())
table_baseline = *section_baseline + section_offset.block_offset;
}
section_offset.block_offset += fragment.BlockSize();
}
if (needs_end_border_spacing)
section_offset.block_offset += border_spacing.block_size;
LayoutUnit column_block_size =
section_offset.block_offset - border_padding.block_start;
if (needs_end_border_spacing)
column_block_size -= border_spacing.block_size * 2;
const LayoutUnit grid_block_size = std::max(
section_offset.block_offset - block_offset + border_padding.block_end,
minimal_table_grid_block_size);
const LogicalRect table_grid_rect(LayoutUnit(), block_offset,
container_builder_.InlineSize(),
grid_block_size);
block_offset += grid_block_size;
// Add all the bottom captions.
for (const auto& caption : captions) {
if (caption.node.Style().CaptionSide() == ECaptionSide::kBottom)
AddCaptionResult(caption, &block_offset);
}
if (ConstraintSpace().IsFixedBlockSize()) {
container_builder_.SetFragmentBlockSize(
ConstraintSpace().AvailableSize().block_size);
} else {
container_builder_.SetFragmentBlockSize(block_offset);
}
container_builder_.SetIntrinsicBlockSize(block_offset);
const WritingModeConverter grid_converter(
Style().GetWritingDirection(),
ToPhysicalSize(container_builder_.Size(),
table_writing_direction.GetWritingMode()));
ComputeTableSpecificFragmentData(grouped_children, column_locations, rows,
table_borders,
grid_converter.ToPhysical(table_grid_rect),
border_spacing, column_block_size);
if (table_baseline)
container_builder_.SetBaseline(*table_baseline);
container_builder_.SetIsTableNGPart();
NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), &container_builder_).Run();
return container_builder_.ToBoxFragment();
}
} // namespace blink