diff --git a/components/layout_2020/table/layout.rs b/components/layout_2020/table/layout.rs index 49521baaa05..b0b0036922d 100644 --- a/components/layout_2020/table/layout.rs +++ b/components/layout_2020/table/layout.rs @@ -6,7 +6,7 @@ use core::cmp::Ordering; use std::mem; use std::ops::Range; -use app_units::{Au, MAX_AU}; +use app_units::Au; use log::warn; use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; use servo_arc::Arc; @@ -99,7 +99,24 @@ struct ColumnLayout { constrained: bool, has_originating_cells: bool, content_sizes: ContentSizes, - percentage: Percentage, + percentage: Option, +} + +fn max_two_optional_percentages( + a: Option, + b: Option, +) -> Option { + match (a, b) { + (Some(a), Some(b)) => Some(Percentage(a.0.max(b.0))), + _ => a.or(b), + } +} + +impl ColumnLayout { + fn incorporate_cell_measure(&mut self, cell_measure: &CellOrTrackMeasure) { + self.content_sizes.max_assign(cell_measure.content_sizes); + self.percentage = max_two_optional_percentages(self.percentage, cell_measure.percentage); + } } impl CollapsedBorder { @@ -200,19 +217,19 @@ pub(crate) struct TableLayout<'a> { #[derive(Clone, Debug)] struct CellOrTrackMeasure { content_sizes: ContentSizes, - percentage: Percentage, + percentage: Option, } impl Zero for CellOrTrackMeasure { fn zero() -> Self { Self { content_sizes: ContentSizes::zero(), - percentage: Percentage(0.), + percentage: None, } } fn is_zero(&self) -> bool { - self.content_sizes.is_zero() && self.percentage.is_zero() + self.content_sizes.is_zero() && self.percentage.is_none() } } @@ -280,12 +297,17 @@ impl<'a> TableLayout<'a> { block: padding.block_sum() + border.block_sum(), }; - let (size, min_size, max_size, inline_size_is_auto, percentage_contribution) = - get_outer_sizes_for_measurement( - &cell.base.style, - writing_mode, - &padding_border_sums, - ); + let CellOrColumnOuterSizes { + preferred: preferred_size, + min: min_size, + max: max_size, + inline_preferred_size_is_auto, + percentage: percentage_size, + } = CellOrColumnOuterSizes::new( + &cell.base.style, + writing_mode, + &padding_border_sums, + ); // // > When a table-root is laid out in fixed mode, the content of its table-cells is ignored @@ -305,31 +327,30 @@ impl<'a> TableLayout<'a> { // These formulas differ from the spec, but seem to match Gecko and Blink. let outer_min_content_width = if is_in_fixed_mode { - if inline_size_is_auto { + if inline_preferred_size_is_auto { // This is an outer size, but we deliberately ignore borders and padding. // This is like allowing the content-box width to be negative. Au::zero() } else { - size.inline.min(max_size.inline).max(min_size.inline) + preferred_size + .inline + .clamp_between_extremums(min_size.inline, max_size.inline) } } else { inline_content_sizes .min_content - .min(max_size.inline) - .max(min_size.inline) + .clamp_between_extremums(min_size.inline, max_size.inline) }; let outer_max_content_width = if self.columns[column_index].constrained { inline_content_sizes .min_content - .max(size.inline) - .min(max_size.inline) - .max(min_size.inline) + .max(preferred_size.inline) + .clamp_between_extremums(min_size.inline, max_size.inline) } else { inline_content_sizes .max_content - .max(size.inline) - .min(max_size.inline) - .max(min_size.inline) + .max(preferred_size.inline) + .clamp_between_extremums(min_size.inline, max_size.inline) }; assert!(outer_min_content_width <= outer_max_content_width); @@ -338,7 +359,7 @@ impl<'a> TableLayout<'a> { min_content: outer_min_content_width, max_content: outer_max_content_width, }, - percentage: percentage_contribution.inline, + percentage: percentage_size.inline, } }; @@ -346,8 +367,8 @@ impl<'a> TableLayout<'a> { // These sizes are incorporated after the first row layout pass, when the block size // of the layout is known. let block_measure = CellOrTrackMeasure { - content_sizes: size.block.into(), - percentage: percentage_contribution.block, + content_sizes: preferred_size.block.into(), + percentage: percentage_size.block, }; self.cell_measures[row_index][column_index] = LogicalVec2 { @@ -465,7 +486,7 @@ impl<'a> TableLayout<'a> { // // TODO: Take into account `table-column` and `table-column-group` lengths. // TODO: Take into account changes to this computation for fixed table layout. - let mut next_span_n = usize::MAX; + let mut colspan_cell_constraints = Vec::new(); for column_index in 0..self.table.size.width { let column = &mut self.columns[column_index]; @@ -477,31 +498,34 @@ impl<'a> TableLayout<'a> { for row_index in 0..self.table.size.height { let coords = TableSlotCoordinates::new(column_index, row_index); - match self.table.resolve_first_cell(coords) { - Some(cell) if cell.colspan == 1 => cell, - Some(cell) => { - next_span_n = next_span_n.min(cell.colspan); - continue; - }, + let cell_measure = &self.cell_measures[row_index][column_index].inline; + + let cell = match self.table.get_slot(coords) { + Some(TableSlot::Cell(cell)) => cell, _ => continue, }; + if cell.colspan != 1 { + colspan_cell_constraints.push(ColspanToDistribute { + starting_column: column_index, + span: cell.colspan, + content_sizes: cell_measure.content_sizes, + percentage: cell_measure.percentage, + }); + continue; + } + // This takes the max of `min_content`, `max_content`, and // intrinsic percentage width as described above. - let cell_measure = &self.cell_measures[row_index][column_index].inline; - column.content_sizes.max_assign(cell_measure.content_sizes); - column.percentage = - Percentage(column_measure.percentage.0.max(cell_measure.percentage.0)); + column.incorporate_cell_measure(cell_measure); } } - // Now we have the base computation complete, so iteratively take into account cells - // with higher colspan. Using `next_span_n` we can skip over span counts that don't - // correspond to any cells. - while next_span_n < usize::MAX { - (next_span_n, self.columns) = - self.compute_content_sizes_for_columns_with_span_up_to_n(next_span_n); - } + // Sort the colspanned cell constraints by their span and starting column. + colspan_cell_constraints.sort_by(ColspanToDistribute::comparison_for_sort); + + // Distribute constraints from cells with colspan != 1 to their component columns. + self.distribute_colspanned_cells_to_columns(colspan_cell_constraints); // > intrinsic percentage width of a column: // > the smaller of: @@ -511,216 +535,98 @@ impl<'a> TableLayout<'a> { // > the table (further left when direction is "ltr" (right for "rtl")) let mut total_intrinsic_percentage_width = 0.; for column in self.columns.iter_mut() { - let final_intrinsic_percentage_width = column - .percentage - .0 - .min(1. - total_intrinsic_percentage_width); - total_intrinsic_percentage_width += final_intrinsic_percentage_width; - column.percentage = Percentage(final_intrinsic_percentage_width); + if let Some(ref mut percentage) = column.percentage { + let final_intrinsic_percentage_width = + percentage.0.min(1. - total_intrinsic_percentage_width); + total_intrinsic_percentage_width += final_intrinsic_percentage_width; + *percentage = Percentage(final_intrinsic_percentage_width); + } } } - fn compute_content_sizes_for_columns_with_span_up_to_n( - &self, - n: usize, - ) -> (usize, Vec) { - let mut next_span_n = usize::MAX; - let mut new_columns = Vec::new(); - let border_spacing = self.table.border_spacing(); - - for column_index in 0..self.table.size.width { - let old_column = &self.columns[column_index]; - let mut new_column_content_sizes = old_column.content_sizes; - let mut new_column_intrinsic_percentage_width = old_column.percentage; - - for row_index in 0..self.table.size.height { - let coords = TableSlotCoordinates::new(column_index, row_index); - let resolved_coords = match self.table.resolve_first_cell_coords(coords) { - Some(resolved_coords) => resolved_coords, - None => continue, - }; - - let cell = match self.table.resolve_first_cell(resolved_coords) { - Some(cell) if cell.colspan <= n => cell, - Some(cell) => { - next_span_n = next_span_n.min(cell.colspan); - continue; - }, - _ => continue, - }; - - let cell_measures = - &self.cell_measures[resolved_coords.y][resolved_coords.x].inline; - let cell_inline_content_sizes = cell_measures.content_sizes; - - let columns_spanned = resolved_coords.x..resolved_coords.x + cell.colspan; - let baseline_content_sizes: ContentSizes = columns_spanned.clone().fold( - ContentSizes::zero(), - |total: ContentSizes, spanned_column_index| { - total + self.columns[spanned_column_index].content_sizes - }, - ); - - let old_column_content_size = old_column.content_sizes; - - // > **min-content width of a column based on cells of span up to N (N > 1)** - // > - // > 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, where - // > the contribution of a cell is the result of taking the following steps: - // > - // > 1. Define the baseline min-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. - // - // Note: This definition is likely a typo, so we use the sum of the min-content - // widths here instead. - let baseline_min_content_width = baseline_content_sizes.min_content; - let baseline_max_content_width = baseline_content_sizes.max_content; - - // > 2. Define the baseline border spacing as the sum of the horizontal - // > border-spacing for any columns spanned by the cell, other than the one in - // > which the cell originates. - let baseline_border_spacing = border_spacing.inline * (n as i32 - 1); - - // > 3. The contribution of the cell is the sum of: - // > a. the min-content width of the column based on cells of span up to N-1 - let a = old_column_content_size.min_content; - - // > b. the product of: - // > - the ratio of: - // > - the max-content width of the column based on cells of span up - // > to N-1 of the column minus the min-content width of the - // > column based on cells of span up to N-1 of the column, to - // > - the baseline max-content width minus the baseline min-content - // > width - // > or zero if this ratio is undefined, and - // > - the outer min-content width of the cell minus the baseline - // > min-content width and the baseline border spacing, clamped to be - // > at least 0 and at most the difference between the baseline - // > max-content width and the baseline min-content width - let old_content_size_difference = - old_column_content_size.max_content - old_column_content_size.min_content; - let baseline_difference = baseline_min_content_width - baseline_max_content_width; - - let mut b = - old_content_size_difference.to_f32_px() / baseline_difference.to_f32_px(); - if !b.is_finite() { - b = 0.0; - } - let b = (cell_inline_content_sizes.min_content - - baseline_content_sizes.min_content - - baseline_border_spacing) - .clamp_between_extremums(Au::zero(), Some(baseline_difference)) - .scale_by(b); - - // > c. the product of: - // > - 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 - // > - the outer min-content width of the cell minus the baseline - // > max-content width and baseline border spacing, or 0 if this is - // > negative - let c = (cell_inline_content_sizes.min_content - - baseline_content_sizes.max_content - - baseline_border_spacing) - .min(Au::zero()) - .scale_by( - old_column_content_size.max_content.to_f32_px() / - baseline_content_sizes.max_content.to_f32_px(), - ); - - let new_column_min_content_width = a + b + c; - - // > **max-content width of a column based on cells of span up to N (N > 1)** - // > - // > 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, where the - // > contribution of a cell is the result of taking the following steps: - - // > 1. 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. - // - // This is calculated above for the min-content width. - - // > 2. Define the baseline border spacing as the sum of the horizontal - // > border-spacing for any columns spanned by the cell, other than the one in - // > which the cell originates. - // - // This is calculated above for min-content width. - - // > 3. The contribution of the cell is the sum of: - // > a. the max-content width of the column based on cells of span up to N-1 - let a = old_column_content_size.max_content; - - // > b. the product of: - // > 1. 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 - let b_1 = old_column_content_size.max_content.to_f32_px() / - baseline_content_sizes.max_content.to_f32_px(); - - // > 2. 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 - let b_2 = (cell_inline_content_sizes.max_content - - baseline_content_sizes.max_content - - baseline_border_spacing) - .min(Au::zero()); - let b = b_2.scale_by(b_1); - let new_column_max_content_width = a + b + c; - - // The computed values for the column are always the largest of any processed cell - // in that column. - new_column_content_sizes.max_assign(ContentSizes { - min_content: new_column_min_content_width, - max_content: new_column_max_content_width, - }); - - // > If the intrinsic percentage width of a column based on cells of span up to N-1 is - // > greater than 0%, then the intrinsic percentage width of the column based on cells - // > of span up to N is the same as the intrinsic percentage width of the column based - // > on cells of span up to N-1. - // > Otherwise, it is the largest of the contributions of the cells in the column - // > whose colSpan is N, where the contribution of a cell is the result of taking - // > the following steps: - if old_column.percentage.0 <= 0. && cell_measures.percentage.0 != 0. { - // > 1. Start with the percentage contribution of the cell. - // > 2. Subtract the intrinsic percentage width of the column based on cells - // > of span up to N-1 of all columns that the cell spans. If this gives a - // > negative result, change it to 0%. - let mut spanned_columns_with_zero = 0; - let other_column_percentages_sum = - (columns_spanned).fold(0., |sum, spanned_column_index| { - let spanned_column_percentage = - self.columns[spanned_column_index].percentage; - if spanned_column_percentage.0 == 0. { - spanned_columns_with_zero += 1; - } - sum + spanned_column_percentage.0 - }); - let step_2 = (cell_measures.percentage - - Percentage(other_column_percentages_sum)) - .clamp_to_non_negative(); - - // > Multiply by the ratio of: - // > 1. the column’s non-spanning max-content width to - // > 2. the sum of the non-spanning max-content widths of all columns - // > spanned by the cell that have an intrinsic percentage width of the column - // > based on cells of span up to N-1 equal to 0%. - // > However, if this ratio is undefined because the denominator is zero, - // > instead use the 1 divided by the number of columns spanned by the cell - // > that have an intrinsic percentage width of the column based on cells of - // > span up to N-1 equal to zero. - let step_3 = step_2.0 * (1.0 / spanned_columns_with_zero as f32); - - new_column_intrinsic_percentage_width = - Percentage(new_column_intrinsic_percentage_width.0.max(step_3)); - } - } - let mut new_column = old_column.clone(); - new_column.content_sizes = new_column_content_sizes; - new_column.percentage = new_column_intrinsic_percentage_width; - new_columns.push(new_column); + fn distribute_colspanned_cells_to_columns( + &mut self, + colspan_cell_constraints: Vec, + ) { + for colspan_cell_constraints in colspan_cell_constraints { + self.distribute_colspanned_cell_to_columns(colspan_cell_constraints); + } + } + + /// Distribute the inline size from a cell with colspan != 1 to the columns that it spans. + /// This is heavily inspired by the approach that Chromium takes in redistributing colspan + /// cells' inline size to columns (`DistributeColspanCellToColumnsAuto` in + /// `blink/renderer/core/layout/table/table_layout_utils.cc`). + fn distribute_colspanned_cell_to_columns( + &mut self, + colspan_cell_constraints: ColspanToDistribute, + ) { + let border_spacing = self.table.border_spacing().inline; + let column_range = colspan_cell_constraints.range(); + let column_count = column_range.len(); + let total_border_spacing = + border_spacing.scale_by((colspan_cell_constraints.span - 1) as f32); + + let mut percent_columns_count = 0; + let mut columns_percent_sum = 0.; + let mut columns_non_percent_max_inline_size_sum = Au::zero(); + for column in self.columns[column_range.clone()].iter() { + if let Some(percentage) = column.percentage { + percent_columns_count += 1; + columns_percent_sum += percentage.0; + } else { + columns_non_percent_max_inline_size_sum += column.content_sizes.max_content; + } + } + + let colspan_percentage = colspan_cell_constraints.percentage.unwrap_or_default(); + let surplus_percent = colspan_percentage.0 - columns_percent_sum; + if surplus_percent > 0. && column_count > percent_columns_count { + for column in self.columns[column_range.clone()].iter_mut() { + if column.percentage.is_some() { + continue; + } + + let ratio = if columns_non_percent_max_inline_size_sum.is_zero() { + 1. / ((column_count - percent_columns_count) as f32) + } else { + column.content_sizes.max_content.to_f32_px() / + columns_non_percent_max_inline_size_sum.to_f32_px() + }; + column.percentage = Some(Percentage(surplus_percent * ratio)); + } + } + + let colspan_cell_min_size = (colspan_cell_constraints.content_sizes.min_content - + total_border_spacing) + .max(Au::zero()); + let distributed_minimum = Self::distribute_width_to_columns( + colspan_cell_min_size, + &self.columns[column_range.clone()], + ); + { + let column_span = &mut self.columns[colspan_cell_constraints.range()]; + for (column, minimum_size) in column_span.iter_mut().zip(distributed_minimum) { + column.content_sizes.min_content.max_assign(minimum_size); + } + } + + let colspan_cell_max_size = (colspan_cell_constraints.content_sizes.max_content - + total_border_spacing) + .max(Au::zero()); + let distributed_maximum = Self::distribute_width_to_columns( + colspan_cell_max_size, + &self.columns[colspan_cell_constraints.range()], + ); + { + let column_span = &mut self.columns[colspan_cell_constraints.range()]; + for (column, maximum_size) in column_span.iter_mut().zip(distributed_maximum) { + column + .content_sizes + .max_content + .max_assign(maximum_size.max(column.content_sizes.min_content)); + } } - (next_span_n, new_columns) } /// Compute the GRIDMIN and GRIDMAX. @@ -819,14 +725,10 @@ impl<'a> TableLayout<'a> { /// Distribute width to columns, performing step 2.4 of table layout from /// . - fn distribute_width_to_columns( - &self, - target_inline_size: Au, - columns: &[ColumnLayout], - ) -> Vec { + fn distribute_width_to_columns(target_inline_size: Au, columns: &[ColumnLayout]) -> Vec { // No need to do anything if there is no column. // Note that tables without rows may still have columns. - if self.table.size.width.is_zero() { + if columns.is_empty() { return Vec::new(); } @@ -872,8 +774,8 @@ impl<'a> TableLayout<'a> { min_content_percentage_sizing_guess, min_content_specified_sizing_guess, max_content_sizing_guess, - ) = if !column.percentage.is_zero() { - let resolved = target_inline_size.scale_by(column.percentage.0); + ) = if let Some(percentage) = column.percentage { + let resolved = target_inline_size.scale_by(percentage.0); let percent_guess = min_content_width.max(resolved); (percent_guess, percent_guess, percent_guess) } else if constrained { @@ -901,9 +803,11 @@ impl<'a> TableLayout<'a> { let max_content_sizing_sum = sum(&max_content_sizing_guesses); if target_inline_size >= max_content_sizing_sum { - self.distribute_extra_width_to_columns( + Self::distribute_extra_width_to_columns( + columns, &mut max_content_sizing_guesses, max_content_sizing_sum, + target_inline_size, ); return max_content_sizing_guesses; } @@ -999,26 +903,29 @@ impl<'a> TableLayout<'a> { /// This is an implementation of *Distributing excess width to columns* from /// . - fn distribute_extra_width_to_columns(&self, column_sizes: &mut [Au], column_sizes_sum: Au) { - let all_columns = 0..self.table.size.width; - let extra_inline_size = self.assignable_width - column_sizes_sum; + fn distribute_extra_width_to_columns( + columns: &[ColumnLayout], + column_sizes: &mut [Au], + column_sizes_sum: Au, + assignable_width: Au, + ) { + let all_columns = 0..columns.len(); + let extra_inline_size = assignable_width - column_sizes_sum; let has_originating_cells = - |column_index: &usize| self.columns[*column_index].has_originating_cells; - let is_constrained = |column_index: &usize| self.columns[*column_index].constrained; + |column_index: &usize| columns[*column_index].has_originating_cells; + let is_constrained = |column_index: &usize| columns[*column_index].constrained; let is_unconstrained = |column_index: &usize| !is_constrained(column_index); - let has_percent_greater_than_zero = - |column_index: &usize| self.columns[*column_index].percentage.0 > 0.; - let has_percent_zero = |column_index: &usize| !has_percent_greater_than_zero(column_index); - let has_max_content = |column_index: &usize| { - !self.columns[*column_index] - .content_sizes - .max_content - .is_zero() + let has_percent_greater_than_zero = |column_index: &usize| { + columns[*column_index] + .percentage + .is_some_and(|percentage| percentage.0 > 0.) }; + let has_percent_zero = |column_index: &usize| !has_percent_greater_than_zero(column_index); + let has_max_content = + |column_index: &usize| !columns[*column_index].content_sizes.max_content.is_zero(); - let max_content_sum = - |column_index: usize| self.columns[column_index].content_sizes.max_content; + let max_content_sum = |column_index: usize| columns[column_index].content_sizes.max_content; // > If there are non-constrained columns that have originating cells with intrinsic // > percentage width of 0% and with nonzero max-content width (aka the columns allowed to @@ -1038,10 +945,7 @@ impl<'a> TableLayout<'a> { if total_max_content_width != Au::zero() { for column_index in unconstrained_max_content_columns { column_sizes[column_index] += extra_inline_size.scale_by( - self.columns[column_index] - .content_sizes - .max_content - .to_f32_px() / + columns[column_index].content_sizes.max_content.to_f32_px() / total_max_content_width.to_f32_px(), ); } @@ -1086,10 +990,7 @@ impl<'a> TableLayout<'a> { if total_max_content_width != Au::zero() { for column_index in constrained_max_content_columns { column_sizes[column_index] += extra_inline_size.scale_by( - self.columns[column_index] - .content_sizes - .max_content - .to_f32_px() / + columns[column_index].content_sizes.max_content.to_f32_px() / total_max_content_width.to_f32_px(), ); } @@ -1104,12 +1005,13 @@ impl<'a> TableLayout<'a> { let columns_with_percentage = all_columns.clone().filter(has_percent_greater_than_zero); let total_percent = columns_with_percentage .clone() - .map(|column_index| self.columns[column_index].percentage.0) + .map(|column_index| columns[column_index].percentage.unwrap_or_default().0) .sum::(); if total_percent > 0. { for column_index in columns_with_percentage { - column_sizes[column_index] += extra_inline_size - .scale_by(self.columns[column_index].percentage.0 / total_percent); + let column_percentage = columns[column_index].percentage.unwrap_or_default(); + column_sizes[column_index] += + extra_inline_size.scale_by(column_percentage.0 / total_percent); } return; } @@ -1130,8 +1032,7 @@ impl<'a> TableLayout<'a> { // > Otherwise, the distributed widths of all columns are increased by equal amounts so the // total increase adds to the excess width. - let extra_space_for_all_columns = - extra_inline_size.scale_by(1.0 / self.table.size.width as f32); + let extra_space_for_all_columns = extra_inline_size.scale_by(1.0 / columns.len() as f32); for guess in column_sizes.iter_mut() { *guess += extra_space_for_all_columns; } @@ -1320,11 +1221,12 @@ impl<'a> TableLayout<'a> { .get_row_measure_for_row_at_index(writing_mode, row_index); row_sizes[row_index].max_assign(row_measure.content_sizes.min_content); - let mut percentage = row_measure.percentage.0; + let mut percentage = row_measure.percentage.unwrap_or_default().0; for column_index in 0..self.table.size.width { let cell_percentage = self.cell_measures[row_index][column_index] .block .percentage + .unwrap_or_default() .0; percentage = percentage.max(cell_percentage); @@ -1821,7 +1723,7 @@ impl<'a> TableLayout<'a> { containing_block_for_children: &ContainingBlock, ) -> BoxFragment { self.distributed_column_widths = - self.distribute_width_to_columns(self.assignable_width, &self.columns); + Self::distribute_width_to_columns(self.assignable_width, &self.columns); self.layout_cells_in_row( layout_context, containing_block_for_children, @@ -2650,8 +2552,13 @@ impl Table { None => return CellOrTrackMeasure::zero(), }; - let (size, min_size, max_size, _, percentage_contribution) = - get_outer_sizes_for_measurement(&column.style, writing_mode, &LogicalVec2::zero()); + let CellOrColumnOuterSizes { + preferred: preferred_size, + min: min_size, + max: max_size, + percentage: percentage_size, + .. + } = CellOrColumnOuterSizes::new(&column.style, writing_mode, &Default::default()); CellOrTrackMeasure { content_sizes: ContentSizes { @@ -2663,9 +2570,11 @@ impl Table { // > The outer max-content width of a table-column or table-column-group is // > max(min-width, min(max-width, width)). // This matches Gecko, but Blink and WebKit ignore max_size. - max_content: min_size.inline.max(max_size.inline.min(size.inline)), + max_content: preferred_size + .inline + .clamp_between_extremums(min_size.inline, max_size.inline), }, - percentage: percentage_contribution.inline, + percentage: percentage_size.inline, } } @@ -2910,7 +2819,7 @@ impl TableSlotCell { fn get_size_percentage_contribution( size: &LogicalVec2>, max_size: &LogicalVec2>, -) -> LogicalVec2 { +) -> LogicalVec2> { // From // > The percentage contribution of a table cell, column, or column group is defined // > in terms of the computed values of width and max-width that have computed values @@ -2922,13 +2831,11 @@ fn get_size_percentage_contribution( |size: &Size, max_size: &Size| { let size_percentage = size .to_numeric() - .and_then(|length_percentage| length_percentage.to_percentage()) - .unwrap_or(Percentage(0.)); + .and_then(|length_percentage| length_percentage.to_percentage()); let max_size_percentage = max_size .to_numeric() - .and_then(|length_percentage| length_percentage.to_percentage()) - .unwrap_or(Percentage(f32::INFINITY)); - Percentage(size_percentage.0.min(max_size_percentage.0)) + .and_then(|length_percentage| length_percentage.to_percentage()); + max_two_optional_percentages(size_percentage, max_size_percentage) }; LogicalVec2 { @@ -2937,43 +2844,60 @@ fn get_size_percentage_contribution( } } -fn get_outer_sizes_for_measurement( - style: &Arc, - writing_mode: WritingMode, - padding_border_sums: &LogicalVec2, -) -> ( - LogicalVec2, - LogicalVec2, - LogicalVec2, - bool, - LogicalVec2, -) { - let box_sizing = style.get_position().box_sizing; - let outer_size = |size: LogicalVec2| match box_sizing { - BoxSizing::ContentBox => size + *padding_border_sums, - BoxSizing::BorderBox => LogicalVec2 { - inline: size.inline.max(padding_border_sums.inline), - block: size.block.max(padding_border_sums.block), - }, - }; - let get_size_for_axis = |size: &Size| { - // Note that measures treat all size values other than - // as the initial value of the property. - size.to_numeric() - .and_then(|length_percentage| length_percentage.to_length()) - .map(Au::from) - }; +struct CellOrColumnOuterSizes { + min: LogicalVec2, + preferred: LogicalVec2, + max: LogicalVec2>, + percentage: LogicalVec2>, + inline_preferred_size_is_auto: bool, +} - let size = style.box_size(writing_mode); - let min_size = style.min_box_size(writing_mode); - let max_size = style.max_box_size(writing_mode); - ( - outer_size(size.map(|v| get_size_for_axis(v).unwrap_or_else(Au::zero))), - outer_size(min_size.map(|v| get_size_for_axis(v).unwrap_or_else(Au::zero))), - outer_size(max_size.map(|v| get_size_for_axis(v).unwrap_or(MAX_AU))), - !size.inline.is_numeric(), - get_size_percentage_contribution(&size, &max_size), - ) +impl CellOrColumnOuterSizes { + fn new( + style: &Arc, + writing_mode: WritingMode, + padding_border_sums: &LogicalVec2, + ) -> Self { + let box_sizing = style.get_position().box_sizing; + let outer_size = |size: LogicalVec2| match box_sizing { + BoxSizing::ContentBox => size + *padding_border_sums, + BoxSizing::BorderBox => LogicalVec2 { + inline: size.inline.max(padding_border_sums.inline), + block: size.block.max(padding_border_sums.block), + }, + }; + + let outer_size_for_max = |size: LogicalVec2>| match box_sizing { + BoxSizing::ContentBox => size.map_inline_and_block_axes( + |inline| inline.map(|inline| inline + padding_border_sums.inline), + |block| block.map(|block| block + padding_border_sums.block), + ), + BoxSizing::BorderBox => size.map_inline_and_block_axes( + |inline| inline.map(|inline| inline.max(padding_border_sums.inline)), + |block| block.map(|block| block.max(padding_border_sums.block)), + ), + }; + + let get_size_for_axis = |size: &Size| { + // Note that measures treat all size values other than + // as the initial value of the property. + size.to_numeric() + .and_then(|length_percentage| length_percentage.to_length()) + .map(Au::from) + }; + + let size = style.box_size(writing_mode); + let min_size = style.min_box_size(writing_mode); + let max_size = style.max_box_size(writing_mode); + + Self { + min: outer_size(min_size.map(|v| get_size_for_axis(v).unwrap_or_default())), + preferred: outer_size(size.map(|v| get_size_for_axis(v).unwrap_or_default())), + max: outer_size_for_max(max_size.map(get_size_for_axis)), + inline_preferred_size_is_auto: !size.inline.is_numeric(), + percentage: get_size_percentage_contribution(&size, &max_size), + } + } } struct RowspanToDistribute<'a> { @@ -2991,3 +2915,28 @@ impl RowspanToDistribute<'_> { other.coordinates.y > self.coordinates.y && other.range().end < self.range().end } } + +/// The inline size constraints provided by a cell that span multiple columns (`colspan` > 1). +/// These constraints are distributed to the individual columns that make up this cell's span. +#[derive(Debug)] +struct ColspanToDistribute { + starting_column: usize, + span: usize, + content_sizes: ContentSizes, + percentage: Option, +} + +impl ColspanToDistribute { + /// A comparison function to sort the colspan cell constraints primarily by their span + /// width and secondarily by their starting column. This is not an implementation of + /// `PartialOrd` because we want to return [`Ordering::Equal`] even if `self != other`. + fn comparison_for_sort(a: &Self, b: &Self) -> Ordering { + a.span + .cmp(&b.span) + .then_with(|| b.starting_column.cmp(&b.starting_column)) + } + + fn range(&self) -> Range { + self.starting_column..self.starting_column + self.span + } +} diff --git a/tests/wpt/meta/css/css-tables/colspan-001.html.ini b/tests/wpt/meta/css/css-tables/colspan-001.html.ini deleted file mode 100644 index 6cd6b19d88c..00000000000 --- a/tests/wpt/meta/css/css-tables/colspan-001.html.ini +++ /dev/null @@ -1,9 +0,0 @@ -[colspan-001.html] - [td 1] - expected: FAIL - - [td 2] - expected: FAIL - - [td 4] - expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/colspan-002.html.ini b/tests/wpt/meta/css/css-tables/colspan-002.html.ini deleted file mode 100644 index edc4bdec83d..00000000000 --- a/tests/wpt/meta/css/css-tables/colspan-002.html.ini +++ /dev/null @@ -1,9 +0,0 @@ -[colspan-002.html] - [td 1] - expected: FAIL - - [td 2] - expected: FAIL - - [td 4] - expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/colspan-003.html.ini b/tests/wpt/meta/css/css-tables/colspan-003.html.ini deleted file mode 100644 index 7c1dc536a8b..00000000000 --- a/tests/wpt/meta/css/css-tables/colspan-003.html.ini +++ /dev/null @@ -1,9 +0,0 @@ -[colspan-003.html] - [td 1] - expected: FAIL - - [td 2] - expected: FAIL - - [td 4] - expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/height-distribution/computing-row-measure-1.html.ini b/tests/wpt/meta/css/css-tables/height-distribution/computing-row-measure-1.html.ini index 4f761a9796a..1ef6d1e9423 100644 --- a/tests/wpt/meta/css/css-tables/height-distribution/computing-row-measure-1.html.ini +++ b/tests/wpt/meta/css/css-tables/height-distribution/computing-row-measure-1.html.ini @@ -1,6 +1,3 @@ [computing-row-measure-1.html] [Checking intermediate min-content width for span 2 (4)] expected: FAIL - - [Checking intermediate min-content width for span 2 (1)] - expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/html5-table-formatting-3.html.ini b/tests/wpt/meta/css/css-tables/html5-table-formatting-3.html.ini index d09c45d2ea4..1d3fdeaee07 100644 --- a/tests/wpt/meta/css/css-tables/html5-table-formatting-3.html.ini +++ b/tests/wpt/meta/css/css-tables/html5-table-formatting-3.html.ini @@ -1,7 +1,4 @@ [html5-table-formatting-3.html] - [Anonymous consecutive columns spanned by the same set of cells are merged] - expected: FAIL - [Explicitely-defined consecutive columns spanned by the same set of cells are not merged, and cells span across border-spacing] expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/tentative/colspan-redistribution.html.ini b/tests/wpt/meta/css/css-tables/tentative/colspan-redistribution.html.ini index 6cba24fc148..4552581db90 100644 --- a/tests/wpt/meta/css/css-tables/tentative/colspan-redistribution.html.ini +++ b/tests/wpt/meta/css/css-tables/tentative/colspan-redistribution.html.ini @@ -8,27 +8,9 @@ [table 3] expected: FAIL - [table 4] - expected: FAIL - [table 6] expected: FAIL - [table 9] - expected: FAIL - - [table 10] - expected: FAIL - - [table 11] - expected: FAIL - - [table 12] - expected: FAIL - - [table 13] - expected: FAIL - [table 14] expected: FAIL @@ -41,24 +23,12 @@ [table 17] expected: FAIL - [table 18] - expected: FAIL - - [table 19] - expected: FAIL - [table 20] expected: FAIL [table 22] expected: FAIL - [table 23] - expected: FAIL - - [table 24] - expected: FAIL - [table 26] expected: FAIL @@ -68,14 +38,5 @@ [table 28] expected: FAIL - [table 29] - expected: FAIL - - [table 30] - expected: FAIL - - [table 31] - expected: FAIL - [table 8] expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/tentative/table-width-redistribution-fixed-padding.html.ini b/tests/wpt/meta/css/css-tables/tentative/table-width-redistribution-fixed-padding.html.ini index e234381178a..5a523f52bd6 100644 --- a/tests/wpt/meta/css/css-tables/tentative/table-width-redistribution-fixed-padding.html.ini +++ b/tests/wpt/meta/css/css-tables/tentative/table-width-redistribution-fixed-padding.html.ini @@ -13,6 +13,3 @@ [table 15] expected: FAIL - - [table 7] - expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/tentative/table-width-redistribution-fixed.html.ini b/tests/wpt/meta/css/css-tables/tentative/table-width-redistribution-fixed.html.ini index b0b62481a73..8ad9b341543 100644 --- a/tests/wpt/meta/css/css-tables/tentative/table-width-redistribution-fixed.html.ini +++ b/tests/wpt/meta/css/css-tables/tentative/table-width-redistribution-fixed.html.ini @@ -4,6 +4,3 @@ [table 19] expected: FAIL - - [table 12] - expected: FAIL diff --git a/tests/wpt/meta/html/semantics/tabular-data/processing-model-1/col-span-limits.html.ini b/tests/wpt/meta/html/semantics/tabular-data/processing-model-1/col-span-limits.html.ini deleted file mode 100644 index 2cfd485a6c7..00000000000 --- a/tests/wpt/meta/html/semantics/tabular-data/processing-model-1/col-span-limits.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[col-span-limits.html] - [col span of 1000 must work] - expected: FAIL - - [col span of 1001 must be treated as 1000] - expected: FAIL