diff --git a/components/layout_2020/flow/mod.rs b/components/layout_2020/flow/mod.rs
index d0096742174..bd6cbb11c8a 100644
--- a/components/layout_2020/flow/mod.rs
+++ b/components/layout_2020/flow/mod.rs
@@ -308,7 +308,8 @@ fn calculate_inline_content_size_for_block_level_boxes(
impl AccumulatedData {
fn max_size_including_uncleared_floats(&self) -> ContentSizes {
- self.max_size.max(self.left_floats.add(&self.right_floats))
+ self.max_size
+ .max(self.left_floats.union(&self.right_floats))
}
fn clear_floats(&mut self, clear: Clear) {
match clear {
@@ -333,12 +334,12 @@ fn calculate_inline_content_size_for_block_level_boxes(
let accumulate = |mut data: AccumulatedData, (size, float, clear)| {
data.clear_floats(clear);
match float {
- Float::Left => data.left_floats = data.left_floats.add(&size),
- Float::Right => data.right_floats = data.right_floats.add(&size),
+ Float::Left => data.left_floats = data.left_floats.union(&size),
+ Float::Right => data.right_floats = data.right_floats.union(&size),
Float::None => {
data.max_size = data
.max_size
- .max(data.left_floats.add(&data.right_floats).add(&size));
+ .max(data.left_floats.union(&data.right_floats).union(&size));
data.left_floats = ContentSizes::zero();
data.right_floats = ContentSizes::zero();
},
diff --git a/components/layout_2020/sizing.rs b/components/layout_2020/sizing.rs
index 187093086ed..a5748eb9eb8 100644
--- a/components/layout_2020/sizing.rs
+++ b/components/layout_2020/sizing.rs
@@ -4,6 +4,8 @@
//!
+use std::ops::{Add, AddAssign};
+
use app_units::Au;
use serde::Serialize;
use style::logical_geometry::WritingMode;
@@ -14,7 +16,7 @@ use style::Zero;
use crate::style_ext::{Clamp, ComputedValuesExt};
-#[derive(Clone, Debug, Serialize)]
+#[derive(Clone, Copy, Debug, Serialize)]
pub(crate) struct ContentSizes {
pub min_content: Au,
pub max_content: Au,
@@ -43,7 +45,11 @@ impl ContentSizes {
}
}
- pub fn add(&self, other: &Self) -> Self {
+ pub fn max_assign(&mut self, other: Self) {
+ *self = self.max(other);
+ }
+
+ pub fn union(&self, other: &Self) -> Self {
Self {
min_content: self.min_content.max(other.min_content),
max_content: self.max_content + other.max_content,
@@ -51,6 +57,23 @@ impl ContentSizes {
}
}
+impl Add for ContentSizes {
+ type Output = Self;
+
+ fn add(self, rhs: Self) -> Self {
+ Self {
+ min_content: self.min_content + rhs.min_content,
+ max_content: self.max_content + rhs.max_content,
+ }
+ }
+}
+
+impl AddAssign for ContentSizes {
+ fn add_assign(&mut self, rhs: Self) {
+ *self = self.add(rhs)
+ }
+}
+
impl ContentSizes {
///
pub fn shrink_to_fit(&self, available_size: Au) -> Au {
diff --git a/components/layout_2020/table/layout.rs b/components/layout_2020/table/layout.rs
index acd49a7ebc6..92628918137 100644
--- a/components/layout_2020/table/layout.rs
+++ b/components/layout_2020/table/layout.rs
@@ -2,11 +2,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
-use app_units::Au;
+use app_units::{Au, MAX_AU};
use euclid::num::Zero;
use log::warn;
use style::logical_geometry::WritingMode;
-use style::values::computed::{CSSPixelLength, Length, LengthOrAuto};
+use style::values::computed::{CSSPixelLength, Length, LengthOrAuto, Percentage};
use style::values::generics::length::GenericLengthPercentageOrAuto::{Auto, LengthPercentage};
use super::{Table, TableSlot, TableSlotCell};
@@ -16,7 +16,7 @@ use crate::fragment_tree::{BoxFragment, CollapsedBlockMargins, Fragment};
use crate::geom::{LogicalRect, LogicalSides, LogicalVec2};
use crate::positioned::{PositioningContext, PositioningContextLength};
use crate::sizing::ContentSizes;
-use crate::style_ext::{ComputedValuesExt, PaddingBorderMargin};
+use crate::style_ext::{Clamp, ComputedValuesExt, PaddingBorderMargin};
use crate::table::TableSlotCoordinates;
use crate::ContainingBlock;
@@ -37,19 +37,42 @@ struct CellLayout {
struct TableLayout<'a> {
table: &'a Table,
pbm: PaddingBorderMargin,
+ column_constrainedness: Vec,
+ column_has_originating_cell: Vec,
+ cell_measures: Vec>,
assignable_width: Au,
- column_sizes: Vec,
+ column_measures: Vec,
+ distributed_column_widths: Vec,
row_sizes: Vec,
cells_laid_out: Vec>>,
}
+#[derive(Clone, Debug)]
+struct CellOrColumnMeasure {
+ content_sizes: ContentSizes,
+ percentage_width: Percentage,
+}
+
+impl CellOrColumnMeasure {
+ fn zero() -> Self {
+ Self {
+ content_sizes: ContentSizes::zero(),
+ percentage_width: Percentage(0.),
+ }
+ }
+}
+
impl<'a> TableLayout<'a> {
fn new(table: &'a Table) -> TableLayout {
Self {
table,
pbm: PaddingBorderMargin::zero(),
+ column_constrainedness: Vec::new(),
+ column_has_originating_cell: Vec::new(),
+ cell_measures: Vec::new(),
assignable_width: Au::zero(),
- column_sizes: Vec::new(),
+ column_measures: Vec::new(),
+ distributed_column_widths: Vec::new(),
row_sizes: Vec::new(),
cells_laid_out: Vec::new(),
}
@@ -63,24 +86,468 @@ impl<'a> TableLayout<'a> {
positioning_context: &mut PositioningContext,
containing_block: &ContainingBlock,
) {
- let (inline_content_sizes, column_content_sizes) = self
- .table
- .compute_inline_content_sizes(layout_context, containing_block.style.writing_mode);
-
- self.calculate_assignable_table_width(containing_block, inline_content_sizes);
- self.column_sizes =
- self.distribute_width_to_columns(column_content_sizes, containing_block);
+ let writing_mode = containing_block.style.writing_mode;
+ self.compute_column_constrainedness_and_has_originating_cells(writing_mode);
+ self.compute_cell_measures(layout_context, containing_block);
+ self.compute_column_measures();
+ self.compute_table_width(containing_block);
+ self.distributed_column_widths = self.distribute_width_to_columns();
self.do_row_layout_first_pass(layout_context, containing_block, positioning_context);
self.distribute_height_to_rows();
}
- fn calculate_assignable_table_width(
+ /// This is an implementation of *Computing Cell Measures* from
+ /// .
+ pub(crate) fn compute_cell_measures(
&mut self,
+ layout_context: &LayoutContext,
containing_block: &ContainingBlock,
- inline_content_sizes: ContentSizes,
) {
- let grid_min_inline_size = inline_content_sizes.min_content;
- let grid_max_inline_size = inline_content_sizes.max_content;
+ let writing_mode = containing_block.style.writing_mode;
+ for row_index in 0..self.table.size.height {
+ let mut row_measures = vec![CellOrColumnMeasure::zero(); self.table.size.width];
+
+ for column_index in 0..self.table.size.width {
+ let cell = match self.table.slots[row_index][column_index] {
+ TableSlot::Cell(ref cell) => cell,
+ _ => continue,
+ };
+
+ // TODO: Should `box_size` percentages be treated as zero here or resolved against
+ // the containing block?
+ let pbm = cell.style.padding_border_margin(containing_block);
+ let min_inline_size: Au = cell
+ .style
+ .min_box_size(writing_mode)
+ .inline
+ .percentage_relative_to(Length::zero())
+ .map(|value| value.into())
+ .auto_is(Au::zero);
+ let max_inline_size: Au = cell.style.max_box_size(writing_mode).inline.map_or_else(
+ || MAX_AU,
+ |length_percentage| length_percentage.resolve(Length::zero()).into(),
+ );
+ let inline_size: Au = cell
+ .style
+ .box_size(writing_mode)
+ .inline
+ .percentage_relative_to(Length::zero())
+ .map(|value| value.into())
+ .auto_is(Au::zero);
+
+ let content_sizes = cell
+ .contents
+ .contents
+ .inline_content_sizes(layout_context, writing_mode);
+
+ // > The outer min-content width of a table-cell is max(min-width, min-content width)
+ // > adjusted by the cell intrinsic offsets.
+ let mut outer_min_content_width = content_sizes.min_content.max(min_inline_size);
+ let mut outer_max_content_width = if !self.column_constrainedness[column_index] {
+ // > The outer max-content width of a table-cell in a non-constrained column is
+ // > max(min-width, width, min-content width, min(max-width, max-content width))
+ // > adjusted by the cell intrinsic offsets.
+ min_inline_size
+ .max(inline_size)
+ .max(content_sizes.min_content)
+ .max(max_inline_size.min(content_sizes.max_content))
+ } else {
+ // > The outer max-content width of a table-cell in a constrained column is
+ // > max(min-width, width, min-content width, min(max-width, width)) adjusted by the
+ // > cell intrinsic offsets.
+ min_inline_size
+ .max(inline_size)
+ .max(content_sizes.min_content)
+ .max(max_inline_size.min(inline_size))
+ };
+
+ // > 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
+ // > that are percentages:
+ // > min(percentage width, percentage max-width).
+ // > If the computed values are not percentages, then 0% is used for width, and an
+ // > infinite percentage is used for max-width.
+ let inline_size_percent = cell
+ .style
+ .box_size(writing_mode)
+ .inline
+ .non_auto()
+ .and_then(|length_percentage| length_percentage.to_percentage())
+ .unwrap_or_else(|| Percentage(0.));
+ let max_inline_size_percent = cell
+ .style
+ .max_box_size(writing_mode)
+ .inline
+ .and_then(|length_percentage| length_percentage.to_percentage())
+ .unwrap_or_else(|| Percentage(f32::INFINITY));
+ let percentage_contribution =
+ Percentage(inline_size_percent.0.min(max_inline_size_percent.0));
+
+ outer_min_content_width += pbm.padding_border_sums.inline.into();
+ outer_max_content_width += pbm.padding_border_sums.inline.into();
+ row_measures[column_index] = CellOrColumnMeasure {
+ content_sizes: ContentSizes {
+ min_content: outer_min_content_width,
+ max_content: outer_max_content_width,
+ },
+ percentage_width: percentage_contribution,
+ };
+ }
+
+ self.cell_measures.push(row_measures);
+ }
+ }
+
+ /// Compute the constrainedness of every column in the table.
+ ///
+ /// > A column is constrained if its corresponding table-column-group (if any), its
+ /// > corresponding table-column (if any), or any of the cells spanning only that
+ /// > column has a computed width that is not "auto", and is not a percentage.
+ fn compute_column_constrainedness_and_has_originating_cells(
+ &mut self,
+ writing_mode: WritingMode,
+ ) {
+ for column_index in 0..self.table.size.width {
+ let mut column_constrained = false;
+ let mut column_has_originating_cell = false;
+
+ for row_index in 0..self.table.size.height {
+ let coords = TableSlotCoordinates::new(column_index, row_index);
+ let cell_constrained = match self.table.resolve_first_cell(coords) {
+ Some(cell) if cell.colspan == 1 => cell
+ .style
+ .box_size(writing_mode)
+ .inline
+ .non_auto()
+ .map(|length_percentage| length_percentage.to_length().is_some())
+ .unwrap_or(false),
+ _ => false,
+ };
+ column_has_originating_cell = column_has_originating_cell ||
+ matches!(self.table.get_slot(coords), Some(TableSlot::Cell(_)));
+ column_constrained = column_constrained || cell_constrained;
+ }
+ self.column_constrainedness.push(column_constrained);
+ self.column_has_originating_cell
+ .push(column_has_originating_cell);
+ }
+ }
+
+ /// This is an implementation of *Computing Column Measures* from
+ /// .
+ fn compute_column_measures(&mut self) {
+ let mut column_measures = Vec::new();
+
+ // Compute the column measures only taking into account cells with colspan == 1.
+ // This is the base case that will be used to iteratively account for cells with
+ // larger colspans afterward.
+ //
+ // > min-content width of a column based on cells of span up to 1
+ // > The largest of:
+ // > - the width specified for the column:
+ // > - the outer min-content width of its corresponding table-column,
+ // > if any (and not auto)
+ // > - the outer min-content width of its corresponding table-column-group, if any
+ // > - or 0, if there is none
+ // > - the outer min-content width of each cell that spans the column whose colSpan
+ // > is 1 (or just the one in the first row in fixed mode) or 0 if there is none
+ // >
+ // > max-content width of a column based on cells of span up to 1
+ // > The largest of:
+ // > - the outer max-content width of its corresponding
+ // > table-column-group, if any
+ // > - the outer max-content width of its corresponding table-column, if any
+ // > - the outer max-content width of each cell that spans the column
+ // > whose colSpan is 1 (or just the one in the first row if in fixed mode) or 0
+ // > if there is no such cell
+ // >
+ // > intrinsic percentage width of a column based on cells of span up to 1
+ // > The largest of the percentage contributions of each cell that spans the column whose colSpan is
+ // > 1, of its corresponding table-column (if any), and of its corresponding table-column-group (if
+ // > any)
+ //
+ // 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;
+ for column_index in 0..self.table.size.width {
+ let mut column_measure = CellOrColumnMeasure::zero();
+
+ 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;
+ },
+ _ => 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];
+ column_measure
+ .content_sizes
+ .max_assign(cell_measure.content_sizes);
+ column_measure.percentage_width = Percentage(
+ column_measure
+ .percentage_width
+ .0
+ .max(cell_measure.percentage_width.0),
+ );
+ }
+
+ column_measures.push(column_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, column_measures) = self
+ .compute_content_sizes_for_columns_with_span_up_to_n(next_span_n, &column_measures);
+ }
+
+ // > intrinsic percentage width of a column:
+ // > the smaller of:
+ // > * the intrinsic percentage width of the column based on cells of span up to N,
+ // > where N is the number of columns in the table
+ // > * 100% minus the sum of the intrinsic percentage width of all prior columns in
+ // > the table (further left when direction is "ltr" (right for "rtl"))
+ let mut total_intrinsic_percentage_width = 0.;
+ for column_index in 0..self.table.size.width {
+ let column_measure = &mut column_measures[column_index];
+ let final_intrinsic_percentage_width = column_measure
+ .percentage_width
+ .0
+ .min(100. - total_intrinsic_percentage_width);
+ total_intrinsic_percentage_width += final_intrinsic_percentage_width;
+ column_measure.percentage_width = Percentage(final_intrinsic_percentage_width);
+ }
+
+ self.column_measures = column_measures;
+ }
+
+ fn compute_content_sizes_for_columns_with_span_up_to_n(
+ &self,
+ n: usize,
+ old_column_measures: &[CellOrColumnMeasure],
+ ) -> (usize, Vec) {
+ let mut next_span_n = usize::MAX;
+ let mut new_content_sizes_for_columns = Vec::new();
+
+ for column_index in 0..self.table.size.width {
+ let old_column_measure = &old_column_measures[column_index];
+ let mut new_column_content_sizes = ContentSizes::zero();
+ let mut new_column_intrinsic_percentage_width = Percentage(0.);
+
+ 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];
+ 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 + old_column_measures[spanned_column_index].content_sizes
+ },
+ );
+
+ // TODO: Take into account border spacing.
+ let old_column_content_size = old_column_measure.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.
+ //
+ // TODO: Take into account border spacing.
+
+ // > 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
+ //
+ // TODO: Take into account border spacing.
+ 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)
+ .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)
+ .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.
+ //
+ // TODO: Take into account. border spacing.
+
+ // > 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
+ //
+ // TODO: Take into account. border spacing.
+ let b_2 = (cell_inline_content_sizes.max_content -
+ baseline_content_sizes.max_content)
+ .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_measure.percentage_width.0 <= 0. &&
+ cell_measures.percentage_width.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 =
+ old_column_measures[spanned_column_index].percentage_width;
+ if spanned_column_percentage.0 == 0. {
+ spanned_columns_with_zero += 1;
+ }
+ sum + spanned_column_percentage.0
+ });
+ let step_2 = (cell_measures.percentage_width -
+ 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));
+ }
+ }
+ new_content_sizes_for_columns.push(CellOrColumnMeasure {
+ content_sizes: new_column_content_sizes,
+ percentage_width: new_column_intrinsic_percentage_width,
+ });
+ }
+ (next_span_n, new_content_sizes_for_columns)
+ }
+
+ fn compute_table_width(&mut self, containing_block: &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 grid_min_and_max = self
+ .column_measures
+ .iter()
+ .fold(ContentSizes::zero(), |result, measure| {
+ result + measure.content_sizes
+ });
self.pbm = self.table.style.padding_border_margin(containing_block);
let content_box_size = self
@@ -95,7 +562,9 @@ impl<'a> TableLayout<'a> {
// 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_inline_size.max(min_content_sizes.inline.into());
+ let used_min_width_of_table = grid_min_and_max
+ .min_content
+ .max(min_content_sizes.inline.into());
// 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:
@@ -106,24 +575,20 @@ impl<'a> TableLayout<'a> {
// > the table’s containing block width), the used min-width of the table.
let used_width_of_table = match content_box_size.inline {
LengthPercentage(length_percentage) => {
- length_percentage.max(used_min_width_of_table.into())
+ Au::from(length_percentage).max(used_min_width_of_table)
},
- Auto => grid_max_inline_size
+ Auto => grid_min_and_max
+ .max_content
.min(containing_block.inline_size.into())
- .max(used_min_width_of_table)
- .into(),
+ .max(used_min_width_of_table),
};
- self.assignable_width = used_width_of_table.into();
+ self.assignable_width = used_width_of_table;
}
/// Distribute width to columns, performing step 2.4 of table layout from
/// .
- fn distribute_width_to_columns(
- &self,
- column_content_sizes: Vec,
- containing_block: &ContainingBlock,
- ) -> Vec {
+ fn distribute_width_to_columns(&self) -> Vec {
if self.table.slots.is_empty() {
return Vec::new();
}
@@ -162,41 +627,27 @@ impl<'a> TableLayout<'a> {
let mut max_content_sizing_guesses = Vec::new();
for column_idx in 0..self.table.size.width {
- let coords = TableSlotCoordinates::new(column_idx, 0);
- let cell = match self.table.resolve_first_cell(coords) {
- Some(cell) => cell,
- None => {
- min_content_sizing_guesses.push(Au::zero());
- min_content_percentage_sizing_guesses.push(Au::zero());
- min_content_specified_sizing_guesses.push(Au::zero());
- max_content_sizing_guesses.push(Au::zero());
- continue;
- },
- };
+ use style::Zero;
- let inline_size = cell
- .style
- .box_size(containing_block.style.writing_mode)
- .inline;
- let min_and_max_content = &column_content_sizes[column_idx];
- let min_content_width = min_and_max_content.min_content;
- let max_content_width = min_and_max_content.max_content;
+ let column_measure = &self.column_measures[column_idx];
+ let min_content_width = column_measure.content_sizes.min_content;
+ let max_content_width = column_measure.content_sizes.max_content;
+ let constrained = self.column_constrainedness[column_idx];
let (
min_content_percentage_sizing_guess,
min_content_specified_sizing_guess,
max_content_sizing_guess,
- ) = match inline_size {
- LengthPercentage(length_percentage) if length_percentage.has_percentage() => {
- let percent_guess = min_content_width.max(
- length_percentage
- .resolve(self.assignable_width.into())
- .into(),
- );
- (percent_guess, percent_guess, percent_guess)
- },
- LengthPercentage(_) => (min_content_width, max_content_width, max_content_width),
- Auto => (min_content_width, min_content_width, max_content_width),
+ ) = if !column_measure.percentage_width.is_zero() {
+ let resolved = self
+ .assignable_width
+ .scale_by(column_measure.percentage_width.0);
+ let percent_guess = min_content_width.max(resolved.into());
+ (percent_guess, percent_guess, percent_guess)
+ } else if constrained {
+ (min_content_width, max_content_width, max_content_width)
+ } else {
+ (min_content_width, min_content_width, max_content_width)
};
min_content_sizing_guesses.push(min_content_width);
@@ -284,17 +735,143 @@ impl<'a> TableLayout<'a> {
)
}
- fn distribute_extra_width_to_columns(
- &self,
- max_content_sizing_guesses: &mut Vec,
- max_content_sum: Au,
- ) {
- // The simplest distribution algorithm, until we have support for proper extra space
- // distribution is to equally distribute the extra space.
- let ratio_factor = 1.0 / max_content_sizing_guesses.len() as f32;
+ /// This is an implementation of *Distributing excess width to columns* from
+ /// .
+ fn distribute_extra_width_to_columns(&self, column_sizes: &mut Vec, column_sizes_sum: Au) {
+ let all_columns = 0..self.table.size.width;
+ let extra_inline_size = self.assignable_width - column_sizes_sum;
+
+ let has_originating_cells =
+ |column_index: &usize| self.column_has_originating_cell[*column_index];
+ let is_constrained = |column_index: &usize| self.column_constrainedness[*column_index];
+ let is_unconstrained = |column_index: &usize| !is_constrained(column_index);
+ let has_percent_greater_than_zero =
+ |column_index: &usize| self.column_measures[*column_index].percentage_width.0 > 0.;
+ let has_percent_zero = |column_index: &usize| !has_percent_greater_than_zero(column_index);
+ let has_max_content = |column_index: &usize| {
+ self.column_measures[*column_index]
+ .content_sizes
+ .max_content !=
+ Au(0)
+ };
+
+ let max_content_sum =
+ |column_index: usize| self.column_measures[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
+ // > grow by this rule), the distributed widths of the columns allowed to grow by this rule
+ // > are increased in proportion to max-content width so the total increase adds to the
+ // > excess width.
+ let unconstrained_max_content_columns = all_columns
+ .clone()
+ .filter(is_unconstrained)
+ .filter(has_originating_cells)
+ .filter(has_percent_zero)
+ .filter(has_max_content);
+ let total_max_content_width = unconstrained_max_content_columns
+ .clone()
+ .map(max_content_sum)
+ .fold(Au::zero(), |a, b| a + b);
+ 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.column_measures[column_index]
+ .content_sizes
+ .max_content
+ .to_f32_px() /
+ total_max_content_width.to_f32_px(),
+ );
+ }
+ return;
+ }
+
+ // > Otherwise, if there are non-constrained columns that have originating cells with intrinsic
+ // > percentage width of 0% (aka the columns allowed to grow by this rule, which thanks to the
+ // > previous rule must have zero max-content width), the distributed widths of the columns
+ // > allowed to grow by this rule are increased by equal amounts so the total increase adds to
+ // > the excess width.V
+ let unconstrained_no_percent_columns = all_columns
+ .clone()
+ .filter(is_unconstrained)
+ .filter(has_originating_cells)
+ .filter(has_percent_zero);
+ let total_unconstrained_no_percent = unconstrained_no_percent_columns.clone().count();
+ if total_unconstrained_no_percent > 0 {
+ let extra_space_per_column =
+ extra_inline_size.scale_by(1.0 / total_unconstrained_no_percent as f32);
+ for column_index in unconstrained_no_percent_columns {
+ column_sizes[column_index] += extra_space_per_column;
+ }
+ return;
+ }
+
+ // > Otherwise, if there are constrained columns with intrinsic percentage width of 0% and
+ // > with nonzero max-content width (aka the columns allowed to grow by this rule, which, due
+ // > to other rules, must have originating cells), the distributed widths of the columns
+ // > allowed to grow by this rule are increased in proportion to max-content width so the
+ // > total increase adds to the excess width.
+ let constrained_max_content_columns = all_columns
+ .clone()
+ .filter(is_constrained)
+ .filter(has_originating_cells)
+ .filter(has_percent_zero)
+ .filter(has_max_content);
+ let total_max_content_width = constrained_max_content_columns
+ .clone()
+ .map(max_content_sum)
+ .fold(Au::zero(), |a, b| a + b);
+ 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.column_measures[column_index]
+ .content_sizes
+ .max_content
+ .to_f32_px() /
+ total_max_content_width.to_f32_px(),
+ );
+ }
+ return;
+ }
+
+ // > Otherwise, if there are columns with intrinsic percentage width greater than 0% (aka the
+ // > columns allowed to grow by this rule, which, due to other rules, must have originating
+ // > cells), the distributed widths of the columns allowed to grow by this rule are increased
+ // > in proportion to intrinsic percentage width so the total increase adds to the excess
+ // > width.
+ let columns_with_percentage = all_columns.clone().filter(has_percent_greater_than_zero);
+ let total_percent = columns_with_percentage
+ .clone()
+ .map(|column_index| self.column_measures[column_index].percentage_width.0)
+ .sum::();
+ if total_percent > 0. {
+ for column_index in columns_with_percentage {
+ column_sizes[column_index] += extra_inline_size.scale_by(
+ self.column_measures[column_index].percentage_width.0 / total_percent,
+ );
+ }
+ return;
+ }
+
+ // > Otherwise, if there is any such column, the distributed widths of all columns that have
+ // > originating cells are increased by equal amounts so the total increase adds to the excess
+ // > width.
+ let has_originating_cells_columns = all_columns.clone().filter(has_originating_cells);
+ let total_has_originating_cells = has_originating_cells_columns.clone().count();
+ if total_has_originating_cells > 0 {
+ let extra_space_per_column =
+ extra_inline_size.scale_by(1.0 / total_has_originating_cells as f32);
+ for column_index in has_originating_cells_columns {
+ column_sizes[column_index] += extra_space_per_column;
+ }
+ return;
+ }
+
+ // > 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 =
- (self.assignable_width - max_content_sum).scale_by(ratio_factor);
- for guess in max_content_sizing_guesses.iter_mut() {
+ extra_inline_size.scale_by(1.0 / self.table.size.width as f32);
+ for guess in column_sizes.iter_mut() {
*guess += extra_space_for_all_columns;
}
}
@@ -319,7 +896,7 @@ impl<'a> TableLayout<'a> {
let mut total_width = Au::zero();
for width_index in column_index..column_index + cell.colspan {
- total_width += self.column_sizes[width_index];
+ total_width += self.distributed_column_widths[width_index];
}
let border = cell.style.border_width(containing_block.style.writing_mode);
@@ -393,7 +970,7 @@ impl<'a> TableLayout<'a> {
positioning_context: &mut PositioningContext,
) -> (Vec, Au) {
assert_eq!(self.table.size.height, self.row_sizes.len());
- assert_eq!(self.table.size.width, self.column_sizes.len());
+ assert_eq!(self.table.size.width, self.distributed_column_widths.len());
let mut fragments = Vec::new();
let mut row_offset = Au::zero();
@@ -402,7 +979,7 @@ impl<'a> TableLayout<'a> {
let row_size = self.row_sizes[row_index];
for column_index in 0..self.table.size.width {
- let column_size = self.column_sizes[column_index];
+ let column_size = self.distributed_column_widths[column_index];
let layout = match self.cells_laid_out[row_index][column_index].take() {
Some(layout) => layout,
None => continue,
@@ -463,19 +1040,27 @@ impl Table {
&self,
layout_context: &LayoutContext,
writing_mode: WritingMode,
- ) -> (ContentSizes, Vec) {
- let mut final_size = ContentSizes::zero();
- let column_content_sizes = (0..self.size.width)
- .map(|column_idx| {
- let coords = TableSlotCoordinates::new(column_idx, 0);
+ ) -> (ContentSizes, Vec>) {
+ let mut total_size = ContentSizes::zero();
+ let mut inline_content_sizes = Vec::new();
+ for column_index in 0..self.size.width {
+ let mut row_inline_content_sizes = Vec::new();
+ let mut max_content_sizes_in_column = ContentSizes::zero();
+
+ for row_index in 0..self.size.width {
+ // TODO: Take into account padding and border here.
+ let coords = TableSlotCoordinates::new(column_index, row_index);
+
let content_sizes =
self.inline_content_sizes_for_cell_at(coords, layout_context, writing_mode);
- final_size.min_content += content_sizes.min_content;
- final_size.max_content += content_sizes.max_content;
- content_sizes
- })
- .collect();
- (final_size, column_content_sizes)
+ max_content_sizes_in_column.max_assign(content_sizes);
+ row_inline_content_sizes.push(content_sizes);
+ }
+
+ inline_content_sizes.push(row_inline_content_sizes);
+ total_size += max_content_sizes_in_column;
+ }
+ (total_size, inline_content_sizes)
}
pub(crate) fn inline_content_sizes(
diff --git a/tests/wpt/meta/css/CSS2/tables/border-collapse-offset-002.xht.ini b/tests/wpt/meta/css/CSS2/tables/border-collapse-offset-002.xht.ini
new file mode 100644
index 00000000000..0949aad716a
--- /dev/null
+++ b/tests/wpt/meta/css/CSS2/tables/border-collapse-offset-002.xht.ini
@@ -0,0 +1,2 @@
+[border-collapse-offset-002.xht]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-tables/border-collapse-rowspan-cell.html.ini b/tests/wpt/meta/css/css-tables/border-collapse-rowspan-cell.html.ini
new file mode 100644
index 00000000000..8e2e4d7a3f2
--- /dev/null
+++ b/tests/wpt/meta/css/css-tables/border-collapse-rowspan-cell.html.ini
@@ -0,0 +1,2 @@
+[border-collapse-rowspan-cell.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-tables/column-track-merging.html.ini b/tests/wpt/meta/css/css-tables/column-track-merging.html.ini
index 4941e89e0b5..ff35b117ec5 100644
--- a/tests/wpt/meta/css/css-tables/column-track-merging.html.ini
+++ b/tests/wpt/meta/css/css-tables/column-track-merging.html.ini
@@ -34,3 +34,6 @@
[main table 9]
expected: FAIL
+
+ [main table 3]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-tables/fixed-layout-1.html.ini b/tests/wpt/meta/css/css-tables/fixed-layout-1.html.ini
index 2d03a606c8f..dd00b63aa4a 100644
--- a/tests/wpt/meta/css/css-tables/fixed-layout-1.html.ini
+++ b/tests/wpt/meta/css/css-tables/fixed-layout-1.html.ini
@@ -1,6 +1,3 @@
[fixed-layout-1.html]
[Table-layout:fixed grows the table if needed for minimum-width]
expected: FAIL
-
- [Table-layout:fixed takes visual order into account, not dom order]
- expected: FAIL
diff --git a/tests/wpt/meta/css/css-tables/fixed-layout-excess-width-distribution-001.html.ini b/tests/wpt/meta/css/css-tables/fixed-layout-excess-width-distribution-001.html.ini
deleted file mode 100644
index 5d81ad8624d..00000000000
--- a/tests/wpt/meta/css/css-tables/fixed-layout-excess-width-distribution-001.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[fixed-layout-excess-width-distribution-001.html]
- [#theTable 1]
- expected: FAIL
diff --git a/tests/wpt/meta/css/css-tables/fractional-percent-width.html.ini b/tests/wpt/meta/css/css-tables/fractional-percent-width.html.ini
deleted file mode 100644
index 99c5f3b4c25..00000000000
--- a/tests/wpt/meta/css/css-tables/fractional-percent-width.html.ini
+++ /dev/null
@@ -1,9 +0,0 @@
-[fractional-percent-width.html]
- [.cell 1]
- expected: FAIL
-
- [.cell 2]
- expected: FAIL
-
- [.cell 3]
- expected: FAIL
diff --git a/tests/wpt/meta/css/css-tables/height-distribution/percentage-sizing-of-table-cell-children.html.ini b/tests/wpt/meta/css/css-tables/height-distribution/percentage-sizing-of-table-cell-children.html.ini
index aa13a4d49d4..1f62b3f9938 100644
--- a/tests/wpt/meta/css/css-tables/height-distribution/percentage-sizing-of-table-cell-children.html.ini
+++ b/tests/wpt/meta/css/css-tables/height-distribution/percentage-sizing-of-table-cell-children.html.ini
@@ -2,9 +2,6 @@
[Percentages resolve based on the row height]
expected: FAIL
- [Percentages resolve based on the final column width]
- expected: FAIL
-
[Percentages resolve based on the final row height]
expected: FAIL
diff --git a/tests/wpt/meta/css/css-tables/table-cell-baseline-static-position.html.ini b/tests/wpt/meta/css/css-tables/table-cell-baseline-static-position.html.ini
deleted file mode 100644
index 05f4ec866ce..00000000000
--- a/tests/wpt/meta/css/css-tables/table-cell-baseline-static-position.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[table-cell-baseline-static-position.html]
- 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 715f1209480..00015d4a33b 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
@@ -20,9 +20,6 @@
[table 7]
expected: FAIL
- [table 8]
- expected: FAIL
-
[table 9]
expected: FAIL
diff --git a/tests/wpt/meta/css/css-tables/tentative/table-quirks.html.ini b/tests/wpt/meta/css/css-tables/tentative/table-quirks.html.ini
index 7f519f119cf..7b3c593006a 100644
--- a/tests/wpt/meta/css/css-tables/tentative/table-quirks.html.ini
+++ b/tests/wpt/meta/css/css-tables/tentative/table-quirks.html.ini
@@ -7,3 +7,6 @@
[table 5]
expected: FAIL
+
+ [table 3]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-tables/tentative/td-box-sizing-003.html.ini b/tests/wpt/meta/css/css-tables/tentative/td-box-sizing-003.html.ini
index feb1896c45c..121baab5ecb 100644
--- a/tests/wpt/meta/css/css-tables/tentative/td-box-sizing-003.html.ini
+++ b/tests/wpt/meta/css/css-tables/tentative/td-box-sizing-003.html.ini
@@ -14,12 +14,6 @@
[table 11]
expected: FAIL
- [table 1]
- expected: FAIL
-
- [table 4]
- expected: FAIL
-
[table 5]
expected: FAIL
diff --git a/tests/wpt/meta/css/css-tables/th-text-align.html.ini b/tests/wpt/meta/css/css-tables/th-text-align.html.ini
new file mode 100644
index 00000000000..01a4b1f4e58
--- /dev/null
+++ b/tests/wpt/meta/css/css-tables/th-text-align.html.ini
@@ -0,0 +1,2 @@
+[th-text-align.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-tables/width-distribution/distribution-algo-min-content-percent-guess.html.ini b/tests/wpt/meta/css/css-tables/width-distribution/distribution-algo-min-content-percent-guess.html.ini
deleted file mode 100644
index 5931ec4e91c..00000000000
--- a/tests/wpt/meta/css/css-tables/width-distribution/distribution-algo-min-content-percent-guess.html.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-[distribution-algo-min-content-percent-guess.html]
- [The second cell is 200px due to the 50% set on the first cell and the second gets distributed the remaining space since its auto]
- expected: FAIL
-
- [The first cell is 200px due to its 50% specified width is greater than ]
- expected: FAIL