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