Skip to main content

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