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