@ -6,7 +6,7 @@ use core::cmp::Ordering;
use std ::mem ;
use std ::mem ;
use std ::ops ::Range ;
use std ::ops ::Range ;
use app_units ::{ Au , MAX_AU } ;
use app_units ::Au ;
use log ::warn ;
use log ::warn ;
use rayon ::iter ::{ IndexedParallelIterator , IntoParallelRefIterator , ParallelIterator } ;
use rayon ::iter ::{ IndexedParallelIterator , IntoParallelRefIterator , ParallelIterator } ;
use servo_arc ::Arc ;
use servo_arc ::Arc ;
@ -99,7 +99,24 @@ struct ColumnLayout {
constrained : bool ,
constrained : bool ,
has_originating_cells : bool ,
has_originating_cells : bool ,
content_sizes : ContentSizes ,
content_sizes : ContentSizes ,
percentage : Percentage ,
percentage : Option < Percentage > ,
}
fn max_two_optional_percentages (
a : Option < Percentage > ,
b : Option < Percentage > ,
) -> Option < Percentage > {
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 {
impl CollapsedBorder {
@ -200,19 +217,19 @@ pub(crate) struct TableLayout<'a> {
#[ derive(Clone, Debug) ]
#[ derive(Clone, Debug) ]
struct CellOrTrackMeasure {
struct CellOrTrackMeasure {
content_sizes : ContentSizes ,
content_sizes : ContentSizes ,
percentage : Percentage ,
percentage : Option < Percentage > ,
}
}
impl Zero for CellOrTrackMeasure {
impl Zero for CellOrTrackMeasure {
fn zero ( ) -> Self {
fn zero ( ) -> Self {
Self {
Self {
content_sizes : ContentSizes ::zero ( ) ,
content_sizes : ContentSizes ::zero ( ) ,
percentage : Percentage ( 0. ) ,
percentage : None ,
}
}
}
}
fn is_zero ( & self ) -> bool {
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 ( ) ,
block : padding . block_sum ( ) + border . block_sum ( ) ,
} ;
} ;
let ( size , min_size , max_size , inline_size_is_auto , percentage_contribution ) =
let CellOrColumnOuterSizes {
get_outer_sizes_for_measurement (
preferred : preferred_size ,
& cell . base . style ,
min : min_size ,
writing_mode ,
max : max_size ,
& padding_border_sums ,
inline_preferred_size_is_auto ,
) ;
percentage : percentage_size ,
} = CellOrColumnOuterSizes ::new (
& cell . base . style ,
writing_mode ,
& padding_border_sums ,
) ;
// <https://drafts.csswg.org/css-tables/#in-fixed-mode>
// <https://drafts.csswg.org/css-tables/#in-fixed-mode>
// > When a table-root is laid out in fixed mode, the content of its table-cells is ignored
// > 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.
// These formulas differ from the spec, but seem to match Gecko and Blink.
let outer_min_content_width = if is_in_fixed_mode {
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 an outer size, but we deliberately ignore borders and padding.
// This is like allowing the content-box width to be negative.
// This is like allowing the content-box width to be negative.
Au ::zero ( )
Au ::zero ( )
} else {
} else {
size . inline . min ( max_size . inline ) . max ( min_size . inline )
preferred_size
. inline
. clamp_between_extremums ( min_size . inline , max_size . inline )
}
}
} else {
} else {
inline_content_sizes
inline_content_sizes
. min_content
. min_content
. min ( max_size . inline )
. clamp_between_extremums ( min_size . inline , max_size . inline )
. max ( min_size . inline )
} ;
} ;
let outer_max_content_width = if self . columns [ column_index ] . constrained {
let outer_max_content_width = if self . columns [ column_index ] . constrained {
inline_content_sizes
inline_content_sizes
. min_content
. min_content
. max ( size . inline )
. max ( preferred_size . inline )
. min ( max_size . inline )
. clamp_between_extremums ( min_size . inline , max_size . inline )
. max ( min_size . inline )
} else {
} else {
inline_content_sizes
inline_content_sizes
. max_content
. max_content
. max ( size . inline )
. max ( preferred_size . inline )
. min ( max_size . inline )
. clamp_between_extremums ( min_size . inline , max_size . inline )
. max ( min_size . inline )
} ;
} ;
assert! ( outer_min_content_width < = outer_max_content_width ) ;
assert! ( outer_min_content_width < = outer_max_content_width ) ;
@ -338,7 +359,7 @@ impl<'a> TableLayout<'a> {
min_content : outer_min_content_width ,
min_content : outer_min_content_width ,
max_content : outer_max_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
// These sizes are incorporated after the first row layout pass, when the block size
// of the layout is known.
// of the layout is known.
let block_measure = CellOrTrackMeasure {
let block_measure = CellOrTrackMeasure {
content_sizes : size. block . into ( ) ,
content_sizes : preferred_ size. block . into ( ) ,
percentage : percentage_ contribution . block ,
percentage : percentage_ size . block ,
} ;
} ;
self . cell_measures [ row_index ] [ column_index ] = LogicalVec2 {
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 `table-column` and `table-column-group` lengths.
// TODO: Take into account changes to this computation for fixed table layout.
// 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 {
for column_index in 0 .. self . table . size . width {
let column = & mut self . columns [ column_index ] ;
let column = & mut self . columns [ column_index ] ;
@ -477,31 +498,34 @@ impl<'a> TableLayout<'a> {
for row_index in 0 .. self . table . size . height {
for row_index in 0 .. self . table . size . height {
let coords = TableSlotCoordinates ::new ( column_index , row_index ) ;
let coords = TableSlotCoordinates ::new ( column_index , row_index ) ;
match self . table . resolve_first_cell ( coords ) {
let cell_measure = & self . cell_measures [ row_index ] [ column_index ] . inline ;
Some ( cell ) if cell . colspan = = 1 = > cell ,
Some ( cell ) = > {
let cell = match self . table . get_slot ( coords ) {
next_span_n = next_span_n . min ( cell . colspan ) ;
Some ( TableSlot ::Cell ( cell ) ) = > cell ,
continue ;
} ,
_ = > continue ,
_ = > 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
// This takes the max of `min_content`, `max_content`, and
// intrinsic percentage width as described above.
// intrinsic percentage width as described above.
let cell_measure = & self . cell_measures [ row_index ] [ column_index ] . inline ;
column . incorporate_cell_measure ( cell_measure ) ;
column . content_sizes . max_assign ( cell_measure . content_sizes ) ;
column . percentage =
Percentage ( column_measure . percentage . 0. max ( cell_measure . percentage . 0 ) ) ;
}
}
}
}
// Now we have the base computation complete, so iteratively take into account cells
// Sort the colspanned cell constraints by their span and starting column.
// with higher colspan. Using `next_span_n` we can skip over span counts that don't
colspan_cell_constraints . sort_by ( ColspanToDistribute ::comparison_for_sort ) ;
// correspond to any cells.
while next_span_n < usize ::MAX {
// Distribute constraints from cells with colspan != 1 to their component columns.
( next_span_n , self . columns ) =
self . distribute_colspanned_cells_to_columns ( colspan_cell_constraints ) ;
self . compute_content_sizes_for_columns_with_span_up_to_n ( next_span_n ) ;
}
// > intrinsic percentage width of a column:
// > intrinsic percentage width of a column:
// > the smaller of:
// > the smaller of:
@ -511,216 +535,98 @@ impl<'a> TableLayout<'a> {
// > the table (further left when direction is "ltr" (right for "rtl"))
// > the table (further left when direction is "ltr" (right for "rtl"))
let mut total_intrinsic_percentage_width = 0. ;
let mut total_intrinsic_percentage_width = 0. ;
for column in self . columns . iter_mut ( ) {
for column in self . columns . iter_mut ( ) {
let final_intrinsic_percentage_width = column
if let Some ( ref mut percentage ) = column . percentage {
. percentage
let final_intrinsic_percentage_width =
. 0
percentage . 0. min ( 1. - total_intrinsic_percentage_width ) ;
. min ( 1. - total_intrinsic_percentage_width ) ;
total_intrinsic_percentage_width + = final_intrinsic_percentage_width ;
total_intrinsic_ percentage_width + = final_intrinsic_percentage_width ;
* percentage = Percentage ( final_intrinsic_percentage_width ) ;
column . percentage = Percentage ( final_intrinsic_percentage_width ) ;
}
}
}
}
}
fn compute_content_sizes_for_columns_with_span_up_to_n (
fn distribute_colspanned_cells_to_columns (
& self ,
& mut self ,
n : usize ,
colspan_cell_constraints : Vec < ColspanToDistribute > ,
) -> ( usize , Vec < ColumnLayout > ) {
) {
let mut next_span_n = usize ::MAX ;
for colspan_cell_constraints in colspan_cell_constraints {
let mut new_columns = Vec ::new ( ) ;
self . distribute_colspanned_cell_to_columns ( colspan_cell_constraints ) ;
let border_spacing = self . table . border_spacing ( ) ;
}
}
for column_index in 0 .. self . table . size . width {
let old_column = & self . columns [ column_index ] ;
/// Distribute the inline size from a cell with colspan != 1 to the columns that it spans.
let mut new_column_content_sizes = old_column . content_sizes ;
/// This is heavily inspired by the approach that Chromium takes in redistributing colspan
let mut new_column_intrinsic_percentage_width = old_column . percentage ;
/// cells' inline size to columns (`DistributeColspanCellToColumnsAuto` in
/// `blink/renderer/core/layout/table/table_layout_utils.cc`).
for row_index in 0 .. self . table . size . height {
fn distribute_colspanned_cell_to_columns (
let coords = TableSlotCoordinates ::new ( column_index , row_index ) ;
& mut self ,
let resolved_coords = match self . table . resolve_first_cell_coords ( coords ) {
colspan_cell_constraints : ColspanToDistribute ,
Some ( resolved_coords ) = > resolved_coords ,
) {
None = > continue ,
let border_spacing = self . table . border_spacing ( ) . inline ;
} ;
let column_range = colspan_cell_constraints . range ( ) ;
let column_count = column_range . len ( ) ;
let cell = match self . table . resolve_first_cell ( resolved_coords ) {
let total_border_spacing =
Some ( cell ) if cell . colspan < = n = > cell ,
border_spacing . scale_by ( ( colspan_cell_constraints . span - 1 ) as f32 ) ;
Some ( cell ) = > {
next_span_n = next_span_n . min ( cell . colspan ) ;
let mut percent_columns_count = 0 ;
continue ;
let mut columns_percent_sum = 0. ;
} ,
let mut columns_non_percent_max_inline_size_sum = Au ::zero ( ) ;
_ = > continue ,
for column in self . columns [ column_range . clone ( ) ] . iter ( ) {
} ;
if let Some ( percentage ) = column . percentage {
percent_columns_count + = 1 ;
let cell_measures =
columns_percent_sum + = percentage . 0 ;
& self . cell_measures [ resolved_coords . y ] [ resolved_coords . x ] . inline ;
} else {
let cell_inline_content_sizes = cell_measures . content_sizes ;
columns_non_percent_max_inline_size_sum + = column . content_sizes . max_content ;
}
let columns_spanned = resolved_coords . x .. resolved_coords . x + cell . colspan ;
}
let baseline_content_sizes : ContentSizes = columns_spanned . clone ( ) . fold (
ContentSizes ::zero ( ) ,
let colspan_percentage = colspan_cell_constraints . percentage . unwrap_or_default ( ) ;
| total : ContentSizes , spanned_column_index | {
let surplus_percent = colspan_percentage . 0 - columns_percent_sum ;
total + self . columns [ spanned_column_index ] . content_sizes
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 old_column_content_size = old_column . content_sizes ;
}
// > **min-content width of a column based on cells of span up to N (N > 1)**
let ratio = if columns_non_percent_max_inline_size_sum . is_zero ( ) {
// >
1. / ( ( column_count - percent_columns_count ) as f32 )
// > the largest of the min-content width of the column based on cells of span up to
} else {
// > N-1 and the contributions of the cells in the column whose colSpan is N, where
column . content_sizes . max_content . to_f32_px ( ) /
// > the contribution of a cell is the result of taking the following steps:
columns_non_percent_max_inline_size_sum . to_f32_px ( )
// >
} ;
// > 1. Define the baseline min-content width as the sum of the max-content
column . percentage = Some ( Percentage ( surplus_percent * ratio ) ) ;
// > 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 colspan_cell_min_size = ( colspan_cell_constraints . content_sizes . min_content -
let baseline_min_content_width = baseline_content_sizes . min_content ;
total_border_spacing )
let baseline_max_content_width = baseline_content_sizes . max_content ;
. max ( Au ::zero ( ) ) ;
let distributed_minimum = Self ::distribute_width_to_columns (
// > 2. Define the baseline border spacing as the sum of the horizontal
colspan_cell_min_size ,
// > border-spacing for any columns spanned by the cell, other than the one in
& self . columns [ column_range . clone ( ) ] ,
// > which the cell originates.
) ;
let baseline_border_spacing = border_spacing . inline * ( n as i32 - 1 ) ;
{
let column_span = & mut self . columns [ colspan_cell_constraints . range ( ) ] ;
// > 3. The contribution of the cell is the sum of:
for ( column , minimum_size ) in column_span . iter_mut ( ) . zip ( distributed_minimum ) {
// > a. the min-content width of the column based on cells of span up to N-1
column . content_sizes . min_content . max_assign ( minimum_size ) ;
let a = old_column_content_size . min_content ;
}
}
// > b. the product of:
// > - the ratio of:
let colspan_cell_max_size = ( colspan_cell_constraints . content_sizes . max_content -
// > - the max-content width of the column based on cells of span up
total_border_spacing )
// > to N-1 of the column minus the min-content width of the
. max ( Au ::zero ( ) ) ;
// > column based on cells of span up to N-1 of the column, to
let distributed_maximum = Self ::distribute_width_to_columns (
// > - the baseline max-content width minus the baseline min-content
colspan_cell_max_size ,
// > width
& self . columns [ colspan_cell_constraints . range ( ) ] ,
// > 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
let column_span = & mut self . columns [ colspan_cell_constraints . range ( ) ] ;
// > at least 0 and at most the difference between the baseline
for ( column , maximum_size ) in column_span . iter_mut ( ) . zip ( distributed_maximum ) {
// > max-content width and the baseline min-content width
column
let old_content_size_difference =
. content_sizes
old_column_content_size . max_content - old_column_content_size . min_content ;
. max_content
let baseline_difference = baseline_min_content_width - baseline_max_content_width ;
. max_assign ( maximum_size . max ( column . content_sizes . min_content ) ) ;
}
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 ) ;
}
}
( next_span_n , new_columns )
}
}
/// Compute the GRIDMIN and GRIDMAX.
/// 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
/// Distribute width to columns, performing step 2.4 of table layout from
/// <https://drafts.csswg.org/css-tables/#table-layout-algorithm>.
/// <https://drafts.csswg.org/css-tables/#table-layout-algorithm>.
fn distribute_width_to_columns (
fn distribute_width_to_columns ( target_inline_size : Au , columns : & [ ColumnLayout ] ) -> Vec < Au > {
& self ,
target_inline_size : Au ,
columns : & [ ColumnLayout ] ,
) -> Vec < Au > {
// No need to do anything if there is no column.
// No need to do anything if there is no column.
// Note that tables without rows may still have columns.
// Note that tables without rows may still have columns.
if self . table . size . width . is_zero ( ) {
if columns . is_empty ( ) {
return Vec ::new ( ) ;
return Vec ::new ( ) ;
}
}
@ -872,8 +774,8 @@ impl<'a> TableLayout<'a> {
min_content_percentage_sizing_guess ,
min_content_percentage_sizing_guess ,
min_content_specified_sizing_guess ,
min_content_specified_sizing_guess ,
max_content_sizing_guess ,
max_content_sizing_guess ,
) = if ! column . percentage . is_zero ( ) {
) = if let Some ( percentage ) = column . percentage {
let resolved = target_inline_size . scale_by ( column. percentage. 0 ) ;
let resolved = target_inline_size . scale_by ( percentage. 0 ) ;
let percent_guess = min_content_width . max ( resolved ) ;
let percent_guess = min_content_width . max ( resolved ) ;
( percent_guess , percent_guess , percent_guess )
( percent_guess , percent_guess , percent_guess )
} else if constrained {
} else if constrained {
@ -901,9 +803,11 @@ impl<'a> TableLayout<'a> {
let max_content_sizing_sum = sum ( & max_content_sizing_guesses ) ;
let max_content_sizing_sum = sum ( & max_content_sizing_guesses ) ;
if target_inline_size > = max_content_sizing_sum {
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 ,
& mut max_content_sizing_guesses ,
max_content_sizing_sum ,
max_content_sizing_sum ,
target_inline_size ,
) ;
) ;
return max_content_sizing_guesses ;
return max_content_sizing_guesses ;
}
}
@ -999,26 +903,29 @@ impl<'a> TableLayout<'a> {
/// This is an implementation of *Distributing excess width to columns* from
/// This is an implementation of *Distributing excess width to columns* from
/// <https://drafts.csswg.org/css-tables/#distributing-width-to-columns>.
/// <https://drafts.csswg.org/css-tables/#distributing-width-to-columns>.
fn distribute_extra_width_to_columns ( & self , column_sizes : & mut [ Au ] , column_sizes_sum : Au ) {
fn distribute_extra_width_to_columns (
let all_columns = 0 .. self . table . size . width ;
columns : & [ ColumnLayout ] ,
let extra_inline_size = self . assignable_width - column_sizes_sum ;
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 =
let has_originating_cells =
| column_index : & usize | self . columns [ * column_index ] . has_originating_cells ;
| column_index : & usize | columns [ * column_index ] . has_originating_cells ;
let is_constrained = | column_index : & usize | self . columns [ * column_index ] . constrained ;
let is_constrained = | column_index : & usize | columns [ * column_index ] . constrained ;
let is_unconstrained = | column_index : & usize | ! is_constrained ( column_index ) ;
let is_unconstrained = | column_index : & usize | ! is_constrained ( column_index ) ;
let has_percent_greater_than_zero =
let has_percent_greater_than_zero = | column_index : & usize | {
| column_index : & usize | self . columns [ * column_index ] . percentage . 0 > 0. ;
columns [ * column_index ]
let has_percent_zero = | column_index : & usize | ! has_percent_greater_than_zero ( column_index ) ;
. percentage
let has_max_content = | column_index : & usize | {
. is_some_and ( | percentage | percentage . 0 > 0. )
! self . columns [ * column_index ]
. content_sizes
. max_content
. is_zero ( )
} ;
} ;
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 =
let max_content_sum = | column_index : usize | columns [ column_index ] . content_sizes . max_content ;
| column_index : usize | self . columns [ column_index ] . content_sizes . max_content ;
// > If there are non-constrained columns that have originating cells with intrinsic
// > 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
// > 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 ( ) {
if total_max_content_width ! = Au ::zero ( ) {
for column_index in unconstrained_max_content_columns {
for column_index in unconstrained_max_content_columns {
column_sizes [ column_index ] + = extra_inline_size . scale_by (
column_sizes [ column_index ] + = extra_inline_size . scale_by (
self . columns [ column_index ]
columns [ column_index ] . content_sizes . max_content . to_f32_px ( ) /
. content_sizes
. max_content
. to_f32_px ( ) /
total_max_content_width . 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 ( ) {
if total_max_content_width ! = Au ::zero ( ) {
for column_index in constrained_max_content_columns {
for column_index in constrained_max_content_columns {
column_sizes [ column_index ] + = extra_inline_size . scale_by (
column_sizes [ column_index ] + = extra_inline_size . scale_by (
self . columns [ column_index ]
columns [ column_index ] . content_sizes . max_content . to_f32_px ( ) /
. content_sizes
. max_content
. to_f32_px ( ) /
total_max_content_width . 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 columns_with_percentage = all_columns . clone ( ) . filter ( has_percent_greater_than_zero ) ;
let total_percent = columns_with_percentage
let total_percent = columns_with_percentage
. clone ( )
. clone ( )
. map ( | column_index | self . columns [ column_index ] . percentage . 0 )
. map ( | column_index | columns [ column_index ] . percentage . unwrap_or_default ( ) . 0 )
. sum ::< f32 > ( ) ;
. sum ::< f32 > ( ) ;
if total_percent > 0. {
if total_percent > 0. {
for column_index in columns_with_percentage {
for column_index in columns_with_percentage {
column_sizes [ column_index ] + = extra_inline_size
let column_percentage = columns [ column_index ] . percentage . unwrap_or_default ( ) ;
. scale_by ( self . columns [ column_index ] . percentage . 0 / total_percent ) ;
column_sizes [ column_index ] + =
extra_inline_size . scale_by ( column_percentage . 0 / total_percent ) ;
}
}
return ;
return ;
}
}
@ -1130,8 +1032,7 @@ impl<'a> TableLayout<'a> {
// > Otherwise, the distributed widths of all columns are increased by equal amounts so the
// > Otherwise, the distributed widths of all columns are increased by equal amounts so the
// total increase adds to the excess width.
// total increase adds to the excess width.
let extra_space_for_all_columns =
let extra_space_for_all_columns = extra_inline_size . scale_by ( 1.0 / columns . len ( ) as f32 ) ;
extra_inline_size . scale_by ( 1.0 / self . table . size . width as f32 ) ;
for guess in column_sizes . iter_mut ( ) {
for guess in column_sizes . iter_mut ( ) {
* guess + = extra_space_for_all_columns ;
* 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 ) ;
. get_row_measure_for_row_at_index ( writing_mode , row_index ) ;
row_sizes [ row_index ] . max_assign ( row_measure . content_sizes . min_content ) ;
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 {
for column_index in 0 .. self . table . size . width {
let cell_percentage = self . cell_measures [ row_index ] [ column_index ]
let cell_percentage = self . cell_measures [ row_index ] [ column_index ]
. block
. block
. percentage
. percentage
. unwrap_or_default ( )
. 0 ;
. 0 ;
percentage = percentage . max ( cell_percentage ) ;
percentage = percentage . max ( cell_percentage ) ;
@ -1821,7 +1723,7 @@ impl<'a> TableLayout<'a> {
containing_block_for_children : & ContainingBlock ,
containing_block_for_children : & ContainingBlock ,
) -> BoxFragment {
) -> BoxFragment {
self . distributed_column_widths =
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 (
self . layout_cells_in_row (
layout_context ,
layout_context ,
containing_block_for_children ,
containing_block_for_children ,
@ -2650,8 +2552,13 @@ impl Table {
None = > return CellOrTrackMeasure ::zero ( ) ,
None = > return CellOrTrackMeasure ::zero ( ) ,
} ;
} ;
let ( size , min_size , max_size , _ , percentage_contribution ) =
let CellOrColumnOuterSizes {
get_outer_sizes_for_measurement ( & column . style , writing_mode , & LogicalVec2 ::zero ( ) ) ;
preferred : preferred_size ,
min : min_size ,
max : max_size ,
percentage : percentage_size ,
..
} = CellOrColumnOuterSizes ::new ( & column . style , writing_mode , & Default ::default ( ) ) ;
CellOrTrackMeasure {
CellOrTrackMeasure {
content_sizes : ContentSizes {
content_sizes : ContentSizes {
@ -2663,9 +2570,11 @@ impl Table {
// > The outer max-content width of a table-column or table-column-group is
// > The outer max-content width of a table-column or table-column-group is
// > max(min-width, min(max-width, width)).
// > max(min-width, min(max-width, width)).
// This matches Gecko, but Blink and WebKit ignore max_size.
// 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 (
fn get_size_percentage_contribution (
size : & LogicalVec2 < Size < ComputedLengthPercentage > > ,
size : & LogicalVec2 < Size < ComputedLengthPercentage > > ,
max_size : & LogicalVec2 < Size < ComputedLengthPercentage > > ,
max_size : & LogicalVec2 < Size < ComputedLengthPercentage > > ,
) -> LogicalVec2 < Percentage > {
) -> LogicalVec2 < Option < Percentage > > {
// From <https://drafts.csswg.org/css-tables/#percentage-contribution>
// From <https://drafts.csswg.org/css-tables/#percentage-contribution>
// > The percentage contribution of a table cell, column, or column group is defined
// > 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
// > 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 < ComputedLengthPercentage > , max_size : & Size < ComputedLengthPercentage > | {
| size : & Size < ComputedLengthPercentage > , max_size : & Size < ComputedLengthPercentage > | {
let size_percentage = size
let size_percentage = size
. to_numeric ( )
. to_numeric ( )
. and_then ( | length_percentage | length_percentage . to_percentage ( ) )
. and_then ( | length_percentage | length_percentage . to_percentage ( ) ) ;
. unwrap_or ( Percentage ( 0. ) ) ;
let max_size_percentage = max_size
let max_size_percentage = max_size
. to_numeric ( )
. to_numeric ( )
. and_then ( | length_percentage | length_percentage . to_percentage ( ) )
. and_then ( | length_percentage | length_percentage . to_percentage ( ) ) ;
. unwrap_or ( Percentage ( f32 ::INFINITY ) ) ;
max_two_optional_percentages ( size_percentage , max_size_percentage )
Percentage ( size_percentage . 0. min ( max_size_percentage . 0 ) )
} ;
} ;
LogicalVec2 {
LogicalVec2 {
@ -2937,43 +2844,60 @@ fn get_size_percentage_contribution(
}
}
}
}
fn get_outer_sizes_for_measurement (
struct CellOrColumnOuterSizes {
style : & Arc < ComputedValues > ,
min : LogicalVec2 < Au > ,
writing_mode : WritingMode ,
preferred : LogicalVec2 < Au > ,
padding_border_sums : & LogicalVec2 < Au > ,
max : LogicalVec2 < Option < Au > > ,
) -> (
percentage : LogicalVec2 < Option < Percentage > > ,
LogicalVec2 < Au > ,
inline_preferred_size_is_auto : bool ,
LogicalVec2 < Au > ,
}
LogicalVec2 < Au > ,
bool ,
LogicalVec2 < Percentage > ,
) {
let box_sizing = style . get_position ( ) . box_sizing ;
let outer_size = | size : LogicalVec2 < Au > | 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 < ComputedLengthPercentage > | {
// Note that measures treat all size values other than <length>
// 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 ) ;
impl CellOrColumnOuterSizes {
let min_size = style . min_box_size ( writing_mode ) ;
fn new (
let max_size = style . max_box_size ( writing_mode ) ;
style : & Arc < ComputedValues > ,
(
writing_mode : WritingMode ,
outer_size ( size . map ( | v | get_size_for_axis ( v ) . unwrap_or_else ( Au ::zero ) ) ) ,
padding_border_sums : & LogicalVec2 < Au > ,
outer_size ( min_size . map ( | v | get_size_for_axis ( v ) . unwrap_or_else ( Au ::zero ) ) ) ,
) -> Self {
outer_size ( max_size . map ( | v | get_size_for_axis ( v ) . unwrap_or ( MAX_AU ) ) ) ,
let box_sizing = style . get_position ( ) . box_sizing ;
! size . inline . is_numeric ( ) ,
let outer_size = | size : LogicalVec2 < Au > | match box_sizing {
get_size_percentage_contribution ( & size , & max_size ) ,
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 < Option < Au > > | 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 < ComputedLengthPercentage > | {
// Note that measures treat all size values other than <length>
// 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 > {
struct RowspanToDistribute < ' a > {
@ -2991,3 +2915,28 @@ impl RowspanToDistribute<'_> {
other . coordinates . y > self . coordinates . y & & other . range ( ) . end < self . range ( ) . end
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 < Percentage > ,
}
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 < usize > {
self . starting_column .. self . starting_column + self . span
}
}