Fix size of tables in flow layout (#31455)

* Fix size of tables in flow layout

The contents of a table can make it bigger than what we would expect
from its 'width', 'min-width', 'height' and ' min-height' properties.
Also, 'width: auto' doesn't stretch it to fill the containing block.

We had to refactor the resolution of margins to happen after layout,
otherwise 'auto' margins wouldn't align correctly.

Co-authored-by: Martin Robinson <mrobinson@igalia.com>

* Feedback

* Consistently use `containing_block_for_table` in table layout

* Update test result

---------

Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Oriol Brufau 2024-03-02 02:39:41 +01:00 committed by GitHub
parent c23999941a
commit 50fdb82246
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
54 changed files with 289 additions and 388 deletions

View file

@ -79,6 +79,7 @@ struct TableLayout<'a> {
columns: Vec<ColumnLayout>,
cell_measures: Vec<Vec<LogicalVec2<CellOrTrackMeasure>>>,
assignable_width: Au,
final_table_height: Au,
column_measures: Vec<CellOrTrackMeasure>,
distributed_column_widths: Vec<Au>,
row_sizes: Vec<Au>,
@ -115,6 +116,7 @@ impl<'a> TableLayout<'a> {
columns: Vec::new(),
cell_measures: Vec::new(),
assignable_width: Au::zero(),
final_table_height: Au::zero(),
column_measures: Vec::new(),
distributed_column_widths: Vec::new(),
row_sizes: Vec::new(),
@ -130,18 +132,27 @@ impl<'a> TableLayout<'a> {
&mut self,
layout_context: &LayoutContext,
positioning_context: &mut PositioningContext,
containing_block: &ContainingBlock,
containing_block_for_children: &ContainingBlock,
containing_block_for_table: &ContainingBlock,
) {
let writing_mode = containing_block.style.writing_mode;
let writing_mode = containing_block_for_children.style.writing_mode;
self.compute_track_constrainedness_and_has_originating_cells(writing_mode);
self.compute_cell_measures(layout_context, containing_block);
self.compute_column_measures(containing_block.style.writing_mode);
self.compute_table_width(containing_block);
self.compute_cell_measures(layout_context, containing_block_for_children);
self.compute_column_measures(writing_mode);
self.compute_table_width(containing_block_for_children, containing_block_for_table);
self.distributed_column_widths = self.distribute_width_to_columns();
self.layout_cells_in_row(layout_context, containing_block, positioning_context);
self.layout_cells_in_row(
layout_context,
containing_block_for_children,
positioning_context,
);
let first_layout_row_heights = self.do_first_row_layout(writing_mode);
self.compute_table_height_and_final_row_heights(first_layout_row_heights, containing_block);
self.compute_table_height_and_final_row_heights(
first_layout_row_heights,
containing_block_for_children,
containing_block_for_table,
);
}
/// This is an implementation of *Computing Cell Measures* from
@ -149,12 +160,12 @@ impl<'a> TableLayout<'a> {
pub(crate) fn compute_cell_measures(
&mut self,
layout_context: &LayoutContext,
containing_block: &ContainingBlock,
containing_block_for_table: &ContainingBlock,
) {
let row_measures = vec![LogicalVec2::zero(); self.table.size.width];
self.cell_measures = vec![row_measures; self.table.size.height];
let writing_mode = containing_block.style.writing_mode;
let writing_mode = containing_block_for_table.style.writing_mode;
for row_index in 0..self.table.size.height {
for column_index in 0..self.table.size.width {
let cell = match self.table.slots[row_index][column_index] {
@ -623,57 +634,70 @@ impl<'a> TableLayout<'a> {
(next_span_n, new_content_sizes_for_columns)
}
fn compute_table_width(&mut self, containing_block: &ContainingBlock) {
fn compute_table_width(
&mut self,
containing_block_for_children: &ContainingBlock,
containing_block_for_table: &ContainingBlock,
) {
// https://drafts.csswg.org/css-tables/#gridmin:
// > The row/column-grid width minimum (GRIDMIN) width is the sum of the min-content width of
// > all the columns plus cell spacing or borders.
// https://drafts.csswg.org/css-tables/#gridmax:
// > The row/column-grid width maximum (GRIDMAX) width is the sum of the max-content width of
// > all the columns plus cell spacing or borders.
let mut grid_min_and_max = self
let ContentSizes {
min_content: mut gridmin,
max_content: mut gridmax,
} = self
.column_measures
.iter()
.fold(ContentSizes::zero(), |result, measure| {
result + measure.content_sizes
});
// TODO: GRIDMAX should never be smaller than GRIDMIN!
gridmax = gridmax.max(gridmin);
let border_spacing = self.table.border_spacing();
let inline_border_spacing = border_spacing.inline * (self.table.size.width as i32 + 1);
grid_min_and_max.min_content += inline_border_spacing;
grid_min_and_max.max_content += inline_border_spacing;
let inline_border_spacing = if self.table.size.width > 0 {
border_spacing.inline * (self.table.size.width as i32 + 1)
} else {
Au::zero()
};
gridmin += inline_border_spacing;
gridmax += inline_border_spacing;
self.pbm = self.table.style.padding_border_margin(containing_block);
let content_box_size = self
.table
.style
.content_box_size(containing_block, &self.pbm);
let min_content_sizes = self
.table
.style
.content_min_box_size(containing_block, &self.pbm)
.auto_is(Length::zero);
let style = &self.table.style;
self.pbm = style.padding_border_margin(containing_block_for_table);
// https://drafts.csswg.org/css-tables/#used-min-width-of-table
// > The used min-width of a table is the greater of the resolved min-width, CAPMIN, and GRIDMIN.
let used_min_width_of_table = grid_min_and_max
.min_content
.max(min_content_sizes.inline.into());
// https://drafts.csswg.org/css-tables/#resolved-table-width
// * If inline-size computes to 'auto', this is the stretch-fit size
// (https://drafts.csswg.org/css-sizing-3/#stretch-fit-size).
// * Otherwise, it's the resulting length (with percentages resolved).
// In both cases, it's clamped between min-inline-size and max-inline-size.
// This diverges a little from the specification.
let resolved_table_width = containing_block_for_children.inline_size;
// https://drafts.csswg.org/css-tables/#used-width-of-table
// > The used width of a table depends on the columns and captions widths as follows:
// > * If the table-roots width property has a computed value (resolving to
// > resolved-table-width) other than auto, the used width is the greater of
// > resolved-table-width, and the used min-width of the table.
// > * If the table-root has 'width: auto', the used width is the greater of min(GRIDMAX,
// > the tables containing block width), the used min-width of the table.
let used_width_of_table = match content_box_size.inline {
LengthPercentage(length_percentage) => {
Au::from(length_percentage).max(used_min_width_of_table)
// * If table-root has a computed value for inline-size different than auto:
// use the maximum of the resolved table width and GRIDMIN.
// * If auto: use the resolved_table_width, clamped between GRIDMIN and GRIDMAX,
// but at least as big as min-inline-size.
// This diverges a little from the specification, but should be equivalent
// (other than using the stretch-fit size instead of the containing block width).
let used_width_of_table = match style
.content_box_size(containing_block_for_table, &self.pbm)
.inline
{
LengthPercentage(_) => resolved_table_width.max(gridmin),
Auto => {
let min_width: Au = style
.content_min_box_size(containing_block_for_table, &self.pbm)
.inline
.auto_is(Length::zero)
.into();
resolved_table_width.clamp(gridmin, gridmax).max(min_width)
},
Auto => grid_min_and_max
.max_content
.min(containing_block.inline_size)
.max(used_min_width_of_table),
};
// > The assignable table width is the used width of the table minus the total horizontal
@ -976,7 +1000,7 @@ impl<'a> TableLayout<'a> {
fn layout_cells_in_row(
&mut self,
layout_context: &LayoutContext,
containing_block: &ContainingBlock,
containing_block_for_table: &ContainingBlock,
parent_positioning_context: &mut PositioningContext,
) {
for row_index in 0..self.table.slots.len() {
@ -996,10 +1020,12 @@ impl<'a> TableLayout<'a> {
total_width += self.distributed_column_widths[width_index];
}
let border = cell.style.border_width(containing_block.style.writing_mode);
let border = cell
.style
.border_width(containing_block_for_table.style.writing_mode);
let padding = cell
.style
.padding(containing_block.style.writing_mode)
.padding(containing_block_for_table.style.writing_mode)
.percentages_relative_to(self.basis_for_cell_padding_percentage.into());
let inline_border_padding_sum = border.inline_sum() + padding.inline_sum();
let mut total_width: CSSPixelLength =
@ -1327,27 +1353,37 @@ impl<'a> TableLayout<'a> {
fn compute_table_height_and_final_row_heights(
&mut self,
mut row_sizes: Vec<Au>,
containing_block: &ContainingBlock,
containing_block_for_children: &ContainingBlock,
containing_block_for_table: &ContainingBlock,
) {
// The table content height is the maximum of the computed table height from style and the
// sum of computed row heights from row layout plus size from borders and spacing.
let table_height_from_style = self
.table
.style
.content_box_size(containing_block, &self.pbm)
// When block-size doesn't compute to auto, `containing_block_for children` will have
// the resulting length, properly clamped between min-block-size and max-block-size.
let style = &self.table.style;
let table_height_from_style = match style
.content_box_size(containing_block_for_table, &self.pbm)
.block
.auto_is(Length::zero)
.into();
{
LengthPercentage(_) => containing_block_for_children.block_size,
Auto => style
.content_min_box_size(containing_block_for_table, &self.pbm)
.block
.map(Au::from),
}
.auto_is(Au::zero);
let border_spacing = self.table.border_spacing();
let border_and_spacing = self.pbm.border.block_sum() +
border_spacing.block * (self.table.size.height as i32 + 1);
let table_height_from_rows = row_sizes.iter().sum::<Au>() + border_and_spacing;
let final_table_height = table_height_from_rows.max(table_height_from_style);
let block_border_spacing = if self.table.size.height > 0 {
self.table.border_spacing().block * (self.table.size.height as i32 + 1)
} else {
Au::zero()
};
let table_height_from_rows = row_sizes.iter().sum::<Au>() + block_border_spacing;
self.final_table_height = table_height_from_rows.max(table_height_from_style);
// If the table height is defined by the rows sizes, there is no extra space to distribute
// to rows.
if final_table_height == table_height_from_rows {
if self.final_table_height == table_height_from_rows {
self.row_sizes = row_sizes;
return;
}
@ -1357,10 +1393,10 @@ impl<'a> TableLayout<'a> {
// space.
// TODO: This should first distribute space to row groups and then to rows.
self.distribute_extra_size_to_rows(
final_table_height - table_height_from_rows,
self.final_table_height - table_height_from_rows,
0..self.table.size.height,
&mut row_sizes,
Some(final_table_height),
Some(self.final_table_height),
false, /* rowspan_distribution */
);
self.row_sizes = row_sizes;
@ -1378,7 +1414,8 @@ impl<'a> TableLayout<'a> {
if self.table.size.width == 0 || self.table.size.height == 0 {
return IndependentLayout {
fragments,
content_block_size: Au::zero(),
content_block_size: self.final_table_height,
content_inline_size_for_table: Some(self.assignable_width),
baselines,
};
}
@ -1487,6 +1524,7 @@ impl<'a> TableLayout<'a> {
IndependentLayout {
fragments,
content_block_size: dimensions.table_rect.max_block_position(),
content_inline_size_for_table: Some(dimensions.table_rect.max_inline_position()),
baselines,
}
}
@ -1780,10 +1818,16 @@ impl Table {
&self,
layout_context: &LayoutContext,
positioning_context: &mut PositioningContext,
containing_block: &ContainingBlock,
containing_block_for_children: &ContainingBlock,
containing_block_for_table: &ContainingBlock,
) -> IndependentLayout {
let mut table_layout = TableLayout::new(self);
table_layout.compute_measures(layout_context, positioning_context, containing_block);
table_layout.compute_measures(
layout_context,
positioning_context,
containing_block_for_children,
containing_block_for_table,
);
table_layout.layout(positioning_context)
}
}