layout/table/
layout.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use core::cmp::Ordering;
6use std::mem;
7use std::ops::Range;
8
9use app_units::Au;
10use atomic_refcell::AtomicRef;
11use log::warn;
12use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
13use servo_arc::Arc;
14use style::Zero;
15use style::computed_values::border_collapse::T as BorderCollapse;
16use style::computed_values::box_sizing::T as BoxSizing;
17use style::computed_values::caption_side::T as CaptionSide;
18use style::computed_values::empty_cells::T as EmptyCells;
19use style::computed_values::position::T as Position;
20use style::computed_values::table_layout::T as TableLayoutMode;
21use style::computed_values::visibility::T as Visibility;
22use style::properties::ComputedValues;
23use style::values::computed::{
24    BorderStyle, LengthPercentage as ComputedLengthPercentage, Percentage,
25};
26use style::values::generics::box_::{GenericVerticalAlign as VerticalAlign, VerticalAlignKeyword};
27
28use super::{
29    ArcRefCell, CollapsedBorder, CollapsedBorderLine, SpecificTableGridInfo, Table, TableCaption,
30    TableLayoutStyle, TableSlot, TableSlotCell, TableSlotCoordinates, TableTrack, TableTrackGroup,
31};
32use crate::context::LayoutContext;
33use crate::formatting_contexts::Baselines;
34use crate::fragment_tree::{
35    BoxFragment, CollapsedBlockMargins, ExtraBackground, Fragment, FragmentFlags,
36    PositioningFragment, SpecificLayoutInfo,
37};
38use crate::geom::{
39    LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, PhysicalRect,
40    PhysicalSides, PhysicalVec, ToLogical, ToLogicalWithContainingBlock,
41};
42use crate::layout_box_base::CacheableLayoutResult;
43use crate::positioned::{PositioningContext, PositioningContextLength, relative_adjustement};
44use crate::sizing::{
45    ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, Size, SizeConstraint,
46};
47use crate::style_ext::{
48    BorderStyleColor, Clamp, ComputedValuesExt, LayoutStyle, PaddingBorderMargin,
49};
50use crate::{
51    ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock, WritingMode,
52};
53
54/// A result of a final or speculative layout of a single cell in
55/// the table. Note that this is only done for slots that are not
56/// covered by spans or empty.
57struct CellLayout {
58    layout: CacheableLayoutResult,
59    padding: LogicalSides<Au>,
60    border: LogicalSides<Au>,
61    positioning_context: PositioningContext,
62}
63
64impl CellLayout {
65    fn ascent(&self) -> Au {
66        self.layout
67            .baselines
68            .first
69            .unwrap_or(self.layout.content_block_size)
70    }
71
72    /// The block size of this laid out cell including its border and padding.
73    fn outer_block_size(&self) -> Au {
74        self.layout.content_block_size + self.border.block_sum() + self.padding.block_sum()
75    }
76
77    /// Whether the cell has no in-flow or out-of-flow contents, other than collapsed whitespace.
78    /// Note this logic differs from 'empty-cells', which counts abspos contents as empty.
79    fn is_empty(&self) -> bool {
80        self.layout.fragments.is_empty()
81    }
82
83    /// Whether the cell is considered empty for the purpose of the 'empty-cells' property.
84    fn is_empty_for_empty_cells(&self) -> bool {
85        self.layout
86            .fragments
87            .iter()
88            .all(|fragment| matches!(fragment, Fragment::AbsoluteOrFixedPositioned(_)))
89    }
90}
91
92/// Information stored during the layout of rows.
93#[derive(Clone, Debug, Default)]
94struct RowLayout {
95    constrained: bool,
96    has_cell_with_span_greater_than_one: bool,
97    percent: Percentage,
98}
99
100/// Information stored during the layout of columns.
101#[derive(Clone, Debug, Default)]
102struct ColumnLayout {
103    constrained: bool,
104    has_originating_cells: bool,
105    content_sizes: ContentSizes,
106    percentage: Option<Percentage>,
107}
108
109fn max_two_optional_percentages(
110    a: Option<Percentage>,
111    b: Option<Percentage>,
112) -> Option<Percentage> {
113    match (a, b) {
114        (Some(a), Some(b)) => Some(Percentage(a.0.max(b.0))),
115        _ => a.or(b),
116    }
117}
118
119impl ColumnLayout {
120    fn incorporate_cell_measure(&mut self, cell_measure: &CellOrTrackMeasure) {
121        self.content_sizes.max_assign(cell_measure.content_sizes);
122        self.percentage = max_two_optional_percentages(self.percentage, cell_measure.percentage);
123    }
124}
125
126impl CollapsedBorder {
127    fn new(style_color: BorderStyleColor, width: Au) -> Self {
128        Self { style_color, width }
129    }
130
131    fn from_layout_style(
132        layout_style: &LayoutStyle,
133        writing_mode: WritingMode,
134    ) -> LogicalSides<Self> {
135        let border_style_color = layout_style.style().border_style_color(writing_mode);
136        let border_width = layout_style.border_width(writing_mode);
137        LogicalSides {
138            inline_start: Self::new(border_style_color.inline_start, border_width.inline_start),
139            inline_end: Self::new(border_style_color.inline_end, border_width.inline_end),
140            block_start: Self::new(border_style_color.block_start, border_width.block_start),
141            block_end: Self::new(border_style_color.block_end, border_width.block_end),
142        }
143    }
144
145    fn max_assign(&mut self, other: &Self) {
146        if *self < *other {
147            *self = other.clone();
148        }
149    }
150
151    fn max_assign_to_slice(&self, slice: &mut [CollapsedBorder]) {
152        for collapsed_border in slice {
153            collapsed_border.max_assign(self)
154        }
155    }
156
157    fn hide(&mut self) {
158        self.style_color = BorderStyleColor::hidden();
159        self.width = Au::zero();
160    }
161}
162
163/// <https://drafts.csswg.org/css-tables/#border-specificity>
164/// > Given two borders styles, the border style having the most specificity is the border style which…
165/// > 1. … has the value "hidden" as border-style, if only one does
166/// > 2. … has the biggest border-width, once converted into css pixels
167/// > 3. … has the border-style which comes first in the following list:
168/// >    double, solid, dashed, dotted, ridge, outset, groove, inset, none
169impl PartialOrd for CollapsedBorder {
170    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
171        let is_hidden = |border: &Self| border.style_color.style == BorderStyle::Hidden;
172        let style_specificity = |border: &Self| match border.style_color.style {
173            BorderStyle::None => 0,
174            BorderStyle::Inset => 1,
175            BorderStyle::Groove => 2,
176            BorderStyle::Outset => 3,
177            BorderStyle::Ridge => 4,
178            BorderStyle::Dotted => 5,
179            BorderStyle::Dashed => 6,
180            BorderStyle::Solid => 7,
181            BorderStyle::Double => 8,
182            BorderStyle::Hidden => 9,
183        };
184        let candidate = (is_hidden(self).cmp(&is_hidden(other)))
185            .then_with(|| self.width.cmp(&other.width))
186            .then_with(|| style_specificity(self).cmp(&style_specificity(other)));
187        if !candidate.is_eq() || self.style_color.color == other.style_color.color {
188            Some(candidate)
189        } else {
190            None
191        }
192    }
193}
194
195impl Eq for CollapsedBorder {}
196
197type CollapsedBorders = LogicalVec2<Vec<CollapsedBorderLine>>;
198
199/// A helper struct that performs the layout of the box tree version
200/// of a table into the fragment tree version. This implements
201/// <https://drafts.csswg.org/css-tables/#table-layout-algorithm>
202pub(crate) struct TableLayout<'a> {
203    table: &'a Table,
204    pbm: PaddingBorderMargin,
205    rows: Vec<RowLayout>,
206    columns: Vec<ColumnLayout>,
207    cell_measures: Vec<Vec<LogicalVec2<CellOrTrackMeasure>>>,
208    /// The calculated width of the table, including space for the grid and also for any
209    /// captions.
210    table_width: Au,
211    /// The table width minus the total horizontal border spacing (if any). This is the
212    /// width that we will be able to allocate to the columns.
213    assignable_width: Au,
214    final_table_height: Au,
215    distributed_column_widths: Vec<Au>,
216    row_sizes: Vec<Au>,
217    /// The accumulated baseline of each row, relative to the top of the row.
218    row_baselines: Vec<Au>,
219    cells_laid_out: Vec<Vec<Option<CellLayout>>>,
220    basis_for_cell_padding_percentage: Au,
221    /// Information about collapsed borders.
222    collapsed_borders: Option<CollapsedBorders>,
223    is_in_fixed_mode: bool,
224}
225
226#[derive(Clone, Debug)]
227struct CellOrTrackMeasure {
228    content_sizes: ContentSizes,
229    percentage: Option<Percentage>,
230}
231
232impl Zero for CellOrTrackMeasure {
233    fn zero() -> Self {
234        Self {
235            content_sizes: ContentSizes::zero(),
236            percentage: None,
237        }
238    }
239
240    fn is_zero(&self) -> bool {
241        self.content_sizes.is_zero() && self.percentage.is_none()
242    }
243}
244
245impl<'a> TableLayout<'a> {
246    fn new(table: &'a Table) -> TableLayout<'a> {
247        // The CSSWG resolved that only `inline-size: auto` can prevent fixed table mode.
248        // <https://github.com/w3c/csswg-drafts/issues/10937#issuecomment-2669150397>
249        let style = &table.style;
250        let is_in_fixed_mode = style.get_table().table_layout == TableLayoutMode::Fixed &&
251            !style.box_size(style.writing_mode).inline.is_initial();
252        Self {
253            table,
254            pbm: PaddingBorderMargin::zero(),
255            rows: Vec::new(),
256            columns: Vec::new(),
257            cell_measures: Vec::new(),
258            table_width: Au::zero(),
259            assignable_width: Au::zero(),
260            final_table_height: Au::zero(),
261            distributed_column_widths: Vec::new(),
262            row_sizes: Vec::new(),
263            row_baselines: Vec::new(),
264            cells_laid_out: Vec::new(),
265            basis_for_cell_padding_percentage: Au::zero(),
266            collapsed_borders: None,
267            is_in_fixed_mode,
268        }
269    }
270
271    /// This is an implementation of *Computing Cell Measures* from
272    /// <https://drafts.csswg.org/css-tables/#computing-cell-measures>.
273    pub(crate) fn compute_cell_measures(
274        &mut self,
275        layout_context: &LayoutContext,
276        writing_mode: WritingMode,
277    ) {
278        let row_measures = vec![LogicalVec2::zero(); self.table.size.width];
279        self.cell_measures = vec![row_measures; self.table.size.height];
280
281        for row_index in 0..self.table.size.height {
282            for column_index in 0..self.table.size.width {
283                let cell = match self.table.slots[row_index][column_index] {
284                    TableSlot::Cell(ref cell) => cell,
285                    _ => continue,
286                }
287                .borrow();
288
289                let layout_style = cell.layout_style();
290                let padding = layout_style
291                    .padding(writing_mode)
292                    .percentages_relative_to(Au::zero());
293                let border = self
294                    .get_collapsed_border_widths_for_area(LogicalSides {
295                        inline_start: column_index,
296                        inline_end: column_index + cell.colspan,
297                        block_start: row_index,
298                        block_end: row_index + cell.rowspan,
299                    })
300                    .unwrap_or_else(|| layout_style.border_width(writing_mode));
301
302                let padding_border_sums = LogicalVec2 {
303                    inline: padding.inline_sum() + border.inline_sum(),
304                    block: padding.block_sum() + border.block_sum(),
305                };
306
307                let CellOrColumnOuterSizes {
308                    preferred: preferred_size,
309                    min: min_size,
310                    max: max_size,
311                    percentage: percentage_size,
312                } = CellOrColumnOuterSizes::new(
313                    &cell.base.style,
314                    writing_mode,
315                    &padding_border_sums,
316                    self.is_in_fixed_mode,
317                );
318
319                // <https://drafts.csswg.org/css-tables/#in-fixed-mode>
320                // > When a table-root is laid out in fixed mode, the content of its table-cells is ignored
321                // > for the purpose of width computation, the aggregation algorithm for column sizing considers
322                // > only table-cells belonging to the first row track
323                let inline_measure = if self.is_in_fixed_mode {
324                    if row_index > 0 {
325                        CellOrTrackMeasure::zero()
326                    } else {
327                        CellOrTrackMeasure {
328                            content_sizes: preferred_size.inline.into(),
329                            percentage: percentage_size.inline,
330                        }
331                    }
332                } else {
333                    let inline_content_sizes = cell.inline_content_sizes(layout_context) +
334                        padding_border_sums.inline.into();
335                    assert!(
336                        inline_content_sizes.max_content >= inline_content_sizes.min_content,
337                        "the max-content size should never be smaller than the min-content size"
338                    );
339
340                    // These formulas differ from the spec, but seem to match Gecko and Blink.
341                    let outer_min_content_width = inline_content_sizes
342                        .min_content
343                        .clamp_between_extremums(min_size.inline, max_size.inline);
344                    let outer_max_content_width = if self.columns[column_index].constrained {
345                        inline_content_sizes
346                            .min_content
347                            .max(preferred_size.inline)
348                            .clamp_between_extremums(min_size.inline, max_size.inline)
349                    } else {
350                        inline_content_sizes
351                            .max_content
352                            .max(preferred_size.inline)
353                            .clamp_between_extremums(min_size.inline, max_size.inline)
354                    };
355                    assert!(outer_min_content_width <= outer_max_content_width);
356
357                    CellOrTrackMeasure {
358                        content_sizes: ContentSizes {
359                            min_content: outer_min_content_width,
360                            max_content: outer_max_content_width,
361                        },
362                        percentage: percentage_size.inline,
363                    }
364                };
365
366                // This measure doesn't take into account the `min-content` and `max-content` sizes.
367                // These sizes are incorporated after the first row layout pass, when the block size
368                // of the layout is known.
369                let block_measure = CellOrTrackMeasure {
370                    content_sizes: preferred_size.block.into(),
371                    percentage: percentage_size.block,
372                };
373
374                self.cell_measures[row_index][column_index] = LogicalVec2 {
375                    inline: inline_measure,
376                    block: block_measure,
377                };
378            }
379        }
380    }
381
382    /// Compute the constrainedness of every column in the table.
383    ///
384    /// > A column is constrained if its corresponding table-column-group (if any), its
385    /// > corresponding table-column (if any), or any of the cells spanning only that
386    /// > column has a computed width that is not "auto", and is not a percentage.
387    fn compute_track_constrainedness_and_has_originating_cells(
388        &mut self,
389        writing_mode: WritingMode,
390    ) {
391        self.rows = vec![RowLayout::default(); self.table.size.height];
392        self.columns = vec![ColumnLayout::default(); self.table.size.width];
393
394        let is_length = |size: &Size<ComputedLengthPercentage>| {
395            size.to_numeric().is_some_and(|size| !size.has_percentage())
396        };
397
398        for column_index in 0..self.table.size.width {
399            if let Some(column) = self.table.columns.get(column_index) {
400                let column = column.borrow();
401                if is_length(&column.base.style.box_size(writing_mode).inline) {
402                    self.columns[column_index].constrained = true;
403                    continue;
404                }
405                if let Some(column_group_index) = column.group_index {
406                    let column_group = self.table.column_groups[column_group_index].borrow();
407                    if is_length(&column_group.base.style.box_size(writing_mode).inline) {
408                        self.columns[column_index].constrained = true;
409                        continue;
410                    }
411                }
412            }
413        }
414
415        for row_index in 0..self.table.size.height {
416            if let Some(row) = self.table.rows.get(row_index) {
417                let row = row.borrow();
418                if is_length(&row.base.style.box_size(writing_mode).block) {
419                    self.rows[row_index].constrained = true;
420                    continue;
421                }
422                if let Some(row_group_index) = row.group_index {
423                    let row_group = self.table.row_groups[row_group_index].borrow();
424                    if is_length(&row_group.base.style.box_size(writing_mode).block) {
425                        self.rows[row_index].constrained = true;
426                        continue;
427                    }
428                }
429            }
430        }
431
432        for column_index in 0..self.table.size.width {
433            for row_index in 0..self.table.size.height {
434                let coords = TableSlotCoordinates::new(column_index, row_index);
435                let cell_constrained = match self.table.resolve_first_cell(coords) {
436                    Some(cell) if cell.colspan == 1 => {
437                        cell.base.style.box_size(writing_mode).map(is_length)
438                    },
439                    _ => LogicalVec2::default(),
440                };
441
442                let rowspan_greater_than_1 = match self.table.slots[row_index][column_index] {
443                    TableSlot::Cell(ref cell) => cell.borrow().rowspan > 1,
444                    _ => false,
445                };
446
447                self.rows[row_index].has_cell_with_span_greater_than_one |= rowspan_greater_than_1;
448                self.rows[row_index].constrained |= cell_constrained.block;
449
450                let has_originating_cell =
451                    matches!(self.table.get_slot(coords), Some(TableSlot::Cell(_)));
452                self.columns[column_index].has_originating_cells |= has_originating_cell;
453                self.columns[column_index].constrained |= cell_constrained.inline;
454            }
455        }
456    }
457
458    /// This is an implementation of *Computing Column Measures* from
459    /// <https://drafts.csswg.org/css-tables/#computing-column-measures>.
460    fn compute_column_measures(&mut self, writing_mode: WritingMode) {
461        // Compute the column measures only taking into account cells with colspan == 1.
462        // This is the base case that will be used to iteratively account for cells with
463        // larger colspans afterward.
464        //
465        // > min-content width of a column based on cells of span up to 1
466        // >     The largest of:
467        // >         - the width specified for the column:
468        // >               - the outer min-content width of its corresponding table-column,
469        // >                 if any (and not auto)
470        // >               - the outer min-content width of its corresponding table-column-group, if any
471        // >               - or 0, if there is none
472        // >         - the outer min-content width of each cell that spans the column whose colSpan
473        // >           is 1 (or just the one in the first row in fixed mode) or 0 if there is none
474        // >
475        // > max-content width of a column based on cells of span up to 1
476        // >     The largest of:
477        // >         - the outer max-content width of its corresponding
478        // >           table-column-group, if any
479        // >         - the outer max-content width of its corresponding table-column, if any
480        // >         - the outer max-content width of each cell that spans the column
481        // >           whose colSpan is 1 (or just the one in the first row if in fixed mode) or 0
482        // >           if there is no such cell
483        // >
484        // > intrinsic percentage width of a column based on cells of span up to 1
485        // >     The largest of the percentage contributions of each cell that spans the column whose colSpan is
486        // >     1, of its corresponding table-column (if any), and of its corresponding table-column-group (if
487        // >     any)
488        //
489        // TODO: Take into account `table-column` and `table-column-group` lengths.
490        // TODO: Take into account changes to this computation for fixed table layout.
491        let mut colspan_cell_constraints = Vec::new();
492        for column_index in 0..self.table.size.width {
493            let column = &mut self.columns[column_index];
494
495            let column_measure = self.table.get_column_measure_for_column_at_index(
496                writing_mode,
497                column_index,
498                self.is_in_fixed_mode,
499            );
500            column.content_sizes = column_measure.content_sizes;
501            column.percentage = column_measure.percentage;
502
503            for row_index in 0..self.table.size.height {
504                let coords = TableSlotCoordinates::new(column_index, row_index);
505                let cell_measure = &self.cell_measures[row_index][column_index].inline;
506
507                let cell = match self.table.get_slot(coords) {
508                    Some(TableSlot::Cell(cell)) => cell,
509                    _ => continue,
510                }
511                .borrow();
512
513                if cell.colspan != 1 {
514                    colspan_cell_constraints.push(ColspanToDistribute {
515                        starting_column: column_index,
516                        span: cell.colspan,
517                        content_sizes: cell_measure.content_sizes,
518                        percentage: cell_measure.percentage,
519                    });
520                    continue;
521                }
522
523                // This takes the max of `min_content`, `max_content`, and
524                // intrinsic percentage width as described above.
525                column.incorporate_cell_measure(cell_measure);
526            }
527        }
528
529        // Sort the colspanned cell constraints by their span and starting column.
530        colspan_cell_constraints.sort_by(ColspanToDistribute::comparison_for_sort);
531
532        // Distribute constraints from cells with colspan != 1 to their component columns.
533        self.distribute_colspanned_cells_to_columns(colspan_cell_constraints);
534
535        // > intrinsic percentage width of a column:
536        // > the smaller of:
537        // >   * the intrinsic percentage width of the column based on cells of span up to N,
538        // >     where N is the number of columns in the table
539        // >   * 100% minus the sum of the intrinsic percentage width of all prior columns in
540        // >     the table (further left when direction is "ltr" (right for "rtl"))
541        let mut total_intrinsic_percentage_width = 0.;
542        for column in self.columns.iter_mut() {
543            if let Some(ref mut percentage) = column.percentage {
544                let final_intrinsic_percentage_width =
545                    percentage.0.min(1. - total_intrinsic_percentage_width);
546                total_intrinsic_percentage_width += final_intrinsic_percentage_width;
547                *percentage = Percentage(final_intrinsic_percentage_width);
548            }
549        }
550    }
551
552    fn distribute_colspanned_cells_to_columns(
553        &mut self,
554        colspan_cell_constraints: Vec<ColspanToDistribute>,
555    ) {
556        for colspan_cell_constraints in colspan_cell_constraints {
557            self.distribute_colspanned_cell_to_columns(colspan_cell_constraints);
558        }
559    }
560
561    /// Distribute the inline size from a cell with colspan != 1 to the columns that it spans.
562    /// This is heavily inspired by the approach that Chromium takes in redistributing colspan
563    /// cells' inline size to columns (`DistributeColspanCellToColumnsAuto` in
564    /// `blink/renderer/core/layout/table/table_layout_utils.cc`).
565    fn distribute_colspanned_cell_to_columns(
566        &mut self,
567        colspan_cell_constraints: ColspanToDistribute,
568    ) {
569        let border_spacing = self.table.border_spacing().inline;
570        let column_range = colspan_cell_constraints.range();
571        let column_count = column_range.len();
572        let total_border_spacing =
573            border_spacing.scale_by((colspan_cell_constraints.span - 1) as f32);
574
575        let mut percent_columns_count = 0;
576        let mut columns_percent_sum = 0.;
577        let mut columns_non_percent_max_inline_size_sum = Au::zero();
578        for column in self.columns[column_range.clone()].iter() {
579            if let Some(percentage) = column.percentage {
580                percent_columns_count += 1;
581                columns_percent_sum += percentage.0;
582            } else {
583                columns_non_percent_max_inline_size_sum += column.content_sizes.max_content;
584            }
585        }
586
587        let colspan_percentage = colspan_cell_constraints.percentage.unwrap_or_default();
588        let surplus_percent = colspan_percentage.0 - columns_percent_sum;
589        if surplus_percent > 0. && column_count > percent_columns_count {
590            for column in self.columns[column_range.clone()].iter_mut() {
591                if column.percentage.is_some() {
592                    continue;
593                }
594
595                let ratio = if columns_non_percent_max_inline_size_sum.is_zero() {
596                    1. / ((column_count - percent_columns_count) as f32)
597                } else {
598                    column.content_sizes.max_content.to_f32_px() /
599                        columns_non_percent_max_inline_size_sum.to_f32_px()
600                };
601                column.percentage = Some(Percentage(surplus_percent * ratio));
602            }
603        }
604
605        let colspan_cell_min_size = (colspan_cell_constraints.content_sizes.min_content -
606            total_border_spacing)
607            .max(Au::zero());
608        let distributed_minimum = Self::distribute_width_to_columns(
609            colspan_cell_min_size,
610            &self.columns[column_range.clone()],
611        );
612        {
613            let column_span = &mut self.columns[colspan_cell_constraints.range()];
614            for (column, minimum_size) in column_span.iter_mut().zip(distributed_minimum) {
615                column.content_sizes.min_content.max_assign(minimum_size);
616            }
617        }
618
619        let colspan_cell_max_size = (colspan_cell_constraints.content_sizes.max_content -
620            total_border_spacing)
621            .max(Au::zero());
622        let distributed_maximum = Self::distribute_width_to_columns(
623            colspan_cell_max_size,
624            &self.columns[colspan_cell_constraints.range()],
625        );
626        {
627            let column_span = &mut self.columns[colspan_cell_constraints.range()];
628            for (column, maximum_size) in column_span.iter_mut().zip(distributed_maximum) {
629                column
630                    .content_sizes
631                    .max_content
632                    .max_assign(maximum_size.max(column.content_sizes.min_content));
633            }
634        }
635    }
636
637    fn compute_measures(&mut self, layout_context: &LayoutContext, writing_mode: WritingMode) {
638        self.compute_track_constrainedness_and_has_originating_cells(writing_mode);
639        self.compute_cell_measures(layout_context, writing_mode);
640        self.compute_column_measures(writing_mode);
641    }
642
643    /// Compute the GRIDMIN and GRIDMAX.
644    fn compute_grid_min_max(&self) -> ContentSizes {
645        // https://drafts.csswg.org/css-tables/#gridmin:
646        // > The row/column-grid width minimum (GRIDMIN) width is the sum of the min-content width of
647        // > all the columns plus cell spacing or borders.
648        // https://drafts.csswg.org/css-tables/#gridmax:
649        // > The row/column-grid width maximum (GRIDMAX) width is the sum of the max-content width of
650        // > all the columns plus cell spacing or borders.
651        //
652        // The specification doesn't say what to do with columns with percentages, so we follow the
653        // approach that LayoutNG takes here. We try to figure out the size contribution
654        // of the percentage columns, by working backward to find the calculated
655        // percentage of non-percent columns and using that to calculate the size of the
656        // percent columns.
657        let mut largest_percentage_column_max_size = Au::zero();
658        let mut percent_sum = 0.;
659        let mut non_percent_columns_max_sum = Au::zero();
660        let mut grid_min_max = ContentSizes::zero();
661        for column in self.columns.iter() {
662            match column.percentage {
663                Some(percentage) if !percentage.is_zero() => {
664                    largest_percentage_column_max_size.max_assign(
665                        column
666                            .content_sizes
667                            .max_content
668                            .scale_by(1.0 / percentage.0),
669                    );
670                    percent_sum += percentage.0;
671                },
672                _ => {
673                    non_percent_columns_max_sum += column.content_sizes.max_content;
674                },
675            }
676
677            grid_min_max += column.content_sizes;
678        }
679
680        grid_min_max
681            .max_content
682            .max_assign(largest_percentage_column_max_size);
683
684        // Do not take into account percentage of columns when this table is a descendant
685        // of a flex, grid, or table container. These modes with percentage columns can
686        // cause inline width to become infinitely wide.
687        if !percent_sum.is_zero() &&
688            self.table
689                .percentage_columns_allowed_for_inline_content_sizes
690        {
691            let total_inline_size =
692                non_percent_columns_max_sum.scale_by(1.0 / (1.0 - percent_sum.min(1.0)));
693            grid_min_max.max_content.max_assign(total_inline_size);
694        }
695
696        assert!(
697            grid_min_max.min_content <= grid_min_max.max_content,
698            "GRIDMAX should never be smaller than GRIDMIN {:?}",
699            grid_min_max
700        );
701
702        let inline_border_spacing = self.table.total_border_spacing().inline;
703        grid_min_max.min_content += inline_border_spacing;
704        grid_min_max.max_content += inline_border_spacing;
705        grid_min_max
706    }
707
708    /// Compute CAPMIN: <https://drafts.csswg.org/css-tables/#capmin>
709    fn compute_caption_minimum_inline_size(&self, layout_context: &LayoutContext) -> Au {
710        let containing_block = IndefiniteContainingBlock {
711            size: LogicalVec2::default(),
712            writing_mode: self.table.style.writing_mode,
713        };
714        self.table
715            .captions
716            .iter()
717            .map(|caption| {
718                caption
719                    .borrow()
720                    .context
721                    .outer_inline_content_sizes(
722                        layout_context,
723                        &containing_block,
724                        &LogicalVec2::zero(),
725                        false, /* auto_block_size_stretches_to_containing_block */
726                    )
727                    .sizes
728                    .min_content
729            })
730            .max()
731            .unwrap_or_default()
732    }
733
734    fn compute_table_width(&mut self, containing_block_for_children: &ContainingBlock) {
735        // This assumes that the parent formatting context computed the correct inline size
736        // of the table, by enforcing its min-content size as a minimum.
737        // This should be roughly equivalent to what the spec calls "used width of a table".
738        // https://drafts.csswg.org/css-tables/#used-width-of-table
739        self.table_width = containing_block_for_children.size.inline;
740
741        // > The assignable table width is the used width of the table minus the total horizontal
742        // > border spacing (if any). This is the width that we will be able to allocate to the
743        // > columns.
744        self.assignable_width = self.table_width - self.table.total_border_spacing().inline;
745
746        // This is the amount that we will use to resolve percentages in the padding of cells.
747        // It matches what Gecko and Blink do, though they disagree when there is a big caption.
748        self.basis_for_cell_padding_percentage =
749            self.table_width - self.table.border_spacing().inline * 2;
750    }
751
752    /// Distribute width to columns, performing step 2.4 of table layout from
753    /// <https://drafts.csswg.org/css-tables/#table-layout-algorithm>.
754    fn distribute_width_to_columns(target_inline_size: Au, columns: &[ColumnLayout]) -> Vec<Au> {
755        // No need to do anything if there is no column.
756        // Note that tables without rows may still have columns.
757        if columns.is_empty() {
758            return Vec::new();
759        }
760
761        // > First, each column of the table is assigned a sizing type:
762        // >  * percent-column: a column whose any constraint is defined to use a percentage only
763        // >                    (with a value different from 0%)
764        // >  * pixel-column: column whose any constraint is defined to use a defined length only
765        // >                  (and is not a percent-column)
766        // >  * auto-column: any other column
767        // >
768        // > Then, valid sizing methods are to be assigned to the columns by sizing type, yielding
769        // > the following sizing-guesses:
770        // >
771        // > * The min-content sizing-guess is the set of column width assignments where
772        // >   each column is assigned its min-content width.
773        // > * The min-content-percentage sizing-guess is the set of column width assignments where:
774        // >       * each percent-column is assigned the larger of:
775        // >           * its intrinsic percentage width times the assignable width and
776        // >           * its min-content width.
777        // >       * all other columns are assigned their min-content width.
778        // > * The min-content-specified sizing-guess is the set of column width assignments where:
779        // >       * each percent-column is assigned the larger of:
780        // >           * its intrinsic percentage width times the assignable width and
781        // >           * its min-content width
782        // >       * any other column that is constrained is assigned its max-content width
783        // >       * all other columns are assigned their min-content width.
784        // > * The max-content sizing-guess is the set of column width assignments where:
785        // >       * each percent-column is assigned the larger of:
786        // >           * its intrinsic percentage width times the assignable width and
787        // >           * its min-content width
788        // >       * all other columns are assigned their max-content width.
789        let mut min_content_sizing_guesses = Vec::new();
790        let mut min_content_percentage_sizing_guesses = Vec::new();
791        let mut min_content_specified_sizing_guesses = Vec::new();
792        let mut max_content_sizing_guesses = Vec::new();
793
794        for column in columns {
795            let min_content_width = column.content_sizes.min_content;
796            let max_content_width = column.content_sizes.max_content;
797            let constrained = column.constrained;
798
799            let (
800                min_content_percentage_sizing_guess,
801                min_content_specified_sizing_guess,
802                max_content_sizing_guess,
803            ) = if let Some(percentage) = column.percentage {
804                let resolved = target_inline_size.scale_by(percentage.0);
805                let percent_guess = min_content_width.max(resolved);
806                (percent_guess, percent_guess, percent_guess)
807            } else if constrained {
808                (min_content_width, max_content_width, max_content_width)
809            } else {
810                (min_content_width, min_content_width, max_content_width)
811            };
812
813            min_content_sizing_guesses.push(min_content_width);
814            min_content_percentage_sizing_guesses.push(min_content_percentage_sizing_guess);
815            min_content_specified_sizing_guesses.push(min_content_specified_sizing_guess);
816            max_content_sizing_guesses.push(max_content_sizing_guess);
817        }
818
819        // > If the assignable table width is less than or equal to the max-content sizing-guess, the
820        // > used widths of the columns must be the linear combination (with weights adding to 1) of
821        // > the two consecutive sizing-guesses whose width sums bound the available width.
822        //
823        // > Otherwise, the used widths of the columns are the result of starting from the max-content
824        // > sizing-guess and distributing the excess width to the columns of the table according to
825        // > the rules for distributing excess width to columns (for used width).
826        fn sum(guesses: &[Au]) -> Au {
827            guesses.iter().fold(Au::zero(), |sum, guess| sum + *guess)
828        }
829
830        let max_content_sizing_sum = sum(&max_content_sizing_guesses);
831        if target_inline_size >= max_content_sizing_sum {
832            Self::distribute_extra_width_to_columns(
833                columns,
834                &mut max_content_sizing_guesses,
835                max_content_sizing_sum,
836                target_inline_size,
837            );
838            return max_content_sizing_guesses;
839        }
840        let min_content_specified_sizing_sum = sum(&min_content_specified_sizing_guesses);
841        if target_inline_size == min_content_specified_sizing_sum {
842            return min_content_specified_sizing_guesses;
843        }
844        let min_content_percentage_sizing_sum = sum(&min_content_percentage_sizing_guesses);
845        if target_inline_size == min_content_percentage_sizing_sum {
846            return min_content_percentage_sizing_guesses;
847        }
848        let min_content_sizes_sum = sum(&min_content_sizing_guesses);
849        if target_inline_size <= min_content_sizes_sum {
850            return min_content_sizing_guesses;
851        }
852
853        let bounds = |sum_a, sum_b| target_inline_size > sum_a && target_inline_size < sum_b;
854
855        let blend = |a: &[Au], sum_a: Au, b: &[Au], sum_b: Au| {
856            // First convert the Au units to f32 in order to do floating point division.
857            let weight_a = (target_inline_size - sum_b).to_f32_px() / (sum_a - sum_b).to_f32_px();
858            let weight_b = 1.0 - weight_a;
859
860            let mut remaining_assignable_width = target_inline_size;
861            let mut widths: Vec<Au> = a
862                .iter()
863                .zip(b.iter())
864                .map(|(guess_a, guess_b)| {
865                    let column_width = guess_a.scale_by(weight_a) + guess_b.scale_by(weight_b);
866                    // Clamp to avoid exceeding the assignable width. This could otherwise
867                    // happen when dealing with huge values whose sum is clamped to MAX_AU.
868                    let column_width = column_width.min(remaining_assignable_width);
869                    remaining_assignable_width -= column_width;
870                    column_width
871                })
872                .collect();
873
874            if !remaining_assignable_width.is_zero() {
875                // The computations above can introduce floating-point imprecisions.
876                // Since these errors are very small (1Au), it's fine to simply adjust
877                // the first column such that the total width matches the assignable width
878                debug_assert!(
879                    remaining_assignable_width >= Au::zero(),
880                    "Sum of columns shouldn't exceed the assignable table width"
881                );
882                debug_assert!(
883                    remaining_assignable_width <= Au::new(widths.len() as i32),
884                    "A deviation of more than one Au per column is unlikely to be caused by float imprecision"
885                );
886
887                // We checked if the table had columns at the top of the function, so there
888                // always is a first column
889                widths[0] += remaining_assignable_width;
890            }
891
892            debug_assert!(widths.iter().sum::<Au>() == target_inline_size);
893
894            widths
895        };
896
897        if bounds(min_content_sizes_sum, min_content_percentage_sizing_sum) {
898            return blend(
899                &min_content_sizing_guesses,
900                min_content_sizes_sum,
901                &min_content_percentage_sizing_guesses,
902                min_content_percentage_sizing_sum,
903            );
904        }
905
906        if bounds(
907            min_content_percentage_sizing_sum,
908            min_content_specified_sizing_sum,
909        ) {
910            return blend(
911                &min_content_percentage_sizing_guesses,
912                min_content_percentage_sizing_sum,
913                &min_content_specified_sizing_guesses,
914                min_content_specified_sizing_sum,
915            );
916        }
917
918        assert!(bounds(
919            min_content_specified_sizing_sum,
920            max_content_sizing_sum
921        ));
922        blend(
923            &min_content_specified_sizing_guesses,
924            min_content_specified_sizing_sum,
925            &max_content_sizing_guesses,
926            max_content_sizing_sum,
927        )
928    }
929
930    /// This is an implementation of *Distributing excess width to columns* from
931    /// <https://drafts.csswg.org/css-tables/#distributing-width-to-columns>.
932    fn distribute_extra_width_to_columns(
933        columns: &[ColumnLayout],
934        column_sizes: &mut [Au],
935        column_sizes_sum: Au,
936        assignable_width: Au,
937    ) {
938        let all_columns = 0..columns.len();
939        let extra_inline_size = assignable_width - column_sizes_sum;
940
941        let has_originating_cells =
942            |column_index: &usize| columns[*column_index].has_originating_cells;
943        let is_constrained = |column_index: &usize| columns[*column_index].constrained;
944        let is_unconstrained = |column_index: &usize| !is_constrained(column_index);
945        let has_percent_greater_than_zero = |column_index: &usize| {
946            columns[*column_index]
947                .percentage
948                .is_some_and(|percentage| percentage.0 > 0.)
949        };
950        let has_percent_zero = |column_index: &usize| !has_percent_greater_than_zero(column_index);
951        let has_max_content =
952            |column_index: &usize| !columns[*column_index].content_sizes.max_content.is_zero();
953
954        let max_content_sum = |column_index: usize| columns[column_index].content_sizes.max_content;
955
956        // > If there are non-constrained columns that have originating cells with intrinsic
957        // > percentage width of 0% and with nonzero max-content width (aka the columns allowed to
958        // > grow by this rule), the distributed widths of the columns allowed to grow by this rule
959        // > are increased in proportion to max-content width so the total increase adds to the
960        // > excess width.
961        let unconstrained_max_content_columns = all_columns
962            .clone()
963            .filter(is_unconstrained)
964            .filter(has_originating_cells)
965            .filter(has_percent_zero)
966            .filter(has_max_content);
967        let total_max_content_width = unconstrained_max_content_columns
968            .clone()
969            .map(max_content_sum)
970            .fold(Au::zero(), |a, b| a + b);
971        if !total_max_content_width.is_zero() {
972            for column_index in unconstrained_max_content_columns {
973                column_sizes[column_index] += extra_inline_size.scale_by(
974                    columns[column_index].content_sizes.max_content.to_f32_px() /
975                        total_max_content_width.to_f32_px(),
976                );
977            }
978            return;
979        }
980
981        // > Otherwise, if there are non-constrained columns that have originating cells with intrinsic
982        // > percentage width of 0% (aka the columns allowed to grow by this rule, which thanks to the
983        // > previous rule must have zero max-content width), the distributed widths of the columns
984        // > allowed to grow by this rule are increased by equal amounts so the total increase adds to
985        // > the excess width.V
986        let unconstrained_no_percent_columns = all_columns
987            .clone()
988            .filter(is_unconstrained)
989            .filter(has_originating_cells)
990            .filter(has_percent_zero);
991        let total_unconstrained_no_percent = unconstrained_no_percent_columns.clone().count();
992        if total_unconstrained_no_percent > 0 {
993            let extra_space_per_column =
994                extra_inline_size.scale_by(1.0 / total_unconstrained_no_percent as f32);
995            for column_index in unconstrained_no_percent_columns {
996                column_sizes[column_index] += extra_space_per_column;
997            }
998            return;
999        }
1000
1001        // > Otherwise, if there are constrained columns with intrinsic percentage width of 0% and
1002        // > with nonzero max-content width (aka the columns allowed to grow by this rule, which, due
1003        // > to other rules, must have originating cells), the distributed widths of the columns
1004        // > allowed to grow by this rule are increased in proportion to max-content width so the
1005        // > total increase adds to the excess width.
1006        let constrained_max_content_columns = all_columns
1007            .clone()
1008            .filter(is_constrained)
1009            .filter(has_originating_cells)
1010            .filter(has_percent_zero)
1011            .filter(has_max_content);
1012        let total_max_content_width = constrained_max_content_columns
1013            .clone()
1014            .map(max_content_sum)
1015            .fold(Au::zero(), |a, b| a + b);
1016        if !total_max_content_width.is_zero() {
1017            for column_index in constrained_max_content_columns {
1018                column_sizes[column_index] += extra_inline_size.scale_by(
1019                    columns[column_index].content_sizes.max_content.to_f32_px() /
1020                        total_max_content_width.to_f32_px(),
1021                );
1022            }
1023            return;
1024        }
1025
1026        // > Otherwise, if there are columns with intrinsic percentage width greater than 0% (aka the
1027        // > columns allowed to grow by this rule, which, due to other rules, must have originating
1028        // > cells), the distributed widths of the columns allowed to grow by this rule are increased
1029        // > in proportion to intrinsic percentage width so the total increase adds to the excess
1030        // > width.
1031        let columns_with_percentage = all_columns.clone().filter(has_percent_greater_than_zero);
1032        let total_percent = columns_with_percentage
1033            .clone()
1034            .map(|column_index| columns[column_index].percentage.unwrap_or_default().0)
1035            .sum::<f32>();
1036        if total_percent > 0. {
1037            for column_index in columns_with_percentage {
1038                let column_percentage = columns[column_index].percentage.unwrap_or_default();
1039                column_sizes[column_index] +=
1040                    extra_inline_size.scale_by(column_percentage.0 / total_percent);
1041            }
1042            return;
1043        }
1044
1045        // > Otherwise, if there is any such column, the distributed widths of all columns that have
1046        // > originating cells are increased by equal amounts so the total increase adds to the excess
1047        // > width.
1048        let has_originating_cells_columns = all_columns.clone().filter(has_originating_cells);
1049        let total_has_originating_cells = has_originating_cells_columns.clone().count();
1050        if total_has_originating_cells > 0 {
1051            let extra_space_per_column =
1052                extra_inline_size.scale_by(1.0 / total_has_originating_cells as f32);
1053            for column_index in has_originating_cells_columns {
1054                column_sizes[column_index] += extra_space_per_column;
1055            }
1056            return;
1057        }
1058
1059        // > Otherwise, the distributed widths of all columns are increased by equal amounts so the
1060        // total increase adds to the excess width.
1061        let extra_space_for_all_columns = extra_inline_size.scale_by(1.0 / columns.len() as f32);
1062        for guess in column_sizes.iter_mut() {
1063            *guess += extra_space_for_all_columns;
1064        }
1065    }
1066
1067    /// This is an implementation of *Row layout (first pass)* from
1068    /// <https://drafts.csswg.org/css-tables/#row-layout>.
1069    fn layout_cells_in_row(
1070        &mut self,
1071        layout_context: &LayoutContext,
1072        containing_block_for_table: &ContainingBlock,
1073    ) {
1074        let layout_table_slot = |coordinate: TableSlotCoordinates, slot: &TableSlot| {
1075            let TableSlot::Cell(cell) = slot else {
1076                return None;
1077            };
1078
1079            let cell = cell.borrow();
1080            let area = LogicalSides {
1081                inline_start: coordinate.x,
1082                inline_end: coordinate.x + cell.colspan,
1083                block_start: coordinate.y,
1084                block_end: coordinate.y + cell.rowspan,
1085            };
1086            let layout_style = cell.layout_style();
1087            let border = self
1088                .get_collapsed_border_widths_for_area(area)
1089                .unwrap_or_else(|| {
1090                    layout_style.border_width(containing_block_for_table.style.writing_mode)
1091                });
1092            let padding: LogicalSides<Au> = layout_style
1093                .padding(containing_block_for_table.style.writing_mode)
1094                .percentages_relative_to(self.basis_for_cell_padding_percentage);
1095            let inline_border_padding_sum = border.inline_sum() + padding.inline_sum();
1096            let border_spacing_spanned =
1097                self.table.border_spacing().inline * (cell.colspan - 1) as i32;
1098
1099            let mut total_cell_width: Au = (coordinate.x..coordinate.x + cell.colspan)
1100                .map(|column_index| self.distributed_column_widths[column_index])
1101                .sum::<Au>() -
1102                inline_border_padding_sum +
1103                border_spacing_spanned;
1104            total_cell_width = total_cell_width.max(Au::zero());
1105
1106            let containing_block_for_children = ContainingBlock {
1107                size: ContainingBlockSize {
1108                    inline: total_cell_width,
1109                    block: SizeConstraint::default(),
1110                },
1111                style: &cell.base.style,
1112            };
1113
1114            let mut positioning_context = PositioningContext::default();
1115            let layout = cell.contents.layout(
1116                layout_context,
1117                &mut positioning_context,
1118                &containing_block_for_children,
1119            );
1120
1121            Some(CellLayout {
1122                layout,
1123                padding,
1124                border,
1125                positioning_context,
1126            })
1127        };
1128
1129        self.cells_laid_out = if layout_context.use_rayon {
1130            self.table
1131                .slots
1132                .par_iter()
1133                .enumerate()
1134                .map(|(row_index, row_slots)| {
1135                    row_slots
1136                        .par_iter()
1137                        .enumerate()
1138                        .map(|(column_index, slot)| {
1139                            layout_table_slot(
1140                                TableSlotCoordinates::new(column_index, row_index),
1141                                slot,
1142                            )
1143                        })
1144                        .collect()
1145                })
1146                .collect()
1147        } else {
1148            self.table
1149                .slots
1150                .iter()
1151                .enumerate()
1152                .map(|(row_index, row_slots)| {
1153                    row_slots
1154                        .iter()
1155                        .enumerate()
1156                        .map(|(column_index, slot)| {
1157                            layout_table_slot(
1158                                TableSlotCoordinates::new(column_index, row_index),
1159                                slot,
1160                            )
1161                        })
1162                        .collect()
1163                })
1164                .collect()
1165        };
1166
1167        // Now go through all cells laid out and update the cell measure based on the size
1168        // determined during layout.
1169        for row_index in 0..self.table.size.height {
1170            for column_index in 0..self.table.size.width {
1171                let Some(layout) = &self.cells_laid_out[row_index][column_index] else {
1172                    continue;
1173                };
1174
1175                self.cell_measures[row_index][column_index]
1176                    .block
1177                    .content_sizes
1178                    .max_assign(layout.outer_block_size().into());
1179            }
1180        }
1181    }
1182
1183    /// Do the first layout of a table row, after laying out the cells themselves. This is
1184    /// more or less and implementation of <https://drafts.csswg.org/css-tables/#row-layout>.
1185    fn do_first_row_layout(&mut self, writing_mode: WritingMode) -> Vec<Au> {
1186        let mut row_sizes = (0..self.table.size.height)
1187            .map(|row_index| {
1188                let (mut max_ascent, mut max_descent, mut max_row_height) =
1189                    (Au::zero(), Au::zero(), Au::zero());
1190
1191                for column_index in 0..self.table.size.width {
1192                    let cell = match self.table.slots[row_index][column_index] {
1193                        TableSlot::Cell(ref cell) => cell,
1194                        _ => continue,
1195                    };
1196
1197                    let layout = match self.cells_laid_out[row_index][column_index] {
1198                        Some(ref layout) => layout,
1199                        None => {
1200                            warn!(
1201                                "Did not find a layout at a slot index with an originating cell."
1202                            );
1203                            continue;
1204                        },
1205                    };
1206
1207                    let cell = cell.borrow();
1208                    let outer_block_size = layout.outer_block_size();
1209                    if cell.rowspan == 1 {
1210                        max_row_height.max_assign(outer_block_size);
1211                    }
1212
1213                    if cell.effective_vertical_align() == VerticalAlignKeyword::Baseline {
1214                        let ascent = layout.ascent();
1215                        let border_padding_start =
1216                            layout.border.block_start + layout.padding.block_start;
1217                        let border_padding_end = layout.border.block_end + layout.padding.block_end;
1218                        max_ascent.max_assign(ascent + border_padding_start);
1219
1220                        // Only take into account the descent of this cell if doesn't span
1221                        // rows. The descent portion of the cell in cells that do span rows
1222                        // may extend into other rows.
1223                        if cell.rowspan == 1 {
1224                            max_descent.max_assign(
1225                                layout.layout.content_block_size - ascent + border_padding_end,
1226                            );
1227                        }
1228                    }
1229                }
1230                self.row_baselines.push(max_ascent);
1231                max_row_height.max(max_ascent + max_descent)
1232            })
1233            .collect();
1234        self.calculate_row_sizes_after_first_layout(&mut row_sizes, writing_mode);
1235        row_sizes
1236    }
1237
1238    #[allow(clippy::ptr_arg)] // Needs to be a vec because of the function above
1239    /// After doing layout of table rows, calculate final row size and distribute space across
1240    /// rowspanned cells. This follows the implementation of LayoutNG and the priority
1241    /// agorithm described at <https://github.com/w3c/csswg-drafts/issues/4418>.
1242    fn calculate_row_sizes_after_first_layout(
1243        &mut self,
1244        row_sizes: &mut Vec<Au>,
1245        writing_mode: WritingMode,
1246    ) {
1247        let mut cells_to_distribute = Vec::new();
1248        let mut total_percentage = 0.;
1249        #[allow(clippy::needless_range_loop)] // It makes sense to use it here
1250        for row_index in 0..self.table.size.height {
1251            let row_measure = self
1252                .table
1253                .get_row_measure_for_row_at_index(writing_mode, row_index);
1254            row_sizes[row_index].max_assign(row_measure.content_sizes.min_content);
1255
1256            let mut percentage = row_measure.percentage.unwrap_or_default().0;
1257            for column_index in 0..self.table.size.width {
1258                let cell_percentage = self.cell_measures[row_index][column_index]
1259                    .block
1260                    .percentage
1261                    .unwrap_or_default()
1262                    .0;
1263                percentage = percentage.max(cell_percentage);
1264
1265                let cell_measure = &self.cell_measures[row_index][column_index].block;
1266                let cell = match self.table.slots[row_index][column_index] {
1267                    TableSlot::Cell(ref cell) if cell.borrow().rowspan > 1 => cell,
1268                    TableSlot::Cell(_) => {
1269                        // If this is an originating cell, that isn't spanning, then we make sure the row is
1270                        // at least big enough to hold the cell.
1271                        row_sizes[row_index].max_assign(cell_measure.content_sizes.max_content);
1272                        continue;
1273                    },
1274                    _ => continue,
1275                };
1276
1277                cells_to_distribute.push(RowspanToDistribute {
1278                    coordinates: TableSlotCoordinates::new(column_index, row_index),
1279                    cell: cell.borrow(),
1280                    measure: cell_measure,
1281                });
1282            }
1283
1284            self.rows[row_index].percent = Percentage(percentage.min(1. - total_percentage));
1285            total_percentage += self.rows[row_index].percent.0;
1286        }
1287
1288        cells_to_distribute.sort_by(|a, b| {
1289            if a.range() == b.range() {
1290                return a
1291                    .measure
1292                    .content_sizes
1293                    .min_content
1294                    .cmp(&b.measure.content_sizes.min_content);
1295            }
1296            if a.fully_encloses(b) {
1297                return std::cmp::Ordering::Greater;
1298            }
1299            if b.fully_encloses(a) {
1300                return std::cmp::Ordering::Less;
1301            }
1302            a.coordinates.y.cmp(&b.coordinates.y)
1303        });
1304
1305        for rowspan_to_distribute in cells_to_distribute {
1306            let rows_spanned = rowspan_to_distribute.range();
1307            let current_rows_size: Au = rows_spanned.clone().map(|index| row_sizes[index]).sum();
1308            let border_spacing_spanned =
1309                self.table.border_spacing().block * (rows_spanned.len() - 1) as i32;
1310            let excess_size = (rowspan_to_distribute.measure.content_sizes.min_content -
1311                current_rows_size -
1312                border_spacing_spanned)
1313                .max(Au::zero());
1314
1315            self.distribute_extra_size_to_rows(
1316                excess_size,
1317                rows_spanned,
1318                row_sizes,
1319                None,
1320                true, /* rowspan_distribution */
1321            );
1322        }
1323    }
1324
1325    /// An implementation of the same extra block size distribution algorithm used in
1326    /// LayoutNG and described at <https://github.com/w3c/csswg-drafts/issues/4418>.
1327    fn distribute_extra_size_to_rows(
1328        &self,
1329        mut excess_size: Au,
1330        track_range: Range<usize>,
1331        track_sizes: &mut [Au],
1332        percentage_resolution_size: Option<Au>,
1333        rowspan_distribution: bool,
1334    ) {
1335        if excess_size.is_zero() {
1336            return;
1337        }
1338
1339        let is_constrained = |track_index: &usize| self.rows[*track_index].constrained;
1340        let is_unconstrained = |track_index: &usize| !is_constrained(track_index);
1341        let is_empty: Vec<bool> = track_sizes.iter().map(|size| size.is_zero()).collect();
1342        let is_not_empty = |track_index: &usize| !is_empty[*track_index];
1343        let other_row_that_starts_a_rowspan = |track_index: &usize| {
1344            *track_index != track_range.start &&
1345                self.rows[*track_index].has_cell_with_span_greater_than_one
1346        };
1347
1348        // If we have a table height (not during rowspan distribution), first distribute to rows
1349        // that have percentage sizes proportionally to the size missing to reach the percentage
1350        // of table height required.
1351        if let Some(percentage_resolution_size) = percentage_resolution_size {
1352            let get_percent_block_size_deficit = |row_index: usize, track_size: Au| {
1353                let size_needed_for_percent =
1354                    percentage_resolution_size.scale_by(self.rows[row_index].percent.0);
1355                (size_needed_for_percent - track_size).max(Au::zero())
1356            };
1357            let percent_block_size_deficit: Au = track_range
1358                .clone()
1359                .map(|index| get_percent_block_size_deficit(index, track_sizes[index]))
1360                .sum();
1361            let percent_distributable_block_size = percent_block_size_deficit.min(excess_size);
1362            if percent_distributable_block_size > Au::zero() {
1363                for track_index in track_range.clone() {
1364                    let row_deficit =
1365                        get_percent_block_size_deficit(track_index, track_sizes[track_index]);
1366                    if row_deficit > Au::zero() {
1367                        let ratio =
1368                            row_deficit.to_f32_px() / percent_block_size_deficit.to_f32_px();
1369                        let size = percent_distributable_block_size.scale_by(ratio);
1370                        track_sizes[track_index] += size;
1371                        excess_size -= size;
1372                    }
1373                }
1374            }
1375        }
1376
1377        // If this is rowspan distribution and there are rows other than the first row that have a
1378        // cell with rowspan > 1, distribute the extra space equally to those rows.
1379        if rowspan_distribution {
1380            let rows_that_start_rowspan: Vec<usize> = track_range
1381                .clone()
1382                .filter(other_row_that_starts_a_rowspan)
1383                .collect();
1384            if !rows_that_start_rowspan.is_empty() {
1385                let scale = 1.0 / rows_that_start_rowspan.len() as f32;
1386                for track_index in rows_that_start_rowspan.iter() {
1387                    track_sizes[*track_index] += excess_size.scale_by(scale);
1388                }
1389                return;
1390            }
1391        }
1392
1393        // If there are unconstrained non-empty rows, grow them all proportionally to their current size.
1394        let unconstrained_non_empty_rows: Vec<usize> = track_range
1395            .clone()
1396            .filter(is_unconstrained)
1397            .filter(is_not_empty)
1398            .collect();
1399        if !unconstrained_non_empty_rows.is_empty() {
1400            let total_size: Au = unconstrained_non_empty_rows
1401                .iter()
1402                .map(|index| track_sizes[*index])
1403                .sum();
1404            for track_index in unconstrained_non_empty_rows.iter() {
1405                let scale = track_sizes[*track_index].to_f32_px() / total_size.to_f32_px();
1406                track_sizes[*track_index] += excess_size.scale_by(scale);
1407            }
1408            return;
1409        }
1410
1411        let (non_empty_rows, empty_rows): (Vec<usize>, Vec<usize>) =
1412            track_range.clone().partition(is_not_empty);
1413        let only_have_empty_rows = empty_rows.len() == track_range.len();
1414        if !empty_rows.is_empty() {
1415            // If this is rowspan distribution and there are only empty rows, just grow the
1416            // last one.
1417            if rowspan_distribution && only_have_empty_rows {
1418                track_sizes[*empty_rows.last().unwrap()] += excess_size;
1419                return;
1420            }
1421
1422            // Otherwise, if we only have empty rows or if all the non-empty rows are constrained,
1423            // then grow the empty rows.
1424            let non_empty_rows_all_constrained = !non_empty_rows.iter().any(is_unconstrained);
1425            if only_have_empty_rows || non_empty_rows_all_constrained {
1426                // If there are both unconstrained and constrained empty rows, only increase the
1427                // size of the unconstrained ones, otherwise increase the size of all empty rows.
1428                let mut rows_to_grow = &empty_rows;
1429                let unconstrained_empty_rows: Vec<usize> = rows_to_grow
1430                    .iter()
1431                    .copied()
1432                    .filter(is_unconstrained)
1433                    .collect();
1434                if !unconstrained_empty_rows.is_empty() {
1435                    rows_to_grow = &unconstrained_empty_rows;
1436                }
1437
1438                // All empty rows that will grow equally.
1439                let scale = 1.0 / rows_to_grow.len() as f32;
1440                for track_index in rows_to_grow.iter() {
1441                    track_sizes[*track_index] += excess_size.scale_by(scale);
1442                }
1443                return;
1444            }
1445        }
1446
1447        // If there are non-empty rows, they all grow in proportion to their current size,
1448        // whether or not they are constrained.
1449        if !non_empty_rows.is_empty() {
1450            let total_size: Au = non_empty_rows.iter().map(|index| track_sizes[*index]).sum();
1451            for track_index in non_empty_rows.iter() {
1452                let scale = track_sizes[*track_index].to_f32_px() / total_size.to_f32_px();
1453                track_sizes[*track_index] += excess_size.scale_by(scale);
1454            }
1455        }
1456    }
1457
1458    /// Given computed row sizes, compute the final block size of the table and distribute extra
1459    /// block size to table rows.
1460    fn compute_table_height_and_final_row_heights(
1461        &mut self,
1462        mut row_sizes: Vec<Au>,
1463        containing_block_for_children: &ContainingBlock,
1464    ) {
1465        // The table content height is the maximum of the computed table height from style and the
1466        // sum of computed row heights from row layout plus size from borders and spacing.
1467        // TODO: for `height: stretch`, the block size of the containing block is the available
1468        // space for the entire table wrapper, but here we are using that amount for the table grid.
1469        // Therefore, if there is a caption, this will cause overflow. Gecko and WebKit have the
1470        // same problem, but not Blink.
1471        let table_height_from_style = match containing_block_for_children.size.block {
1472            SizeConstraint::Definite(size) => size,
1473            SizeConstraint::MinMax(min, _) => min,
1474        };
1475
1476        let block_border_spacing = self.table.total_border_spacing().block;
1477        let table_height_from_rows = row_sizes.iter().sum::<Au>() + block_border_spacing;
1478        self.final_table_height = table_height_from_rows.max(table_height_from_style);
1479
1480        // If the table height is defined by the rows sizes, there is no extra space to distribute
1481        // to rows.
1482        if self.final_table_height == table_height_from_rows {
1483            self.row_sizes = row_sizes;
1484            return;
1485        }
1486
1487        // There was extra block size added to the table from the table style, so distribute this
1488        // extra space to rows using the same distribution algorithm used for distributing rowspan
1489        // space.
1490        // TODO: This should first distribute space to row groups and then to rows.
1491        self.distribute_extra_size_to_rows(
1492            self.final_table_height - table_height_from_rows,
1493            0..self.table.size.height,
1494            &mut row_sizes,
1495            Some(self.final_table_height),
1496            false, /* rowspan_distribution */
1497        );
1498        self.row_sizes = row_sizes;
1499    }
1500
1501    fn layout_caption(
1502        &self,
1503        caption: &TableCaption,
1504        layout_context: &LayoutContext,
1505        parent_positioning_context: &mut PositioningContext,
1506    ) -> BoxFragment {
1507        let containing_block = &ContainingBlock {
1508            size: ContainingBlockSize {
1509                inline: self.table_width + self.pbm.padding_border_sums.inline,
1510                block: SizeConstraint::default(),
1511            },
1512            style: &self.table.style,
1513        };
1514
1515        // The parent of a caption is the table wrapper, which establishes an independent
1516        // formatting context. Therefore, we don't ignore block margins when resolving a
1517        // stretch block size. https://drafts.csswg.org/css-sizing-4/#stretch-fit-sizing
1518        let ignore_block_margins_for_stretch = LogicalSides1D::new(false, false);
1519
1520        let mut positioning_context =
1521            PositioningContext::new_for_layout_box_base(&caption.context.base);
1522        let mut box_fragment = caption.context.layout_in_flow_block_level(
1523            layout_context,
1524            positioning_context
1525                .as_mut()
1526                .unwrap_or(parent_positioning_context),
1527            containing_block,
1528            None, /* sequential_layout_state */
1529            ignore_block_margins_for_stretch,
1530        );
1531
1532        if let Some(mut positioning_context) = positioning_context.take() {
1533            positioning_context.layout_collected_children(layout_context, &mut box_fragment);
1534            parent_positioning_context.append(positioning_context);
1535        }
1536
1537        box_fragment
1538    }
1539
1540    /// Lay out the table (grid and captions) of this [`TableLayout`] into fragments. This should
1541    /// only be be called after calling [`TableLayout.compute_measures`].
1542    #[servo_tracing::instrument(name = "Table::layout", skip_all)]
1543    fn layout(
1544        mut self,
1545        layout_context: &LayoutContext,
1546        positioning_context: &mut PositioningContext,
1547        containing_block_for_children: &ContainingBlock,
1548        containing_block_for_table: &ContainingBlock,
1549    ) -> CacheableLayoutResult {
1550        let table_writing_mode = containing_block_for_children.style.writing_mode;
1551        self.compute_border_collapse(table_writing_mode);
1552        let layout_style = self.table.layout_style(Some(&self));
1553
1554        self.pbm = layout_style
1555            .padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1556                table_writing_mode,
1557                containing_block_for_table.size.inline,
1558            );
1559        self.compute_measures(layout_context, table_writing_mode);
1560        self.compute_table_width(containing_block_for_children);
1561
1562        // The table wrapper is the one that has the CSS properties for the grid's border and padding. This
1563        // weirdness is difficult to express in Servo's layout system. We have the wrapper size itself as if
1564        // those properties applied to it and then just account for the discrepency in sizing here. In reality,
1565        // the wrapper does not draw borders / backgrounds and all of its content (grid and captions) are
1566        // placed with a negative offset in the table wrapper's content box so that they overlap the undrawn
1567        // border / padding area.
1568        //
1569        // TODO: This is a pretty large hack. It would be nicer to actually have the grid sized properly,
1570        // but it works for now.
1571        //
1572        // Get the padding, border, and margin of the table using the inline size of the table's containing
1573        // block but in the writing of the table itself.
1574        // TODO: This is broken for orthoganol flows, because the inline size of the parent isn't necessarily
1575        // the inline size of the table.
1576        let containing_block_for_logical_conversion = ContainingBlock {
1577            size: ContainingBlockSize {
1578                inline: self.table_width,
1579                block: containing_block_for_table.size.block,
1580            },
1581            style: containing_block_for_children.style,
1582        };
1583        let offset_from_wrapper = -self.pbm.padding - self.pbm.border;
1584        let mut current_block_offset = offset_from_wrapper.block_start;
1585
1586        let mut table_layout = CacheableLayoutResult {
1587            fragments: Vec::new(),
1588            content_block_size: Zero::zero(),
1589            content_inline_size_for_table: None,
1590            baselines: Baselines::default(),
1591            depends_on_block_constraints: true,
1592            specific_layout_info: Some(SpecificLayoutInfo::TableWrapper),
1593            collapsible_margins_in_children: CollapsedBlockMargins::zero(),
1594        };
1595
1596        table_layout
1597            .fragments
1598            .extend(self.table.captions.iter().filter_map(|caption| {
1599                let caption = caption.borrow();
1600                if caption.context.style().clone_caption_side() != CaptionSide::Top {
1601                    return None;
1602                }
1603
1604                let original_positioning_context_length = positioning_context.len();
1605                let mut caption_fragment =
1606                    self.layout_caption(&caption, layout_context, positioning_context);
1607
1608                // The caption is not placed yet. Construct a rectangle for it in the adjusted containing block
1609                // for the table children and only then convert the result to physical geometry.
1610                let caption_pbm = caption_fragment
1611                    .padding_border_margin()
1612                    .to_logical(table_writing_mode);
1613
1614                let caption_relative_offset = match caption_fragment.style.clone_position() {
1615                    Position::Relative => {
1616                        relative_adjustement(&caption_fragment.style, containing_block_for_children)
1617                    },
1618                    _ => LogicalVec2::zero(),
1619                };
1620
1621                caption_fragment.content_rect = LogicalRect {
1622                    start_corner: LogicalVec2 {
1623                        inline: offset_from_wrapper.inline_start + caption_pbm.inline_start,
1624                        block: current_block_offset + caption_pbm.block_start,
1625                    } + caption_relative_offset,
1626                    size: caption_fragment
1627                        .content_rect
1628                        .size
1629                        .to_logical(table_writing_mode),
1630                }
1631                .as_physical(Some(&containing_block_for_logical_conversion));
1632
1633                current_block_offset += caption_fragment
1634                    .margin_rect()
1635                    .size
1636                    .to_logical(table_writing_mode)
1637                    .block;
1638
1639                let caption_fragment = Fragment::Box(ArcRefCell::new(caption_fragment));
1640                positioning_context.adjust_static_position_of_hoisted_fragments(
1641                    &caption_fragment,
1642                    original_positioning_context_length,
1643                );
1644
1645                caption.context.base.set_fragment(caption_fragment.clone());
1646                Some(caption_fragment)
1647            }));
1648
1649        let original_positioning_context_length = positioning_context.len();
1650        let mut grid_fragment = self.layout_grid(
1651            layout_context,
1652            positioning_context,
1653            &containing_block_for_logical_conversion,
1654            containing_block_for_children,
1655        );
1656
1657        // Take the baseline of the grid fragment, after adjusting it to be in the coordinate system
1658        // of the table wrapper.
1659        let logical_grid_content_rect = grid_fragment
1660            .content_rect
1661            .to_logical(&containing_block_for_logical_conversion);
1662        let grid_pbm = grid_fragment
1663            .padding_border_margin()
1664            .to_logical(table_writing_mode);
1665        table_layout.baselines = grid_fragment.baselines(table_writing_mode).offset(
1666            current_block_offset +
1667                logical_grid_content_rect.start_corner.block +
1668                grid_pbm.block_start,
1669        );
1670
1671        grid_fragment.content_rect = LogicalRect {
1672            start_corner: LogicalVec2 {
1673                inline: offset_from_wrapper.inline_start + grid_pbm.inline_start,
1674                block: current_block_offset + grid_pbm.block_start,
1675            },
1676            size: grid_fragment
1677                .content_rect
1678                .size
1679                .to_logical(table_writing_mode),
1680        }
1681        .as_physical(Some(&containing_block_for_logical_conversion));
1682
1683        current_block_offset += grid_fragment
1684            .border_rect()
1685            .size
1686            .to_logical(table_writing_mode)
1687            .block;
1688        if logical_grid_content_rect.size.inline < self.table_width {
1689            // This can happen when collapsing columns
1690            table_layout.content_inline_size_for_table =
1691                Some(logical_grid_content_rect.size.inline);
1692        }
1693
1694        let grid_fragment = Fragment::Box(ArcRefCell::new(grid_fragment));
1695        positioning_context.adjust_static_position_of_hoisted_fragments(
1696            &grid_fragment,
1697            original_positioning_context_length,
1698        );
1699        table_layout.fragments.push(grid_fragment);
1700
1701        table_layout
1702            .fragments
1703            .extend(self.table.captions.iter().filter_map(|caption| {
1704                let caption = caption.borrow();
1705                if caption.context.style().clone_caption_side() != CaptionSide::Bottom {
1706                    return None;
1707                }
1708
1709                let original_positioning_context_length = positioning_context.len();
1710                let mut caption_fragment =
1711                    self.layout_caption(&caption, layout_context, positioning_context);
1712
1713                // The caption is not placed yet. Construct a rectangle for it in the adjusted containing block
1714                // for the table children and only then convert the result to physical geometry.
1715                let caption_pbm = caption_fragment
1716                    .padding_border_margin()
1717                    .to_logical(table_writing_mode);
1718                caption_fragment.content_rect = LogicalRect {
1719                    start_corner: LogicalVec2 {
1720                        inline: offset_from_wrapper.inline_start + caption_pbm.inline_start,
1721                        block: current_block_offset + caption_pbm.block_start,
1722                    },
1723                    size: caption_fragment
1724                        .content_rect
1725                        .size
1726                        .to_logical(table_writing_mode),
1727                }
1728                .as_physical(Some(&containing_block_for_logical_conversion));
1729
1730                current_block_offset += caption_fragment
1731                    .margin_rect()
1732                    .size
1733                    .to_logical(table_writing_mode)
1734                    .block;
1735
1736                let caption_fragment = Fragment::Box(ArcRefCell::new(caption_fragment));
1737                positioning_context.adjust_static_position_of_hoisted_fragments(
1738                    &caption_fragment,
1739                    original_positioning_context_length,
1740                );
1741
1742                caption.context.base.set_fragment(caption_fragment.clone());
1743                Some(caption_fragment)
1744            }));
1745
1746        table_layout.content_block_size = current_block_offset + offset_from_wrapper.block_end;
1747        table_layout
1748    }
1749
1750    /// Lay out the grid portion of this [`TableLayout`] into fragments. This should only be be
1751    /// called after calling [`TableLayout.compute_measures`].
1752    fn layout_grid(
1753        &mut self,
1754        layout_context: &LayoutContext,
1755        positioning_context: &mut PositioningContext,
1756        containing_block_for_logical_conversion: &ContainingBlock,
1757        containing_block_for_children: &ContainingBlock,
1758    ) -> BoxFragment {
1759        self.distributed_column_widths =
1760            Self::distribute_width_to_columns(self.assignable_width, &self.columns);
1761        self.layout_cells_in_row(layout_context, containing_block_for_children);
1762        let table_writing_mode = containing_block_for_children.style.writing_mode;
1763        let first_layout_row_heights = self.do_first_row_layout(table_writing_mode);
1764        self.compute_table_height_and_final_row_heights(
1765            first_layout_row_heights,
1766            containing_block_for_children,
1767        );
1768
1769        assert_eq!(self.table.size.height, self.row_sizes.len());
1770        assert_eq!(self.table.size.width, self.distributed_column_widths.len());
1771
1772        if self.table.size.width == 0 && self.table.size.height == 0 {
1773            let content_rect = LogicalRect {
1774                start_corner: LogicalVec2::zero(),
1775                size: LogicalVec2 {
1776                    inline: self.table_width,
1777                    block: self.final_table_height,
1778                },
1779            }
1780            .as_physical(Some(containing_block_for_logical_conversion));
1781            return BoxFragment::new(
1782                self.table.grid_base_fragment_info,
1783                self.table.grid_style.clone(),
1784                Vec::new(),
1785                content_rect,
1786                self.pbm.padding.to_physical(table_writing_mode),
1787                self.pbm.border.to_physical(table_writing_mode),
1788                PhysicalSides::zero(),
1789                self.specific_layout_info_for_grid(),
1790            );
1791        }
1792
1793        let mut table_fragments = Vec::new();
1794        let table_and_track_dimensions = TableAndTrackDimensions::new(self);
1795        self.make_fragments_for_columns_and_column_groups(
1796            &table_and_track_dimensions,
1797            &mut table_fragments,
1798        );
1799
1800        let mut baselines = Baselines::default();
1801        let mut row_group_fragment_layout = None;
1802        for row_index in 0..self.table.size.height {
1803            // From <https://drafts.csswg.org/css-align-3/#baseline-export>
1804            // > If any cells in the row participate in first baseline/last baseline alignment along
1805            // > the inline axis, the first/last baseline set of the row is generated from their
1806            // > shared alignment baseline and the row’s first available font, after alignment has
1807            // > been performed. Otherwise, the first/last baseline set of the row is synthesized from
1808            // > the lowest and highest content edges of the cells in the row. [CSS2]
1809            //
1810            // If any cell below has baseline alignment, these values will be overwritten,
1811            // but they are initialized to the content edge of the first row.
1812            if row_index == 0 {
1813                let row_end = table_and_track_dimensions
1814                    .get_row_rect(0)
1815                    .max_block_position();
1816                baselines.first = Some(row_end);
1817                baselines.last = Some(row_end);
1818            }
1819
1820            let row_is_collapsed = self.is_row_collapsed(row_index);
1821            let table_row = self.table.rows[row_index].borrow();
1822            let mut row_fragment_layout = RowFragmentLayout::new(
1823                &table_row,
1824                row_index,
1825                &table_and_track_dimensions,
1826                &self.table.style,
1827            );
1828
1829            let old_row_group_index = row_group_fragment_layout
1830                .as_ref()
1831                .map(|layout: &RowGroupFragmentLayout| layout.index);
1832            if table_row.group_index != old_row_group_index {
1833                // First create the Fragment for any existing RowGroupFragmentLayout.
1834                if let Some(old_row_group_layout) = row_group_fragment_layout.take() {
1835                    table_fragments.push(old_row_group_layout.finish(
1836                        layout_context,
1837                        positioning_context,
1838                        containing_block_for_logical_conversion,
1839                        containing_block_for_children,
1840                    ));
1841                }
1842
1843                // Then, create a new RowGroupFragmentLayout for the current and potentially subsequent rows.
1844                if let Some(new_group_index) = table_row.group_index {
1845                    row_group_fragment_layout = Some(RowGroupFragmentLayout::new(
1846                        self.table.row_groups[new_group_index].clone(),
1847                        new_group_index,
1848                        &table_and_track_dimensions,
1849                    ));
1850                }
1851            }
1852
1853            let column_indices = 0..self.table.size.width;
1854            row_fragment_layout.fragments.reserve(self.table.size.width);
1855            for column_index in column_indices {
1856                self.do_final_cell_layout(
1857                    row_index,
1858                    column_index,
1859                    &table_and_track_dimensions,
1860                    &mut baselines,
1861                    &mut row_fragment_layout,
1862                    row_group_fragment_layout.as_mut(),
1863                    positioning_context,
1864                    self.is_column_collapsed(column_index) || row_is_collapsed,
1865                );
1866            }
1867
1868            let row_fragment = row_fragment_layout.finish(
1869                layout_context,
1870                positioning_context,
1871                containing_block_for_logical_conversion,
1872                containing_block_for_children,
1873                &mut row_group_fragment_layout,
1874            );
1875
1876            match row_group_fragment_layout.as_mut() {
1877                Some(layout) => layout.fragments.push(row_fragment),
1878                None => table_fragments.push(row_fragment),
1879            }
1880        }
1881
1882        if let Some(row_group_layout) = row_group_fragment_layout.take() {
1883            table_fragments.push(row_group_layout.finish(
1884                layout_context,
1885                positioning_context,
1886                containing_block_for_logical_conversion,
1887                containing_block_for_children,
1888            ));
1889        }
1890
1891        let content_rect = LogicalRect {
1892            start_corner: LogicalVec2::zero(),
1893            size: LogicalVec2 {
1894                inline: table_and_track_dimensions.table_rect.max_inline_position(),
1895                block: table_and_track_dimensions.table_rect.max_block_position(),
1896            },
1897        }
1898        .as_physical(Some(containing_block_for_logical_conversion));
1899        BoxFragment::new(
1900            self.table.grid_base_fragment_info,
1901            self.table.grid_style.clone(),
1902            table_fragments,
1903            content_rect,
1904            self.pbm.padding.to_physical(table_writing_mode),
1905            self.pbm.border.to_physical(table_writing_mode),
1906            PhysicalSides::zero(),
1907            self.specific_layout_info_for_grid(),
1908        )
1909        .with_baselines(baselines)
1910    }
1911
1912    fn specific_layout_info_for_grid(&mut self) -> Option<SpecificLayoutInfo> {
1913        mem::take(&mut self.collapsed_borders).map(|mut collapsed_borders| {
1914            // TODO: It would probably be better to use `TableAndTrackDimensions`, since that
1915            // has already taken care of collapsed tracks and knows the final track positions.
1916            let mut track_sizes = LogicalVec2 {
1917                inline: mem::take(&mut self.distributed_column_widths),
1918                block: mem::take(&mut self.row_sizes),
1919            };
1920            for (column_index, column_size) in track_sizes.inline.iter_mut().enumerate() {
1921                if self.is_column_collapsed(column_index) {
1922                    mem::take(column_size);
1923                }
1924            }
1925            for (row_index, row_size) in track_sizes.block.iter_mut().enumerate() {
1926                if self.is_row_collapsed(row_index) {
1927                    mem::take(row_size);
1928                }
1929            }
1930            let writing_mode = self.table.style.writing_mode;
1931            if !writing_mode.is_bidi_ltr() {
1932                track_sizes.inline.reverse();
1933                collapsed_borders.inline.reverse();
1934                for border_line in &mut collapsed_borders.block {
1935                    border_line.reverse();
1936                }
1937            }
1938            SpecificLayoutInfo::TableGridWithCollapsedBorders(Box::new(SpecificTableGridInfo {
1939                collapsed_borders: if writing_mode.is_horizontal() {
1940                    PhysicalVec::new(collapsed_borders.inline, collapsed_borders.block)
1941                } else {
1942                    PhysicalVec::new(collapsed_borders.block, collapsed_borders.inline)
1943                },
1944                track_sizes: if writing_mode.is_horizontal() {
1945                    PhysicalVec::new(track_sizes.inline, track_sizes.block)
1946                } else {
1947                    PhysicalVec::new(track_sizes.block, track_sizes.inline)
1948                },
1949            }))
1950        })
1951    }
1952
1953    fn is_row_collapsed(&self, row_index: usize) -> bool {
1954        let Some(row) = &self.table.rows.get(row_index) else {
1955            return false;
1956        };
1957
1958        let row = row.borrow();
1959        if row.base.style.get_inherited_box().visibility == Visibility::Collapse {
1960            return true;
1961        }
1962        let row_group = match row.group_index {
1963            Some(group_index) => self.table.row_groups[group_index].borrow(),
1964            None => return false,
1965        };
1966        row_group.base.style.get_inherited_box().visibility == Visibility::Collapse
1967    }
1968
1969    fn is_column_collapsed(&self, column_index: usize) -> bool {
1970        let Some(column) = &self.table.columns.get(column_index) else {
1971            return false;
1972        };
1973        let column = column.borrow();
1974        if column.base.style.get_inherited_box().visibility == Visibility::Collapse {
1975            return true;
1976        }
1977        let col_group = match column.group_index {
1978            Some(group_index) => self.table.column_groups[group_index].borrow(),
1979            None => return false,
1980        };
1981        col_group.base.style.get_inherited_box().visibility == Visibility::Collapse
1982    }
1983
1984    #[allow(clippy::too_many_arguments)]
1985    fn do_final_cell_layout(
1986        &mut self,
1987        row_index: usize,
1988        column_index: usize,
1989        dimensions: &TableAndTrackDimensions,
1990        baselines: &mut Baselines,
1991        row_fragment_layout: &mut RowFragmentLayout,
1992        row_group_fragment_layout: Option<&mut RowGroupFragmentLayout>,
1993        positioning_context_for_table: &mut PositioningContext,
1994        is_collapsed: bool,
1995    ) {
1996        // The PositioningContext for cells is, in order or preference, the PositioningContext of the row,
1997        // the PositioningContext of the row group, or the PositioningContext of the table.
1998        let row_group_positioning_context =
1999            row_group_fragment_layout.and_then(|layout| layout.positioning_context.as_mut());
2000        let positioning_context = row_fragment_layout
2001            .positioning_context
2002            .as_mut()
2003            .or(row_group_positioning_context)
2004            .unwrap_or(positioning_context_for_table);
2005
2006        let layout = match self.cells_laid_out[row_index][column_index].take() {
2007            Some(layout) => layout,
2008            None => {
2009                return;
2010            },
2011        };
2012        let cell = match self.table.slots[row_index][column_index] {
2013            TableSlot::Cell(ref cell) => cell,
2014            _ => {
2015                warn!("Did not find a non-spanned cell at index with layout.");
2016                return;
2017            },
2018        }
2019        .borrow();
2020
2021        // If this cell has baseline alignment, it can adjust the table's overall baseline.
2022        let row_block_offset = row_fragment_layout.rect.start_corner.block;
2023        let row_baseline = self.row_baselines[row_index];
2024        if cell.effective_vertical_align() == VerticalAlignKeyword::Baseline && !layout.is_empty() {
2025            let baseline = row_block_offset + row_baseline;
2026            if row_index == 0 {
2027                baselines.first = Some(baseline);
2028            }
2029            baselines.last = Some(baseline);
2030        }
2031        let mut row_relative_cell_rect = dimensions.get_cell_rect(
2032            TableSlotCoordinates::new(column_index, row_index),
2033            cell.rowspan,
2034            cell.colspan,
2035        );
2036        row_relative_cell_rect.start_corner -= row_fragment_layout.rect.start_corner;
2037        let mut fragment = cell.create_fragment(
2038            layout,
2039            row_relative_cell_rect,
2040            row_baseline,
2041            positioning_context,
2042            &self.table.style,
2043            &row_fragment_layout.containing_block,
2044            is_collapsed,
2045        );
2046
2047        // Make a table part rectangle relative to the row fragment for the purposes of
2048        // drawing extra backgrounds.
2049        //
2050        // This rectangle is an offset between the row fragment and the other table
2051        // part rectangle (row group, column, column group). Everything between them
2052        // is laid out in a left-to-right fashion, but respecting the verticality of
2053        // the writing mode. This is why below, only the axes are flipped, but the
2054        // rectangle is not flipped for RTL.
2055        let make_relative_to_row_start = |mut rect: LogicalRect<Au>| {
2056            rect.start_corner -= row_fragment_layout.rect.start_corner;
2057            let writing_mode = self.table.style.writing_mode;
2058            PhysicalRect::new(
2059                if writing_mode.is_horizontal() {
2060                    PhysicalPoint::new(rect.start_corner.inline, rect.start_corner.block)
2061                } else {
2062                    PhysicalPoint::new(rect.start_corner.block, rect.start_corner.inline)
2063                },
2064                rect.size.to_physical_size(writing_mode),
2065            )
2066        };
2067
2068        let column = self.table.columns.get(column_index);
2069        let column_group = column
2070            .and_then(|column| column.borrow().group_index)
2071            .and_then(|index| self.table.column_groups.get(index));
2072        if let Some(column_group) = column_group {
2073            let column_group = column_group.borrow();
2074            let rect = make_relative_to_row_start(dimensions.get_column_group_rect(&column_group));
2075            fragment.add_extra_background(ExtraBackground {
2076                style: column_group.shared_background_style.clone(),
2077                rect,
2078            })
2079        }
2080        if let Some(column) = column {
2081            let column = column.borrow();
2082            if !column.is_anonymous {
2083                let rect = make_relative_to_row_start(dimensions.get_column_rect(column_index));
2084                fragment.add_extra_background(ExtraBackground {
2085                    style: column.shared_background_style.clone(),
2086                    rect,
2087                })
2088            }
2089        }
2090        let row = self.table.rows.get(row_index);
2091        let row_group = row
2092            .and_then(|row| row.borrow().group_index)
2093            .and_then(|index| self.table.row_groups.get(index));
2094        if let Some(row_group) = row_group {
2095            let rect =
2096                make_relative_to_row_start(dimensions.get_row_group_rect(&row_group.borrow()));
2097            fragment.add_extra_background(ExtraBackground {
2098                style: row_group.borrow().shared_background_style.clone(),
2099                rect,
2100            })
2101        }
2102        if let Some(row) = row {
2103            let row = row.borrow();
2104            let rect = make_relative_to_row_start(row_fragment_layout.rect);
2105            fragment.add_extra_background(ExtraBackground {
2106                style: row.shared_background_style.clone(),
2107                rect,
2108            })
2109        }
2110
2111        let fragment = Fragment::Box(ArcRefCell::new(fragment));
2112        cell.base.set_fragment(fragment.clone());
2113        row_fragment_layout.fragments.push(fragment);
2114    }
2115
2116    fn make_fragments_for_columns_and_column_groups(
2117        &self,
2118        dimensions: &TableAndTrackDimensions,
2119        fragments: &mut Vec<Fragment>,
2120    ) {
2121        for column_group in self.table.column_groups.iter() {
2122            let column_group = column_group.borrow();
2123            if !column_group.is_empty() {
2124                let fragment = Fragment::Positioning(PositioningFragment::new_empty(
2125                    column_group.base.base_fragment_info,
2126                    dimensions
2127                        .get_column_group_rect(&column_group)
2128                        .as_physical(None),
2129                    column_group.base.style.clone(),
2130                ));
2131                column_group.base.set_fragment(fragment.clone());
2132                fragments.push(fragment);
2133            }
2134        }
2135
2136        for (column_index, column) in self.table.columns.iter().enumerate() {
2137            let column = column.borrow();
2138            let fragment = Fragment::Positioning(PositioningFragment::new_empty(
2139                column.base.base_fragment_info,
2140                dimensions.get_column_rect(column_index).as_physical(None),
2141                column.base.style.clone(),
2142            ));
2143            column.base.set_fragment(fragment.clone());
2144            fragments.push(fragment);
2145        }
2146    }
2147
2148    fn compute_border_collapse(&mut self, writing_mode: WritingMode) {
2149        if self.table.style.get_inherited_table().border_collapse != BorderCollapse::Collapse {
2150            self.collapsed_borders = None;
2151            return;
2152        }
2153
2154        let mut collapsed_borders = LogicalVec2 {
2155            block: vec![
2156                vec![Default::default(); self.table.size.width];
2157                self.table.size.height + 1
2158            ],
2159            inline: vec![
2160                vec![Default::default(); self.table.size.height];
2161                self.table.size.width + 1
2162            ],
2163        };
2164
2165        let apply_border = |collapsed_borders: &mut CollapsedBorders,
2166                            layout_style: &LayoutStyle,
2167                            block: &Range<usize>,
2168                            inline: &Range<usize>| {
2169            let border = CollapsedBorder::from_layout_style(layout_style, writing_mode);
2170            border
2171                .block_start
2172                .max_assign_to_slice(&mut collapsed_borders.block[block.start][inline.clone()]);
2173            border
2174                .block_end
2175                .max_assign_to_slice(&mut collapsed_borders.block[block.end][inline.clone()]);
2176            border
2177                .inline_start
2178                .max_assign_to_slice(&mut collapsed_borders.inline[inline.start][block.clone()]);
2179            border
2180                .inline_end
2181                .max_assign_to_slice(&mut collapsed_borders.inline[inline.end][block.clone()]);
2182        };
2183        let hide_inner_borders = |collapsed_borders: &mut CollapsedBorders,
2184                                  block: &Range<usize>,
2185                                  inline: &Range<usize>| {
2186            for x in inline.clone() {
2187                for y in block.clone() {
2188                    if x != inline.start {
2189                        collapsed_borders.inline[x][y].hide();
2190                    }
2191                    if y != block.start {
2192                        collapsed_borders.block[y][x].hide();
2193                    }
2194                }
2195            }
2196        };
2197        let all_rows = 0..self.table.size.height;
2198        let all_columns = 0..self.table.size.width;
2199        for row_index in all_rows.clone() {
2200            for column_index in all_columns.clone() {
2201                let cell = match self.table.slots[row_index][column_index] {
2202                    TableSlot::Cell(ref cell) => cell,
2203                    _ => continue,
2204                }
2205                .borrow();
2206                let block_range = row_index..row_index + cell.rowspan;
2207                let inline_range = column_index..column_index + cell.colspan;
2208                hide_inner_borders(&mut collapsed_borders, &block_range, &inline_range);
2209                apply_border(
2210                    &mut collapsed_borders,
2211                    &cell.layout_style(),
2212                    &block_range,
2213                    &inline_range,
2214                );
2215            }
2216        }
2217        for (row_index, row) in self.table.rows.iter().enumerate() {
2218            let row = row.borrow();
2219            apply_border(
2220                &mut collapsed_borders,
2221                &row.layout_style(),
2222                &(row_index..row_index + 1),
2223                &all_columns,
2224            );
2225        }
2226        for row_group in &self.table.row_groups {
2227            let row_group = row_group.borrow();
2228            apply_border(
2229                &mut collapsed_borders,
2230                &row_group.layout_style(),
2231                &row_group.track_range,
2232                &all_columns,
2233            );
2234        }
2235        for (column_index, column) in self.table.columns.iter().enumerate() {
2236            let column = column.borrow();
2237            apply_border(
2238                &mut collapsed_borders,
2239                &column.layout_style(),
2240                &all_rows,
2241                &(column_index..column_index + 1),
2242            );
2243        }
2244        for column_group in &self.table.column_groups {
2245            let column_group = column_group.borrow();
2246            apply_border(
2247                &mut collapsed_borders,
2248                &column_group.layout_style(),
2249                &all_rows,
2250                &column_group.track_range,
2251            );
2252        }
2253        apply_border(
2254            &mut collapsed_borders,
2255            &self.table.layout_style_for_grid(),
2256            &all_rows,
2257            &all_columns,
2258        );
2259
2260        self.collapsed_borders = Some(collapsed_borders);
2261    }
2262
2263    fn get_collapsed_border_widths_for_area(
2264        &self,
2265        area: LogicalSides<usize>,
2266    ) -> Option<LogicalSides<Au>> {
2267        let collapsed_borders = self.collapsed_borders.as_ref()?;
2268        let columns = || area.inline_start..area.inline_end;
2269        let rows = || area.block_start..area.block_end;
2270        let max_width = |slice: &[CollapsedBorder]| {
2271            let slice_widths = slice.iter().map(|collapsed_border| collapsed_border.width);
2272            slice_widths.max().unwrap_or_default()
2273        };
2274        Some(area.map_inline_and_block_axes(
2275            |column| max_width(&collapsed_borders.inline[*column][rows()]) / 2,
2276            |row| max_width(&collapsed_borders.block[*row][columns()]) / 2,
2277        ))
2278    }
2279}
2280
2281struct RowFragmentLayout<'a> {
2282    row: &'a TableTrack,
2283    rect: LogicalRect<Au>,
2284    containing_block: ContainingBlock<'a>,
2285    positioning_context: Option<PositioningContext>,
2286    fragments: Vec<Fragment>,
2287}
2288
2289impl<'a> RowFragmentLayout<'a> {
2290    fn new(
2291        table_row: &'a TableTrack,
2292        index: usize,
2293        dimensions: &TableAndTrackDimensions,
2294        table_style: &'a ComputedValues,
2295    ) -> Self {
2296        let rect = dimensions.get_row_rect(index);
2297        let containing_block = ContainingBlock {
2298            size: ContainingBlockSize {
2299                inline: rect.size.inline,
2300                block: SizeConstraint::Definite(rect.size.block),
2301            },
2302            style: table_style,
2303        };
2304        Self {
2305            row: table_row,
2306            rect,
2307            positioning_context: PositioningContext::new_for_layout_box_base(&table_row.base),
2308            containing_block,
2309            fragments: Vec::new(),
2310        }
2311    }
2312    fn finish(
2313        mut self,
2314        layout_context: &LayoutContext,
2315        table_positioning_context: &mut PositioningContext,
2316        containing_block_for_logical_conversion: &ContainingBlock,
2317        containing_block_for_children: &ContainingBlock,
2318        row_group_fragment_layout: &mut Option<RowGroupFragmentLayout>,
2319    ) -> Fragment {
2320        if self.positioning_context.is_some() {
2321            self.rect.start_corner +=
2322                relative_adjustement(&self.row.base.style, containing_block_for_children);
2323        }
2324
2325        let (inline_size, block_size) = if let Some(row_group_layout) = row_group_fragment_layout {
2326            self.rect.start_corner -= row_group_layout.rect.start_corner;
2327            (
2328                row_group_layout.rect.size.inline,
2329                SizeConstraint::Definite(row_group_layout.rect.size.block),
2330            )
2331        } else {
2332            (
2333                containing_block_for_logical_conversion.size.inline,
2334                containing_block_for_logical_conversion.size.block,
2335            )
2336        };
2337
2338        let row_group_containing_block = ContainingBlock {
2339            size: ContainingBlockSize {
2340                inline: inline_size,
2341                block: block_size,
2342            },
2343            style: containing_block_for_logical_conversion.style,
2344        };
2345
2346        let mut row_fragment = BoxFragment::new(
2347            self.row.base.base_fragment_info,
2348            self.row.base.style.clone(),
2349            self.fragments,
2350            self.rect.as_physical(Some(&row_group_containing_block)),
2351            PhysicalSides::zero(), /* padding */
2352            PhysicalSides::zero(), /* border */
2353            PhysicalSides::zero(), /* margin */
2354            None,                  /* specific_layout_info */
2355        );
2356        row_fragment.set_does_not_paint_background();
2357
2358        if let Some(mut row_positioning_context) = self.positioning_context.take() {
2359            row_positioning_context.layout_collected_children(layout_context, &mut row_fragment);
2360
2361            let parent_positioning_context = row_group_fragment_layout
2362                .as_mut()
2363                .and_then(|layout| layout.positioning_context.as_mut())
2364                .unwrap_or(table_positioning_context);
2365            parent_positioning_context.append(row_positioning_context);
2366        }
2367
2368        let fragment = Fragment::Box(ArcRefCell::new(row_fragment));
2369        self.row.base.set_fragment(fragment.clone());
2370        fragment
2371    }
2372}
2373
2374struct RowGroupFragmentLayout {
2375    row_group: ArcRefCell<TableTrackGroup>,
2376    rect: LogicalRect<Au>,
2377    positioning_context: Option<PositioningContext>,
2378    index: usize,
2379    fragments: Vec<Fragment>,
2380}
2381
2382impl RowGroupFragmentLayout {
2383    fn new(
2384        row_group: ArcRefCell<TableTrackGroup>,
2385        index: usize,
2386        dimensions: &TableAndTrackDimensions,
2387    ) -> Self {
2388        let (rect, positioning_context) = {
2389            let row_group = row_group.borrow();
2390            (
2391                dimensions.get_row_group_rect(&row_group),
2392                PositioningContext::new_for_layout_box_base(&row_group.base),
2393            )
2394        };
2395        Self {
2396            row_group,
2397            rect,
2398            positioning_context,
2399            index,
2400            fragments: Vec::new(),
2401        }
2402    }
2403
2404    fn finish(
2405        mut self,
2406        layout_context: &LayoutContext,
2407        table_positioning_context: &mut PositioningContext,
2408        containing_block_for_logical_conversion: &ContainingBlock,
2409        containing_block_for_children: &ContainingBlock,
2410    ) -> Fragment {
2411        let row_group = self.row_group.borrow();
2412        if self.positioning_context.is_some() {
2413            self.rect.start_corner +=
2414                relative_adjustement(&row_group.base.style, containing_block_for_children);
2415        }
2416
2417        let mut row_group_fragment = BoxFragment::new(
2418            row_group.base.base_fragment_info,
2419            row_group.base.style.clone(),
2420            self.fragments,
2421            self.rect
2422                .as_physical(Some(containing_block_for_logical_conversion)),
2423            PhysicalSides::zero(), /* padding */
2424            PhysicalSides::zero(), /* border */
2425            PhysicalSides::zero(), /* margin */
2426            None,                  /* specific_layout_info */
2427        );
2428        row_group_fragment.set_does_not_paint_background();
2429
2430        if let Some(mut row_positioning_context) = self.positioning_context.take() {
2431            row_positioning_context
2432                .layout_collected_children(layout_context, &mut row_group_fragment);
2433            table_positioning_context.append(row_positioning_context);
2434        }
2435
2436        let fragment = Fragment::Box(ArcRefCell::new(row_group_fragment));
2437        row_group.base.set_fragment(fragment.clone());
2438        fragment
2439    }
2440}
2441
2442struct TableAndTrackDimensions {
2443    /// The rect of the full table, not counting for borders, padding, and margin.
2444    table_rect: LogicalRect<Au>,
2445    /// The rect of the full table, not counting for borders, padding, and margin
2446    /// and offset by any border spacing and caption.
2447    table_cells_rect: LogicalRect<Au>,
2448    /// The min and max block offsets of each table row.
2449    row_dimensions: Vec<(Au, Au)>,
2450    /// The min and max inline offsets of each table column
2451    column_dimensions: Vec<(Au, Au)>,
2452}
2453
2454impl TableAndTrackDimensions {
2455    fn new(table_layout: &TableLayout) -> Self {
2456        let border_spacing = table_layout.table.border_spacing();
2457
2458        // The sizes used for a dimension when that dimension has no table tracks.
2459        let fallback_inline_size = table_layout.assignable_width;
2460        let fallback_block_size = table_layout.final_table_height;
2461
2462        let mut column_dimensions = Vec::new();
2463        let mut column_offset = Au::zero();
2464        for column_index in 0..table_layout.table.size.width {
2465            if table_layout.is_column_collapsed(column_index) {
2466                column_dimensions.push((column_offset, column_offset));
2467                continue;
2468            }
2469            let start_offset = column_offset + border_spacing.inline;
2470            let end_offset = start_offset + table_layout.distributed_column_widths[column_index];
2471            column_dimensions.push((start_offset, end_offset));
2472            column_offset = end_offset;
2473        }
2474        column_offset += if table_layout.table.size.width == 0 {
2475            fallback_inline_size
2476        } else {
2477            border_spacing.inline
2478        };
2479
2480        let mut row_dimensions = Vec::new();
2481        let mut row_offset = Au::zero();
2482        for row_index in 0..table_layout.table.size.height {
2483            if table_layout.is_row_collapsed(row_index) {
2484                row_dimensions.push((row_offset, row_offset));
2485                continue;
2486            }
2487            let start_offset = row_offset + border_spacing.block;
2488            let end_offset = start_offset + table_layout.row_sizes[row_index];
2489            row_dimensions.push((start_offset, end_offset));
2490            row_offset = end_offset;
2491        }
2492        row_offset += if table_layout.table.size.height == 0 {
2493            fallback_block_size
2494        } else {
2495            border_spacing.block
2496        };
2497
2498        let table_start_corner = LogicalVec2 {
2499            inline: column_dimensions.first().map_or_else(Au::zero, |v| v.0),
2500            block: row_dimensions.first().map_or_else(Au::zero, |v| v.0),
2501        };
2502        let table_size = LogicalVec2 {
2503            inline: column_dimensions
2504                .last()
2505                .map_or(fallback_inline_size, |v| v.1),
2506            block: row_dimensions.last().map_or(fallback_block_size, |v| v.1),
2507        } - table_start_corner;
2508        let table_cells_rect = LogicalRect {
2509            start_corner: table_start_corner,
2510            size: table_size,
2511        };
2512
2513        let table_rect = LogicalRect {
2514            start_corner: LogicalVec2::zero(),
2515            size: LogicalVec2 {
2516                inline: column_offset,
2517                block: row_offset,
2518            },
2519        };
2520
2521        Self {
2522            table_rect,
2523            table_cells_rect,
2524            row_dimensions,
2525            column_dimensions,
2526        }
2527    }
2528
2529    fn get_row_rect(&self, row_index: usize) -> LogicalRect<Au> {
2530        let mut row_rect = self.table_cells_rect;
2531        let row_dimensions = self.row_dimensions[row_index];
2532        row_rect.start_corner.block = row_dimensions.0;
2533        row_rect.size.block = row_dimensions.1 - row_dimensions.0;
2534        row_rect
2535    }
2536
2537    fn get_column_rect(&self, column_index: usize) -> LogicalRect<Au> {
2538        let mut row_rect = self.table_cells_rect;
2539        let column_dimensions = self.column_dimensions[column_index];
2540        row_rect.start_corner.inline = column_dimensions.0;
2541        row_rect.size.inline = column_dimensions.1 - column_dimensions.0;
2542        row_rect
2543    }
2544
2545    fn get_row_group_rect(&self, row_group: &TableTrackGroup) -> LogicalRect<Au> {
2546        if row_group.is_empty() {
2547            return LogicalRect::zero();
2548        }
2549
2550        let mut row_group_rect = self.table_cells_rect;
2551        let block_start = self.row_dimensions[row_group.track_range.start].0;
2552        let block_end = self.row_dimensions[row_group.track_range.end - 1].1;
2553        row_group_rect.start_corner.block = block_start;
2554        row_group_rect.size.block = block_end - block_start;
2555        row_group_rect
2556    }
2557
2558    fn get_column_group_rect(&self, column_group: &TableTrackGroup) -> LogicalRect<Au> {
2559        if column_group.is_empty() {
2560            return LogicalRect::zero();
2561        }
2562
2563        let mut column_group_rect = self.table_cells_rect;
2564        let inline_start = self.column_dimensions[column_group.track_range.start].0;
2565        let inline_end = self.column_dimensions[column_group.track_range.end - 1].1;
2566        column_group_rect.start_corner.inline = inline_start;
2567        column_group_rect.size.inline = inline_end - inline_start;
2568        column_group_rect
2569    }
2570
2571    fn get_cell_rect(
2572        &self,
2573        coordinates: TableSlotCoordinates,
2574        rowspan: usize,
2575        colspan: usize,
2576    ) -> LogicalRect<Au> {
2577        let start_corner = LogicalVec2 {
2578            inline: self.column_dimensions[coordinates.x].0,
2579            block: self.row_dimensions[coordinates.y].0,
2580        };
2581        let size = LogicalVec2 {
2582            inline: self.column_dimensions[coordinates.x + colspan - 1].1,
2583            block: self.row_dimensions[coordinates.y + rowspan - 1].1,
2584        } - start_corner;
2585        LogicalRect { start_corner, size }
2586    }
2587}
2588
2589impl Table {
2590    fn border_spacing(&self) -> LogicalVec2<Au> {
2591        if self.style.clone_border_collapse() == BorderCollapse::Collapse {
2592            LogicalVec2::zero()
2593        } else {
2594            let border_spacing = self.style.clone_border_spacing();
2595            LogicalVec2 {
2596                inline: border_spacing.horizontal(),
2597                block: border_spacing.vertical(),
2598            }
2599        }
2600    }
2601
2602    fn total_border_spacing(&self) -> LogicalVec2<Au> {
2603        let border_spacing = self.border_spacing();
2604        LogicalVec2 {
2605            inline: if self.size.width > 0 {
2606                border_spacing.inline * (self.size.width as i32 + 1)
2607            } else {
2608                Au::zero()
2609            },
2610            block: if self.size.height > 0 {
2611                border_spacing.block * (self.size.height as i32 + 1)
2612            } else {
2613                Au::zero()
2614            },
2615        }
2616    }
2617
2618    fn get_column_measure_for_column_at_index(
2619        &self,
2620        writing_mode: WritingMode,
2621        column_index: usize,
2622        is_in_fixed_mode: bool,
2623    ) -> CellOrTrackMeasure {
2624        let column = match self.columns.get(column_index) {
2625            Some(column) => column,
2626            None => return CellOrTrackMeasure::zero(),
2627        }
2628        .borrow();
2629
2630        let CellOrColumnOuterSizes {
2631            preferred: preferred_size,
2632            min: min_size,
2633            max: max_size,
2634            percentage: percentage_size,
2635        } = CellOrColumnOuterSizes::new(
2636            &column.base.style,
2637            writing_mode,
2638            &Default::default(),
2639            is_in_fixed_mode,
2640        );
2641
2642        CellOrTrackMeasure {
2643            content_sizes: ContentSizes {
2644                // > The outer min-content width of a table-column or table-column-group is
2645                // > max(min-width, width).
2646                // But that's clearly wrong, since it would be equal to or greater than
2647                // the outer max-content width. So we match other browsers instead.
2648                min_content: min_size.inline,
2649                // > The outer max-content width of a table-column or table-column-group is
2650                // > max(min-width, min(max-width, width)).
2651                // This matches Gecko, but Blink and WebKit ignore max_size.
2652                max_content: preferred_size
2653                    .inline
2654                    .clamp_between_extremums(min_size.inline, max_size.inline),
2655            },
2656            percentage: percentage_size.inline,
2657        }
2658    }
2659
2660    fn get_row_measure_for_row_at_index(
2661        &self,
2662        writing_mode: WritingMode,
2663        row_index: usize,
2664    ) -> CellOrTrackMeasure {
2665        let row = match self.rows.get(row_index) {
2666            Some(row) => row,
2667            None => return CellOrTrackMeasure::zero(),
2668        };
2669
2670        // In the block axis, the min-content and max-content sizes are the same
2671        // (except for new layout boxes like grid and flex containers). Note that
2672        // other browsers don't seem to use the min and max sizing properties here.
2673        let row = row.borrow();
2674        let size = row.base.style.box_size(writing_mode);
2675        let max_size = row.base.style.max_box_size(writing_mode);
2676        let percentage_contribution = get_size_percentage_contribution(&size, &max_size);
2677
2678        CellOrTrackMeasure {
2679            content_sizes: size
2680                .block
2681                .to_numeric()
2682                .and_then(|size| size.to_length())
2683                .map_or_else(Au::zero, Au::from)
2684                .into(),
2685            percentage: percentage_contribution.block,
2686        }
2687    }
2688
2689    pub(crate) fn layout(
2690        &self,
2691        layout_context: &LayoutContext,
2692        positioning_context: &mut PositioningContext,
2693        containing_block_for_children: &ContainingBlock,
2694        containing_block_for_table: &ContainingBlock,
2695    ) -> CacheableLayoutResult {
2696        TableLayout::new(self).layout(
2697            layout_context,
2698            positioning_context,
2699            containing_block_for_children,
2700            containing_block_for_table,
2701        )
2702    }
2703}
2704
2705impl ComputeInlineContentSizes for Table {
2706    #[servo_tracing::instrument(name = "Table::compute_inline_content_sizes", skip_all)]
2707    fn compute_inline_content_sizes(
2708        &self,
2709        layout_context: &LayoutContext,
2710        constraint_space: &ConstraintSpace,
2711    ) -> InlineContentSizesResult {
2712        let writing_mode = constraint_space.writing_mode;
2713        let mut layout = TableLayout::new(self);
2714        layout.compute_border_collapse(writing_mode);
2715        layout.pbm = self
2716            .layout_style(Some(&layout))
2717            .padding_border_margin_with_writing_mode_and_containing_block_inline_size(
2718                writing_mode,
2719                Au::zero(),
2720            );
2721        layout.compute_measures(layout_context, writing_mode);
2722
2723        let grid_content_sizes = layout.compute_grid_min_max();
2724
2725        // Padding and border should apply to the table grid, but they will be taken into
2726        // account when computing the inline content sizes of the table wrapper (our parent), so
2727        // this code removes their contribution from the inline content size of the caption.
2728        let caption_content_sizes = ContentSizes::from(
2729            layout.compute_caption_minimum_inline_size(layout_context) -
2730                layout.pbm.padding_border_sums.inline,
2731        );
2732
2733        InlineContentSizesResult {
2734            sizes: grid_content_sizes.max(caption_content_sizes),
2735            depends_on_block_constraints: false,
2736        }
2737    }
2738}
2739
2740impl Table {
2741    #[inline]
2742    pub(crate) fn layout_style<'a>(
2743        &'a self,
2744        layout: Option<&'a TableLayout<'a>>,
2745    ) -> LayoutStyle<'a> {
2746        LayoutStyle::Table(TableLayoutStyle {
2747            table: self,
2748            layout,
2749        })
2750    }
2751
2752    #[inline]
2753    pub(crate) fn layout_style_for_grid(&self) -> LayoutStyle<'_> {
2754        LayoutStyle::Default(&self.grid_style)
2755    }
2756}
2757
2758impl TableTrack {
2759    #[inline]
2760    pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
2761        LayoutStyle::Default(&self.base.style)
2762    }
2763}
2764
2765impl TableTrackGroup {
2766    #[inline]
2767    pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
2768        LayoutStyle::Default(&self.base.style)
2769    }
2770}
2771
2772impl TableLayoutStyle<'_> {
2773    #[inline]
2774    pub(crate) fn style(&self) -> &ComputedValues {
2775        &self.table.style
2776    }
2777
2778    #[inline]
2779    pub(crate) fn collapses_borders(&self) -> bool {
2780        self.style().get_inherited_table().border_collapse == BorderCollapse::Collapse
2781    }
2782
2783    pub(crate) fn halved_collapsed_border_widths(&self) -> LogicalSides<Au> {
2784        debug_assert!(self.collapses_borders());
2785        let area = LogicalSides {
2786            inline_start: 0,
2787            inline_end: self.table.size.width,
2788            block_start: 0,
2789            block_end: self.table.size.height,
2790        };
2791        if let Some(layout) = self.layout {
2792            layout.get_collapsed_border_widths_for_area(area)
2793        } else {
2794            // TODO: this should be cached.
2795            let mut layout = TableLayout::new(self.table);
2796            layout.compute_border_collapse(self.style().writing_mode);
2797            layout.get_collapsed_border_widths_for_area(area)
2798        }
2799        .expect("Collapsed borders should be computed")
2800    }
2801}
2802
2803impl TableSlotCell {
2804    #[inline]
2805    fn layout_style(&self) -> LayoutStyle<'_> {
2806        self.contents.layout_style(&self.base)
2807    }
2808
2809    fn effective_vertical_align(&self) -> VerticalAlignKeyword {
2810        match self.base.style.clone_vertical_align() {
2811            VerticalAlign::Keyword(VerticalAlignKeyword::Top) => VerticalAlignKeyword::Top,
2812            VerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => VerticalAlignKeyword::Bottom,
2813            VerticalAlign::Keyword(VerticalAlignKeyword::Middle) => VerticalAlignKeyword::Middle,
2814            _ => VerticalAlignKeyword::Baseline,
2815        }
2816    }
2817
2818    fn inline_content_sizes(&self, layout_context: &LayoutContext) -> ContentSizes {
2819        let constraint_space = ConstraintSpace::new_for_style_and_ratio(
2820            &self.base.style,
2821            None, /* TODO: support preferred aspect ratios on non-replaced boxes */
2822        );
2823        self.base
2824            .inline_content_sizes(layout_context, &constraint_space, &self.contents.contents)
2825            .sizes
2826    }
2827
2828    #[allow(clippy::too_many_arguments)]
2829    fn create_fragment(
2830        &self,
2831        mut layout: CellLayout,
2832        cell_rect: LogicalRect<Au>,
2833        cell_baseline: Au,
2834        positioning_context: &mut PositioningContext,
2835        table_style: &ComputedValues,
2836        containing_block: &ContainingBlock,
2837        is_collapsed: bool,
2838    ) -> BoxFragment {
2839        // This must be scoped to this function because it conflicts with euclid's Zero.
2840        use style::Zero as StyleZero;
2841
2842        let cell_content_rect = cell_rect.deflate(&(layout.padding + layout.border));
2843        let content_block_size = layout.layout.content_block_size;
2844        let vertical_align_offset = match self.effective_vertical_align() {
2845            VerticalAlignKeyword::Top => Au::zero(),
2846            VerticalAlignKeyword::Bottom => cell_content_rect.size.block - content_block_size,
2847            VerticalAlignKeyword::Middle => {
2848                (cell_content_rect.size.block - content_block_size).scale_by(0.5)
2849            },
2850            _ => {
2851                cell_baseline -
2852                    (layout.padding.block_start + layout.border.block_start) -
2853                    layout.ascent()
2854            },
2855        };
2856
2857        let mut base_fragment_info = self.base.base_fragment_info;
2858        if self.base.style.get_inherited_table().empty_cells == EmptyCells::Hide &&
2859            table_style.get_inherited_table().border_collapse != BorderCollapse::Collapse &&
2860            layout.is_empty_for_empty_cells()
2861        {
2862            base_fragment_info.flags.insert(FragmentFlags::DO_NOT_PAINT);
2863        }
2864
2865        if is_collapsed {
2866            base_fragment_info.flags.insert(FragmentFlags::IS_COLLAPSED);
2867        }
2868
2869        // Create an `AnonymousFragment` to move the cell contents to the cell baseline.
2870        let mut vertical_align_fragment_rect = cell_content_rect;
2871        vertical_align_fragment_rect.start_corner = LogicalVec2 {
2872            inline: Au::zero(),
2873            block: vertical_align_offset,
2874        };
2875        let vertical_align_fragment = PositioningFragment::new_anonymous(
2876            self.base.style.clone(),
2877            vertical_align_fragment_rect.as_physical(None),
2878            layout.layout.fragments,
2879        );
2880
2881        // Adjust the static position of all absolute children based on the
2882        // final content rect of this fragment. Note that we are not shifting by the position of the
2883        // Anonymous fragment we use to shift content to the baseline.
2884        //
2885        // TODO(mrobinson): This is correct for absolutes that are direct children of the table
2886        // cell, but wrong for absolute fragments that are more deeply nested in the hierarchy of
2887        // fragments.
2888        let physical_cell_rect = cell_content_rect.as_physical(Some(containing_block));
2889        layout
2890            .positioning_context
2891            .adjust_static_position_of_hoisted_fragments_with_offset(
2892                &physical_cell_rect.origin.to_vector(),
2893                PositioningContextLength::zero(),
2894            );
2895        positioning_context.append(layout.positioning_context);
2896
2897        let specific_layout_info = (table_style.get_inherited_table().border_collapse ==
2898            BorderCollapse::Collapse)
2899            .then_some(SpecificLayoutInfo::TableCellWithCollapsedBorders);
2900
2901        BoxFragment::new(
2902            base_fragment_info,
2903            self.base.style.clone(),
2904            vec![Fragment::Positioning(vertical_align_fragment)],
2905            physical_cell_rect,
2906            layout.padding.to_physical(table_style.writing_mode),
2907            layout.border.to_physical(table_style.writing_mode),
2908            PhysicalSides::zero(), /* margin */
2909            specific_layout_info,
2910        )
2911        .with_baselines(layout.layout.baselines)
2912    }
2913}
2914
2915fn get_size_percentage_contribution(
2916    size: &LogicalVec2<Size<ComputedLengthPercentage>>,
2917    max_size: &LogicalVec2<Size<ComputedLengthPercentage>>,
2918) -> LogicalVec2<Option<Percentage>> {
2919    // From <https://drafts.csswg.org/css-tables/#percentage-contribution>
2920    // > The percentage contribution of a table cell, column, or column group is defined
2921    // > in terms of the computed values of width and max-width that have computed values
2922    // > that are percentages:
2923    // >    min(percentage width, percentage max-width).
2924    // > If the computed values are not percentages, then 0% is used for width, and an
2925    // > infinite percentage is used for max-width.
2926    LogicalVec2 {
2927        inline: max_two_optional_percentages(
2928            size.inline.to_percentage(),
2929            max_size.inline.to_percentage(),
2930        ),
2931        block: max_two_optional_percentages(
2932            size.block.to_percentage(),
2933            max_size.block.to_percentage(),
2934        ),
2935    }
2936}
2937
2938struct CellOrColumnOuterSizes {
2939    min: LogicalVec2<Au>,
2940    preferred: LogicalVec2<Au>,
2941    max: LogicalVec2<Option<Au>>,
2942    percentage: LogicalVec2<Option<Percentage>>,
2943}
2944
2945impl CellOrColumnOuterSizes {
2946    fn new(
2947        style: &Arc<ComputedValues>,
2948        writing_mode: WritingMode,
2949        padding_border_sums: &LogicalVec2<Au>,
2950        is_in_fixed_mode: bool,
2951    ) -> Self {
2952        let box_sizing = style.get_position().box_sizing;
2953        let outer_size = |size: LogicalVec2<Au>| match box_sizing {
2954            BoxSizing::ContentBox => size + *padding_border_sums,
2955            BoxSizing::BorderBox => LogicalVec2 {
2956                inline: size.inline.max(padding_border_sums.inline),
2957                block: size.block.max(padding_border_sums.block),
2958            },
2959        };
2960
2961        let outer_option_size = |size: LogicalVec2<Option<Au>>| match box_sizing {
2962            BoxSizing::ContentBox => size.map_inline_and_block_axes(
2963                |inline| inline.map(|inline| inline + padding_border_sums.inline),
2964                |block| block.map(|block| block + padding_border_sums.block),
2965            ),
2966            BoxSizing::BorderBox => size.map_inline_and_block_axes(
2967                |inline| inline.map(|inline| inline.max(padding_border_sums.inline)),
2968                |block| block.map(|block| block.max(padding_border_sums.block)),
2969            ),
2970        };
2971
2972        let get_size_for_axis = |size: &Size<ComputedLengthPercentage>| {
2973            // Note that measures treat all size values other than <length>
2974            // as the initial value of the property.
2975            size.to_numeric()
2976                .and_then(|length_percentage| length_percentage.to_length())
2977                .map(Au::from)
2978        };
2979
2980        let size = style.box_size(writing_mode);
2981        if is_in_fixed_mode {
2982            return Self {
2983                percentage: size.map(|v| v.to_percentage()),
2984                preferred: outer_option_size(size.map(get_size_for_axis))
2985                    .map(|v| v.unwrap_or_default()),
2986                min: LogicalVec2::default(),
2987                max: LogicalVec2::default(),
2988            };
2989        }
2990
2991        let min_size = style.min_box_size(writing_mode);
2992        let max_size = style.max_box_size(writing_mode);
2993
2994        Self {
2995            min: outer_size(min_size.map(|v| get_size_for_axis(v).unwrap_or_default())),
2996            preferred: outer_size(size.map(|v| get_size_for_axis(v).unwrap_or_default())),
2997            max: outer_option_size(max_size.map(get_size_for_axis)),
2998            percentage: get_size_percentage_contribution(&size, &max_size),
2999        }
3000    }
3001}
3002
3003struct RowspanToDistribute<'a> {
3004    coordinates: TableSlotCoordinates,
3005    cell: AtomicRef<'a, TableSlotCell>,
3006    measure: &'a CellOrTrackMeasure,
3007}
3008
3009impl RowspanToDistribute<'_> {
3010    fn range(&self) -> Range<usize> {
3011        self.coordinates.y..self.coordinates.y + self.cell.rowspan
3012    }
3013
3014    fn fully_encloses(&self, other: &RowspanToDistribute) -> bool {
3015        other.coordinates.y > self.coordinates.y && other.range().end < self.range().end
3016    }
3017}
3018
3019/// The inline size constraints provided by a cell that span multiple columns (`colspan` > 1).
3020/// These constraints are distributed to the individual columns that make up this cell's span.
3021#[derive(Debug)]
3022struct ColspanToDistribute {
3023    starting_column: usize,
3024    span: usize,
3025    content_sizes: ContentSizes,
3026    percentage: Option<Percentage>,
3027}
3028
3029impl ColspanToDistribute {
3030    /// A comparison function to sort the colspan cell constraints primarily by their span
3031    /// width and secondarily by their starting column. This is not an implementation of
3032    /// `PartialOrd` because we want to return [`Ordering::Equal`] even if `self != other`.
3033    fn comparison_for_sort(a: &Self, b: &Self) -> Ordering {
3034        a.span
3035            .cmp(&b.span)
3036            .then_with(|| b.starting_column.cmp(&b.starting_column))
3037    }
3038
3039    fn range(&self) -> Range<usize> {
3040        self.starting_column..self.starting_column + self.span
3041    }
3042}