1use 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
67struct 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 fn outer_block_size(&self) -> Au {
87 self.layout.content_block_size + self.border.block_sum() + self.padding.block_sum()
88 }
89
90 fn is_empty(&self) -> bool {
93 self.layout.fragments.is_empty()
94 }
95
96 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#[derive(Clone, Debug, Default)]
107struct RowLayout {
108 constrained: bool,
109 has_cell_with_span_greater_than_one: bool,
110 percent: Percentage,
111}
112
113#[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
176impl 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
200pub(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 table_width: Au,
212 assignable_width: Au,
215 final_table_height: Au,
216 distributed_column_widths: Vec<Au>,
217 row_sizes: Vec<Au>,
218 row_baselines: Vec<Au>,
220 cells_laid_out: Vec<Vec<Option<CellLayout>>>,
221 basis_for_cell_padding_percentage: Au,
222 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 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 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 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 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 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 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 fn compute_column_measures(&mut self, writing_mode: WritingMode) {
476 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 column.incorporate_cell_measure(cell_measure);
541 }
542 }
543
544 colspan_cell_constraints.sort_by(ColspanToDistribute::comparison_for_sort);
546
547 self.distribute_colspanned_cells_to_columns(colspan_cell_constraints);
549
550 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 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 fn compute_grid_min_max(&self) -> ContentSizes {
658 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 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 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, )
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 self.table_width = containing_block_for_children.size.inline;
753
754 self.assignable_width = self.table_width - self.table.total_border_spacing().inline;
758
759 self.basis_for_cell_padding_percentage =
762 self.table_width - self.table.border_spacing().inline * 2;
763 }
764
765 fn distribute_width_to_columns(target_inline_size: Au, columns: &[ColumnLayout]) -> Vec<Au> {
768 if columns.is_empty() {
771 return Vec::new();
772 }
773
774 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 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 let weight_a = (target_inline_size - sum_b).to_f32_px() / (sum_a - sum_b).to_f32_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 = guess_a.scale_by(weight_a) + guess_b.scale_by(weight_b);
875 let column_width = column_width.min(remaining_assignable_width);
878 remaining_assignable_width -= column_width;
879 column_width
880 })
881 .collect();
882
883 if !remaining_assignable_width.is_zero() {
884 debug_assert!(
888 remaining_assignable_width >= Au::zero(),
889 "Sum of columns shouldn't exceed the assignable table width"
890 );
891 debug_assert!(
892 remaining_assignable_width <= Au::new(widths.len() as i32),
893 "A deviation of more than one Au per column is unlikely to be caused by float imprecision"
894 );
895
896 widths[0] += remaining_assignable_width;
899 }
900
901 debug_assert!(widths.iter().sum::<Au>() == target_inline_size);
902
903 widths
904 };
905
906 if bounds(min_content_sizes_sum, min_content_percentage_sizing_sum) {
907 return blend(
908 &min_content_sizing_guesses,
909 min_content_sizes_sum,
910 &min_content_percentage_sizing_guesses,
911 min_content_percentage_sizing_sum,
912 );
913 }
914
915 if bounds(
916 min_content_percentage_sizing_sum,
917 min_content_specified_sizing_sum,
918 ) {
919 return blend(
920 &min_content_percentage_sizing_guesses,
921 min_content_percentage_sizing_sum,
922 &min_content_specified_sizing_guesses,
923 min_content_specified_sizing_sum,
924 );
925 }
926
927 assert!(bounds(
928 min_content_specified_sizing_sum,
929 max_content_sizing_sum
930 ));
931 blend(
932 &min_content_specified_sizing_guesses,
933 min_content_specified_sizing_sum,
934 &max_content_sizing_guesses,
935 max_content_sizing_sum,
936 )
937 }
938
939 fn distribute_extra_width_to_columns(
942 columns: &[ColumnLayout],
943 column_sizes: &mut [Au],
944 column_sizes_sum: Au,
945 assignable_width: Au,
946 ) {
947 let all_columns = 0..columns.len();
948 let extra_inline_size = assignable_width - column_sizes_sum;
949
950 let has_originating_cells =
951 |column_index: &usize| columns[*column_index].has_originating_cells;
952 let is_constrained = |column_index: &usize| columns[*column_index].constrained;
953 let is_unconstrained = |column_index: &usize| !is_constrained(column_index);
954 let has_percent_greater_than_zero = |column_index: &usize| {
955 columns[*column_index]
956 .percentage
957 .is_some_and(|percentage| percentage.0 > 0.)
958 };
959 let has_percent_zero = |column_index: &usize| !has_percent_greater_than_zero(column_index);
960 let has_max_content =
961 |column_index: &usize| !columns[*column_index].content_sizes.max_content.is_zero();
962
963 let max_content_sum = |column_index: usize| columns[column_index].content_sizes.max_content;
964
965 let unconstrained_max_content_columns = all_columns
971 .clone()
972 .filter(is_unconstrained)
973 .filter(has_originating_cells)
974 .filter(has_percent_zero)
975 .filter(has_max_content);
976 let total_max_content_width: Au = unconstrained_max_content_columns
977 .clone()
978 .map(max_content_sum)
979 .sum();
980 if !total_max_content_width.is_zero() {
981 for column_index in unconstrained_max_content_columns {
982 column_sizes[column_index] += extra_inline_size.scale_by(
983 columns[column_index].content_sizes.max_content.to_f32_px() /
984 total_max_content_width.to_f32_px(),
985 );
986 }
987 return;
988 }
989
990 let unconstrained_no_percent_columns = all_columns
996 .clone()
997 .filter(is_unconstrained)
998 .filter(has_originating_cells)
999 .filter(has_percent_zero);
1000 let total_unconstrained_no_percent = unconstrained_no_percent_columns.clone().count();
1001 if total_unconstrained_no_percent > 0 {
1002 let extra_space_per_column =
1003 extra_inline_size.scale_by(1.0 / total_unconstrained_no_percent as f32);
1004 for column_index in unconstrained_no_percent_columns {
1005 column_sizes[column_index] += extra_space_per_column;
1006 }
1007 return;
1008 }
1009
1010 let constrained_max_content_columns = all_columns
1016 .clone()
1017 .filter(is_constrained)
1018 .filter(has_originating_cells)
1019 .filter(has_percent_zero)
1020 .filter(has_max_content);
1021 let total_max_content_width: Au = constrained_max_content_columns
1022 .clone()
1023 .map(max_content_sum)
1024 .sum();
1025 if !total_max_content_width.is_zero() {
1026 for column_index in constrained_max_content_columns {
1027 column_sizes[column_index] += extra_inline_size.scale_by(
1028 columns[column_index].content_sizes.max_content.to_f32_px() /
1029 total_max_content_width.to_f32_px(),
1030 );
1031 }
1032 return;
1033 }
1034
1035 let columns_with_percentage = all_columns.clone().filter(has_percent_greater_than_zero);
1041 let total_percent = columns_with_percentage
1042 .clone()
1043 .map(|column_index| columns[column_index].percentage.unwrap_or_default().0)
1044 .sum::<f32>();
1045 if total_percent > 0. {
1046 for column_index in columns_with_percentage {
1047 let column_percentage = columns[column_index].percentage.unwrap_or_default();
1048 column_sizes[column_index] +=
1049 extra_inline_size.scale_by(column_percentage.0 / total_percent);
1050 }
1051 return;
1052 }
1053
1054 let has_originating_cells_columns = all_columns.filter(has_originating_cells);
1058 let total_has_originating_cells = has_originating_cells_columns.clone().count();
1059 if total_has_originating_cells > 0 {
1060 let extra_space_per_column =
1061 extra_inline_size.scale_by(1.0 / total_has_originating_cells as f32);
1062 for column_index in has_originating_cells_columns {
1063 column_sizes[column_index] += extra_space_per_column;
1064 }
1065 return;
1066 }
1067
1068 let extra_space_for_all_columns = extra_inline_size.scale_by(1.0 / columns.len() as f32);
1071 for guess in column_sizes.iter_mut() {
1072 *guess += extra_space_for_all_columns;
1073 }
1074 }
1075
1076 fn layout_cells_in_row(
1079 &mut self,
1080 layout_context: &LayoutContext,
1081 containing_block_for_table: &ContainingBlock,
1082 ) {
1083 let layout_table_slot = |coordinate: TableSlotCoordinates, slot: &TableSlot| {
1084 let TableSlot::Cell(cell) = slot else {
1085 return None;
1086 };
1087
1088 let cell = cell.borrow();
1089 let area = LogicalSides {
1090 inline_start: coordinate.x,
1091 inline_end: coordinate.x + cell.colspan,
1092 block_start: coordinate.y,
1093 block_end: coordinate.y + cell.rowspan,
1094 };
1095 let layout_style = cell.context.layout_style();
1096 let border = self
1097 .get_collapsed_border_widths_for_area(area)
1098 .unwrap_or_else(|| {
1099 layout_style.border_width(containing_block_for_table.style.writing_mode)
1100 });
1101 let padding: LogicalSides<Au> = layout_style
1102 .padding(containing_block_for_table.style.writing_mode)
1103 .percentages_relative_to(self.basis_for_cell_padding_percentage);
1104 let padding_border_sums = LogicalVec2 {
1105 inline: padding.inline_sum() + border.inline_sum(),
1106 block: padding.block_sum() + border.block_sum(),
1107 };
1108 let border_spacing_spanned =
1109 self.table.border_spacing().inline * (cell.colspan - 1) as i32;
1110
1111 let mut total_cell_width = (coordinate.x..coordinate.x + cell.colspan)
1112 .map(|column_index| self.distributed_column_widths[column_index])
1113 .sum::<Au>() -
1114 padding_border_sums.inline +
1115 border_spacing_spanned;
1116 total_cell_width = total_cell_width.max(Au::zero());
1117
1118 let preferred_aspect_ratio = cell.context.preferred_aspect_ratio(&padding_border_sums);
1119 let containing_block_for_children = ContainingBlock {
1120 size: ContainingBlockSize {
1121 inline: total_cell_width,
1122 block: SizeConstraint::default(),
1123 },
1124 style: &cell.context.base.style,
1125 };
1126
1127 let mut positioning_context = PositioningContext::default();
1128 let layout = cell.context.layout(
1129 layout_context,
1130 &mut positioning_context,
1131 &containing_block_for_children,
1132 containing_block_for_table,
1133 preferred_aspect_ratio,
1134 &LazySize::intrinsic(),
1135 );
1136
1137 Some(CellLayout {
1138 layout,
1139 padding,
1140 border,
1141 positioning_context,
1142 })
1143 };
1144
1145 let job_sizes = self
1146 .table
1147 .slots
1148 .iter()
1149 .map(|row| row.iter().map(|item| item.subtree_size()).sum::<usize>());
1150 self.cells_laid_out = if layout_context.should_parallelize_layout(job_sizes) {
1151 self.table
1152 .slots
1153 .par_iter()
1154 .enumerate()
1155 .map(|(row_index, row_slots)| {
1156 row_slots
1157 .par_iter()
1158 .enumerate()
1159 .map(|(column_index, slot)| {
1160 layout_table_slot(
1161 TableSlotCoordinates::new(column_index, row_index),
1162 slot,
1163 )
1164 })
1165 .collect()
1166 })
1167 .collect()
1168 } else {
1169 self.table
1170 .slots
1171 .iter()
1172 .enumerate()
1173 .map(|(row_index, row_slots)| {
1174 row_slots
1175 .iter()
1176 .enumerate()
1177 .map(|(column_index, slot)| {
1178 layout_table_slot(
1179 TableSlotCoordinates::new(column_index, row_index),
1180 slot,
1181 )
1182 })
1183 .collect()
1184 })
1185 .collect()
1186 };
1187
1188 for row_index in 0..self.table.size.height {
1191 for column_index in 0..self.table.size.width {
1192 let Some(layout) = &self.cells_laid_out[row_index][column_index] else {
1193 continue;
1194 };
1195
1196 self.cell_measures[row_index][column_index]
1197 .block
1198 .content_sizes
1199 .max_assign(layout.outer_block_size().into());
1200 }
1201 }
1202 }
1203
1204 fn do_first_row_layout(&mut self, writing_mode: WritingMode) -> Vec<Au> {
1207 let mut row_sizes = (0..self.table.size.height)
1208 .map(|row_index| {
1209 let (mut max_ascent, mut max_descent, mut max_row_height) =
1210 (Au::zero(), Au::zero(), Au::zero());
1211
1212 for column_index in 0..self.table.size.width {
1213 let cell = match self.table.slots[row_index][column_index] {
1214 TableSlot::Cell(ref cell) => cell,
1215 _ => continue,
1216 };
1217
1218 let layout = match self.cells_laid_out[row_index][column_index] {
1219 Some(ref layout) => layout,
1220 None => {
1221 warn!(
1222 "Did not find a layout at a slot index with an originating cell."
1223 );
1224 continue;
1225 },
1226 };
1227
1228 let cell = cell.borrow();
1229 let outer_block_size = layout.outer_block_size();
1230 if cell.rowspan == 1 {
1231 max_row_height.max_assign(outer_block_size);
1232 }
1233
1234 if cell.content_alignment() == CellContentAlignment::Baseline {
1235 let ascent = layout.ascent();
1236 let border_padding_start =
1237 layout.border.block_start + layout.padding.block_start;
1238 let border_padding_end = layout.border.block_end + layout.padding.block_end;
1239 max_ascent.max_assign(ascent + border_padding_start);
1240
1241 if cell.rowspan == 1 {
1245 max_descent.max_assign(
1246 layout.layout.content_block_size - ascent + border_padding_end,
1247 );
1248 }
1249 }
1250 }
1251 self.row_baselines.push(max_ascent);
1252 max_row_height.max(max_ascent + max_descent)
1253 })
1254 .collect::<Vec<_>>();
1255 self.calculate_row_sizes_after_first_layout(&mut row_sizes, writing_mode);
1256 row_sizes
1257 }
1258
1259 fn calculate_row_sizes_after_first_layout(
1263 &mut self,
1264 row_sizes: &mut [Au],
1265 writing_mode: WritingMode,
1266 ) {
1267 let mut cells_to_distribute = Vec::new();
1268 let mut total_percentage = 0.;
1269 #[allow(clippy::needless_range_loop)] for row_index in 0..self.table.size.height {
1271 let row_measure = self
1272 .table
1273 .get_row_measure_for_row_at_index(writing_mode, row_index);
1274 row_sizes[row_index].max_assign(row_measure.content_sizes.min_content);
1275
1276 let mut percentage = row_measure.percentage.unwrap_or_default().0;
1277 for column_index in 0..self.table.size.width {
1278 let cell_percentage = self.cell_measures[row_index][column_index]
1279 .block
1280 .percentage
1281 .unwrap_or_default()
1282 .0;
1283 percentage = percentage.max(cell_percentage);
1284
1285 let cell_measure = &self.cell_measures[row_index][column_index].block;
1286 let cell = match self.table.slots[row_index][column_index] {
1287 TableSlot::Cell(ref cell) if cell.borrow().rowspan > 1 => cell,
1288 TableSlot::Cell(_) => {
1289 row_sizes[row_index].max_assign(cell_measure.content_sizes.max_content);
1292 continue;
1293 },
1294 _ => continue,
1295 };
1296
1297 cells_to_distribute.push(RowspanToDistribute {
1298 coordinates: TableSlotCoordinates::new(column_index, row_index),
1299 cell: cell.borrow(),
1300 measure: cell_measure,
1301 });
1302 }
1303
1304 self.rows[row_index].percent = Percentage(percentage.min(1. - total_percentage));
1305 total_percentage += self.rows[row_index].percent.0;
1306 }
1307
1308 cells_to_distribute.sort_by(|a, b| {
1309 if a.range() == b.range() {
1310 return a
1311 .measure
1312 .content_sizes
1313 .min_content
1314 .cmp(&b.measure.content_sizes.min_content);
1315 }
1316 if a.fully_encloses(b) {
1317 return std::cmp::Ordering::Greater;
1318 }
1319 if b.fully_encloses(a) {
1320 return std::cmp::Ordering::Less;
1321 }
1322 a.coordinates.y.cmp(&b.coordinates.y)
1323 });
1324
1325 for rowspan_to_distribute in cells_to_distribute {
1326 let rows_spanned = rowspan_to_distribute.range();
1327 let current_rows_size = rows_spanned.clone().map(|index| row_sizes[index]).sum();
1328 let border_spacing_spanned =
1329 self.table.border_spacing().block * (rows_spanned.len() - 1) as i32;
1330 let excess_size = (rowspan_to_distribute.measure.content_sizes.min_content -
1331 current_rows_size -
1332 border_spacing_spanned)
1333 .max(Au::zero());
1334
1335 self.distribute_extra_size_to_rows(
1336 excess_size,
1337 rows_spanned,
1338 row_sizes,
1339 None,
1340 true, );
1342 }
1343 }
1344
1345 fn distribute_extra_size_to_rows(
1348 &self,
1349 mut excess_size: Au,
1350 track_range: Range<usize>,
1351 track_sizes: &mut [Au],
1352 percentage_resolution_size: Option<Au>,
1353 rowspan_distribution: bool,
1354 ) {
1355 if excess_size.is_zero() {
1356 return;
1357 }
1358
1359 let is_constrained = |track_index: &usize| self.rows[*track_index].constrained;
1360 let is_unconstrained = |track_index: &usize| !is_constrained(track_index);
1361 let is_empty: Vec<bool> = track_sizes.iter().map(|size| size.is_zero()).collect();
1362 let is_not_empty = |track_index: &usize| !is_empty[*track_index];
1363 let other_row_that_starts_a_rowspan = |track_index: &usize| {
1364 *track_index != track_range.start &&
1365 self.rows[*track_index].has_cell_with_span_greater_than_one
1366 };
1367
1368 if let Some(percentage_resolution_size) = percentage_resolution_size {
1372 let get_percent_block_size_deficit = |row_index: usize, track_size: Au| {
1373 let size_needed_for_percent =
1374 percentage_resolution_size.scale_by(self.rows[row_index].percent.0);
1375 (size_needed_for_percent - track_size).max(Au::zero())
1376 };
1377 let percent_block_size_deficit: Au = track_range
1378 .clone()
1379 .map(|index| get_percent_block_size_deficit(index, track_sizes[index]))
1380 .sum();
1381 let percent_distributable_block_size = percent_block_size_deficit.min(excess_size);
1382 if percent_distributable_block_size > Au::zero() {
1383 for track_index in track_range.clone() {
1384 let row_deficit =
1385 get_percent_block_size_deficit(track_index, track_sizes[track_index]);
1386 if row_deficit > Au::zero() {
1387 let ratio =
1388 row_deficit.to_f32_px() / percent_block_size_deficit.to_f32_px();
1389 let size = percent_distributable_block_size.scale_by(ratio);
1390 track_sizes[track_index] += size;
1391 excess_size -= size;
1392 }
1393 }
1394 }
1395 }
1396
1397 if rowspan_distribution {
1400 let rows_that_start_rowspan: Vec<usize> = track_range
1401 .clone()
1402 .filter(other_row_that_starts_a_rowspan)
1403 .collect();
1404 if !rows_that_start_rowspan.is_empty() {
1405 let scale = 1.0 / rows_that_start_rowspan.len() as f32;
1406 for track_index in rows_that_start_rowspan.iter() {
1407 track_sizes[*track_index] += excess_size.scale_by(scale);
1408 }
1409 return;
1410 }
1411 }
1412
1413 let unconstrained_non_empty_rows: Vec<usize> = track_range
1415 .clone()
1416 .filter(is_unconstrained)
1417 .filter(is_not_empty)
1418 .collect();
1419 if !unconstrained_non_empty_rows.is_empty() {
1420 let total_size: Au = unconstrained_non_empty_rows
1421 .iter()
1422 .map(|index| track_sizes[*index])
1423 .sum();
1424 for track_index in unconstrained_non_empty_rows.iter() {
1425 let scale = track_sizes[*track_index].to_f32_px() / total_size.to_f32_px();
1426 track_sizes[*track_index] += excess_size.scale_by(scale);
1427 }
1428 return;
1429 }
1430
1431 let (non_empty_rows, empty_rows): (Vec<usize>, Vec<usize>) =
1432 track_range.clone().partition(is_not_empty);
1433 let only_have_empty_rows = empty_rows.len() == track_range.len();
1434 if !empty_rows.is_empty() {
1435 if rowspan_distribution && only_have_empty_rows {
1438 track_sizes[*empty_rows.last().unwrap()] += excess_size;
1439 return;
1440 }
1441
1442 let non_empty_rows_all_constrained = !non_empty_rows.iter().any(is_unconstrained);
1445 if only_have_empty_rows || non_empty_rows_all_constrained {
1446 let mut rows_to_grow = &empty_rows;
1449 let unconstrained_empty_rows: Vec<usize> = rows_to_grow
1450 .iter()
1451 .copied()
1452 .filter(is_unconstrained)
1453 .collect();
1454 if !unconstrained_empty_rows.is_empty() {
1455 rows_to_grow = &unconstrained_empty_rows;
1456 }
1457
1458 let scale = 1.0 / rows_to_grow.len() as f32;
1460 for track_index in rows_to_grow.iter() {
1461 track_sizes[*track_index] += excess_size.scale_by(scale);
1462 }
1463 return;
1464 }
1465 }
1466
1467 if !non_empty_rows.is_empty() {
1470 let total_size: Au = non_empty_rows.iter().map(|index| track_sizes[*index]).sum();
1471 for track_index in non_empty_rows.iter() {
1472 let scale = track_sizes[*track_index].to_f32_px() / total_size.to_f32_px();
1473 track_sizes[*track_index] += excess_size.scale_by(scale);
1474 }
1475 }
1476 }
1477
1478 fn compute_table_height_and_final_row_heights(
1481 &mut self,
1482 mut row_sizes: Vec<Au>,
1483 containing_block_for_children: &ContainingBlock,
1484 ) {
1485 let table_height_from_style = containing_block_for_children.size.block.definite_or_min();
1492
1493 let block_border_spacing = self.table.total_border_spacing().block;
1494 let table_height_from_rows = row_sizes.iter().sum::<Au>() + block_border_spacing;
1495 self.final_table_height = table_height_from_rows.max(table_height_from_style);
1496
1497 if self.final_table_height == table_height_from_rows {
1500 self.row_sizes = row_sizes;
1501 return;
1502 }
1503
1504 self.distribute_extra_size_to_rows(
1509 self.final_table_height - table_height_from_rows,
1510 0..self.table.size.height,
1511 &mut row_sizes,
1512 Some(self.final_table_height),
1513 false, );
1515 self.row_sizes = row_sizes;
1516 }
1517
1518 fn layout_caption(
1519 &self,
1520 caption: &TableCaption,
1521 layout_context: &LayoutContext,
1522 parent_positioning_context: &mut PositioningContext,
1523 ) -> BoxFragment {
1524 let containing_block = &ContainingBlock {
1525 size: ContainingBlockSize {
1526 inline: self.table_width + self.pbm.padding_border_sums.inline,
1527 block: SizeConstraint::default(),
1528 },
1529 style: &self.table.style,
1530 };
1531
1532 let ignore_block_margins_for_stretch = LogicalSides1D::new(false, false);
1536
1537 let mut positioning_context =
1538 PositioningContext::new_for_layout_box_base(&caption.context.base);
1539 let mut box_fragment = caption.context.layout_in_flow_block_level(
1540 layout_context,
1541 positioning_context
1542 .as_mut()
1543 .unwrap_or(parent_positioning_context),
1544 containing_block,
1545 None, ignore_block_margins_for_stretch,
1547 false, );
1549
1550 if let Some(mut positioning_context) = positioning_context.take() {
1551 positioning_context.layout_collected_children(layout_context, &mut box_fragment);
1552 parent_positioning_context.append(positioning_context);
1553 }
1554
1555 box_fragment
1556 }
1557
1558 #[servo_tracing::instrument(name = "Table::layout", skip_all)]
1561 fn layout(
1562 mut self,
1563 layout_context: &LayoutContext,
1564 positioning_context: &mut PositioningContext,
1565 containing_block_for_children: &ContainingBlock,
1566 containing_block_for_table: &ContainingBlock,
1567 ) -> IndependentFormattingContextLayoutResult {
1568 let table_writing_mode = containing_block_for_children.style.writing_mode;
1569 self.compute_border_collapse(table_writing_mode);
1570 let layout_style = self.table.layout_style(Some(&self));
1571
1572 self.pbm = layout_style
1573 .padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1574 table_writing_mode,
1575 containing_block_for_table.size.inline,
1576 );
1577 self.compute_measures(layout_context, table_writing_mode);
1578 self.compute_table_width(containing_block_for_children);
1579
1580 let containing_block_for_logical_conversion = ContainingBlock {
1595 size: ContainingBlockSize {
1596 inline: self.table_width,
1597 block: containing_block_for_table.size.block,
1598 },
1599 style: containing_block_for_children.style,
1600 };
1601 let offset_from_wrapper = -self.pbm.padding - self.pbm.border;
1602 let mut current_block_offset = offset_from_wrapper.block_start;
1603
1604 let mut table_layout = IndependentFormattingContextLayoutResult {
1605 fragments: Vec::new(),
1606 content_block_size: Zero::zero(),
1607 content_inline_size_for_table: None,
1608 baselines: Baselines::default(),
1609 depends_on_block_constraints: true,
1610 specific_layout_info: Some(SpecificLayoutInfo::TableWrapper),
1611 collapsible_margins_in_children: CollapsedBlockMargins::zero(),
1612 };
1613
1614 #[derive(EnumIter, PartialEq)]
1615 enum TableWrapperSection {
1616 TopCaptions,
1617 Grid,
1618 BottomCaptions,
1619 }
1620 impl TableWrapperSection {
1621 fn accepts_caption(&self, caption: &TableCaption) -> bool {
1622 match caption.context.style().clone_caption_side() {
1623 CaptionSide::Top => *self == TableWrapperSection::TopCaptions,
1624 CaptionSide::Bottom => *self == TableWrapperSection::BottomCaptions,
1625 }
1626 }
1627 }
1628
1629 for section in TableWrapperSection::iter() {
1630 if section == TableWrapperSection::Grid {
1631 let original_positioning_context_length = positioning_context.len();
1632 let grid_fragment = self.layout_grid(
1633 layout_context,
1634 positioning_context,
1635 &containing_block_for_logical_conversion,
1636 containing_block_for_children,
1637 );
1638
1639 let logical_grid_content_rect = grid_fragment
1642 .content_rect()
1643 .to_logical(&containing_block_for_logical_conversion);
1644 let grid_pbm = grid_fragment
1645 .padding_border_margin()
1646 .to_logical(table_writing_mode);
1647 table_layout.baselines = grid_fragment.baselines(table_writing_mode).offset(
1648 current_block_offset +
1649 logical_grid_content_rect.start_corner.block +
1650 grid_pbm.block_start,
1651 );
1652
1653 grid_fragment.base.set_rect(
1654 LogicalRect {
1655 start_corner: LogicalVec2 {
1656 inline: offset_from_wrapper.inline_start + grid_pbm.inline_start,
1657 block: current_block_offset + grid_pbm.block_start,
1658 },
1659 size: grid_fragment
1660 .base
1661 .rect()
1662 .size
1663 .to_logical(table_writing_mode),
1664 }
1665 .as_physical(Some(&containing_block_for_logical_conversion)),
1666 );
1667
1668 current_block_offset += grid_fragment
1669 .border_rect()
1670 .size
1671 .to_logical(table_writing_mode)
1672 .block;
1673 if logical_grid_content_rect.size.inline < self.table_width {
1674 table_layout.content_inline_size_for_table =
1676 Some(logical_grid_content_rect.size.inline);
1677 }
1678
1679 let grid_fragment = Fragment::Box(grid_fragment.into());
1680 positioning_context.adjust_static_position_of_hoisted_fragments(
1681 &grid_fragment,
1682 original_positioning_context_length,
1683 );
1684 table_layout.fragments.push(grid_fragment);
1685 } else {
1686 let caption_fragments = self.table.captions.iter().filter_map(|caption| {
1687 let caption = caption.borrow();
1688 if !section.accepts_caption(&caption) {
1689 return None;
1690 }
1691
1692 let original_positioning_context_length = positioning_context.len();
1693 let caption_fragment =
1694 self.layout_caption(&caption, layout_context, positioning_context);
1695
1696 let caption_pbm = caption_fragment
1699 .padding_border_margin()
1700 .to_logical(table_writing_mode);
1701
1702 let caption_style = caption_fragment.style().clone();
1703 let caption_relative_offset = match caption_style.clone_position() {
1704 Position::Relative => {
1705 relative_adjustement(&caption_style, containing_block_for_children)
1706 },
1707 _ => LogicalVec2::zero(),
1708 };
1709
1710 caption_fragment.base.set_rect(
1711 LogicalRect {
1712 start_corner: LogicalVec2 {
1713 inline: offset_from_wrapper.inline_start + caption_pbm.inline_start,
1714 block: current_block_offset + caption_pbm.block_start,
1715 } + caption_relative_offset,
1716 size: caption_fragment
1717 .content_rect()
1718 .size
1719 .to_logical(table_writing_mode),
1720 }
1721 .as_physical(Some(&containing_block_for_logical_conversion)),
1722 );
1723
1724 current_block_offset += caption_fragment
1725 .margin_rect()
1726 .size
1727 .to_logical(table_writing_mode)
1728 .block;
1729
1730 let caption_fragment = Fragment::Box(caption_fragment.into());
1731 positioning_context.adjust_static_position_of_hoisted_fragments(
1732 &caption_fragment,
1733 original_positioning_context_length,
1734 );
1735
1736 caption.context.base.set_fragment(caption_fragment.clone());
1737 Some(caption_fragment)
1738 });
1739 table_layout.fragments.extend(caption_fragments);
1740 }
1741 }
1742
1743 table_layout.content_block_size = current_block_offset + offset_from_wrapper.block_end;
1744 table_layout
1745 }
1746
1747 fn layout_grid(
1750 &mut self,
1751 layout_context: &LayoutContext,
1752 positioning_context: &mut PositioningContext,
1753 containing_block_for_logical_conversion: &ContainingBlock,
1754 containing_block_for_children: &ContainingBlock,
1755 ) -> BoxFragment {
1756 self.distributed_column_widths =
1757 Self::distribute_width_to_columns(self.assignable_width, &self.columns);
1758 self.layout_cells_in_row(layout_context, containing_block_for_children);
1759 let table_writing_mode = containing_block_for_children.style.writing_mode;
1760 let first_layout_row_heights = self.do_first_row_layout(table_writing_mode);
1761 self.compute_table_height_and_final_row_heights(
1762 first_layout_row_heights,
1763 containing_block_for_children,
1764 );
1765
1766 assert_eq!(self.table.size.height, self.row_sizes.len());
1767 assert_eq!(self.table.size.width, self.distributed_column_widths.len());
1768
1769 if self.table.size.width == 0 && self.table.size.height == 0 {
1770 let content_rect = LogicalRect {
1771 start_corner: LogicalVec2::zero(),
1772 size: LogicalVec2 {
1773 inline: self.table_width,
1774 block: self.final_table_height,
1775 },
1776 }
1777 .as_physical(Some(containing_block_for_logical_conversion));
1778 return BoxFragment::new(
1779 self.table.grid_base_fragment_info,
1780 self.table.grid_style.clone(),
1781 Vec::new(),
1782 content_rect,
1783 self.pbm.padding.to_physical(table_writing_mode),
1784 self.pbm.border.to_physical(table_writing_mode),
1785 PhysicalSides::zero(),
1786 self.specific_layout_info_for_grid(),
1787 );
1788 }
1789
1790 let mut table_fragments = Vec::new();
1791 let table_and_track_dimensions = TableAndTrackDimensions::new(self);
1792 self.make_fragments_for_columns_and_column_groups(
1793 &table_and_track_dimensions,
1794 &mut table_fragments,
1795 );
1796
1797 let mut baselines = Baselines::default();
1798 let mut row_group_fragment_layout = None;
1799 for row_index in 0..self.table.size.height {
1800 if row_index == 0 {
1810 let row_end = table_and_track_dimensions
1811 .get_row_rect(0)
1812 .max_block_position();
1813 baselines.first = Some(row_end);
1814 baselines.last = Some(row_end);
1815 }
1816
1817 let row_is_collapsed = self.is_row_collapsed(row_index);
1818 let table_row = self.table.rows[row_index].borrow();
1819 let mut row_fragment_layout = RowFragmentLayout::new(
1820 &table_row,
1821 row_index,
1822 &table_and_track_dimensions,
1823 &self.table.style,
1824 );
1825
1826 let old_row_group_index = row_group_fragment_layout
1827 .as_ref()
1828 .map(|layout: &RowGroupFragmentLayout| layout.index);
1829 if table_row.group_index != old_row_group_index {
1830 if let Some(old_row_group_layout) = row_group_fragment_layout.take() {
1832 table_fragments.push(old_row_group_layout.finish(
1833 layout_context,
1834 positioning_context,
1835 containing_block_for_logical_conversion,
1836 containing_block_for_children,
1837 ));
1838 }
1839
1840 if let Some(new_group_index) = table_row.group_index {
1842 row_group_fragment_layout = Some(RowGroupFragmentLayout::new(
1843 self.table.row_groups[new_group_index].clone(),
1844 new_group_index,
1845 &table_and_track_dimensions,
1846 ));
1847 }
1848 }
1849
1850 let column_indices = 0..self.table.size.width;
1851 row_fragment_layout.fragments.reserve(self.table.size.width);
1852 for column_index in column_indices {
1853 self.do_final_cell_layout(
1854 row_index,
1855 column_index,
1856 &table_and_track_dimensions,
1857 &mut baselines,
1858 &mut row_fragment_layout,
1859 row_group_fragment_layout.as_mut(),
1860 positioning_context,
1861 self.is_column_collapsed(column_index) || row_is_collapsed,
1862 );
1863 }
1864
1865 let row_fragment = row_fragment_layout.finish(
1866 layout_context,
1867 positioning_context,
1868 containing_block_for_logical_conversion,
1869 containing_block_for_children,
1870 &mut row_group_fragment_layout,
1871 );
1872
1873 match row_group_fragment_layout.as_mut() {
1874 Some(layout) => layout.fragments.push(row_fragment),
1875 None => table_fragments.push(row_fragment),
1876 }
1877 }
1878
1879 if let Some(row_group_layout) = row_group_fragment_layout.take() {
1880 table_fragments.push(row_group_layout.finish(
1881 layout_context,
1882 positioning_context,
1883 containing_block_for_logical_conversion,
1884 containing_block_for_children,
1885 ));
1886 }
1887
1888 let content_rect = LogicalRect {
1889 start_corner: LogicalVec2::zero(),
1890 size: LogicalVec2 {
1891 inline: table_and_track_dimensions.table_rect.max_inline_position(),
1892 block: table_and_track_dimensions.table_rect.max_block_position(),
1893 },
1894 }
1895 .as_physical(Some(containing_block_for_logical_conversion));
1896 BoxFragment::new(
1897 self.table.grid_base_fragment_info,
1898 self.table.grid_style.clone(),
1899 table_fragments,
1900 content_rect,
1901 self.pbm.padding.to_physical(table_writing_mode),
1902 self.pbm.border.to_physical(table_writing_mode),
1903 PhysicalSides::zero(),
1904 self.specific_layout_info_for_grid(),
1905 )
1906 .with_baselines(baselines)
1907 }
1908
1909 fn specific_layout_info_for_grid(&mut self) -> Option<SpecificLayoutInfo> {
1910 mem::take(&mut self.collapsed_borders).map(|mut collapsed_borders| {
1911 let mut track_sizes = LogicalVec2 {
1914 inline: mem::take(&mut self.distributed_column_widths),
1915 block: mem::take(&mut self.row_sizes),
1916 };
1917 for (column_index, column_size) in track_sizes.inline.iter_mut().enumerate() {
1918 if self.is_column_collapsed(column_index) {
1919 mem::take(column_size);
1920 }
1921 }
1922 for (row_index, row_size) in track_sizes.block.iter_mut().enumerate() {
1923 if self.is_row_collapsed(row_index) {
1924 mem::take(row_size);
1925 }
1926 }
1927 let writing_mode = self.table.style.writing_mode;
1928 if !writing_mode.is_bidi_ltr() {
1929 track_sizes.inline.reverse();
1930 collapsed_borders.inline.reverse();
1931 for border_line in &mut collapsed_borders.block {
1932 border_line.reverse();
1933 }
1934 }
1935 SpecificLayoutInfo::TableGridWithCollapsedBorders(Box::new(SpecificTableGridInfo {
1936 collapsed_borders: if writing_mode.is_horizontal() {
1937 PhysicalVec::new(collapsed_borders.inline, collapsed_borders.block)
1938 } else {
1939 PhysicalVec::new(collapsed_borders.block, collapsed_borders.inline)
1940 },
1941 track_sizes: if writing_mode.is_horizontal() {
1942 PhysicalVec::new(track_sizes.inline, track_sizes.block)
1943 } else {
1944 PhysicalVec::new(track_sizes.block, track_sizes.inline)
1945 },
1946 }))
1947 })
1948 }
1949
1950 fn is_row_collapsed(&self, row_index: usize) -> bool {
1951 let Some(row) = &self.table.rows.get(row_index) else {
1952 return false;
1953 };
1954
1955 let row = row.borrow();
1956 if row.base.style.get_inherited_box().visibility == Visibility::Collapse {
1957 return true;
1958 }
1959 let row_group = match row.group_index {
1960 Some(group_index) => self.table.row_groups[group_index].borrow(),
1961 None => return false,
1962 };
1963 row_group.base.style.get_inherited_box().visibility == Visibility::Collapse
1964 }
1965
1966 fn is_column_collapsed(&self, column_index: usize) -> bool {
1967 let Some(column) = &self.table.columns.get(column_index) else {
1968 return false;
1969 };
1970 let column = column.borrow();
1971 if column.base.style.get_inherited_box().visibility == Visibility::Collapse {
1972 return true;
1973 }
1974 let col_group = match column.group_index {
1975 Some(group_index) => self.table.column_groups[group_index].borrow(),
1976 None => return false,
1977 };
1978 col_group.base.style.get_inherited_box().visibility == Visibility::Collapse
1979 }
1980
1981 #[allow(clippy::too_many_arguments)]
1982 fn do_final_cell_layout(
1983 &mut self,
1984 row_index: usize,
1985 column_index: usize,
1986 dimensions: &TableAndTrackDimensions,
1987 baselines: &mut Baselines,
1988 row_fragment_layout: &mut RowFragmentLayout,
1989 row_group_fragment_layout: Option<&mut RowGroupFragmentLayout>,
1990 positioning_context_for_table: &mut PositioningContext,
1991 is_collapsed: bool,
1992 ) {
1993 let row_group_positioning_context =
1996 row_group_fragment_layout.and_then(|layout| layout.positioning_context.as_mut());
1997 let positioning_context = row_fragment_layout
1998 .positioning_context
1999 .as_mut()
2000 .or(row_group_positioning_context)
2001 .unwrap_or(positioning_context_for_table);
2002
2003 let layout = match self.cells_laid_out[row_index][column_index].take() {
2004 Some(layout) => layout,
2005 None => {
2006 return;
2007 },
2008 };
2009 let cell = match self.table.slots[row_index][column_index] {
2010 TableSlot::Cell(ref cell) => cell,
2011 _ => {
2012 warn!("Did not find a non-spanned cell at index with layout.");
2013 return;
2014 },
2015 }
2016 .borrow();
2017
2018 let row_block_offset = row_fragment_layout.rect.start_corner.block;
2020 let row_baseline = self.row_baselines[row_index];
2021 if cell.content_alignment() == CellContentAlignment::Baseline && !layout.is_empty() {
2022 let baseline = row_block_offset + row_baseline;
2023 if row_index == 0 {
2024 baselines.first = Some(baseline);
2025 }
2026 baselines.last = Some(baseline);
2027 }
2028 let mut row_relative_cell_rect = dimensions.get_cell_rect(
2029 TableSlotCoordinates::new(column_index, row_index),
2030 cell.rowspan,
2031 cell.colspan,
2032 );
2033 row_relative_cell_rect.start_corner -= row_fragment_layout.rect.start_corner;
2034 let mut fragment = cell.create_fragment(
2035 layout,
2036 row_relative_cell_rect,
2037 row_baseline,
2038 positioning_context,
2039 &self.table.style,
2040 &row_fragment_layout.containing_block,
2041 is_collapsed,
2042 );
2043
2044 let make_relative_to_row_start = |mut rect: LogicalRect<Au>| {
2053 rect.start_corner -= row_fragment_layout.rect.start_corner;
2054 let writing_mode = self.table.style.writing_mode;
2055 PhysicalRect::new(
2056 if writing_mode.is_horizontal() {
2057 PhysicalPoint::new(rect.start_corner.inline, rect.start_corner.block)
2058 } else {
2059 PhysicalPoint::new(rect.start_corner.block, rect.start_corner.inline)
2060 },
2061 rect.size.to_physical_size(writing_mode),
2062 )
2063 };
2064
2065 let column = self.table.columns.get(column_index);
2066 let column_group = column
2067 .and_then(|column| column.borrow().group_index)
2068 .and_then(|index| self.table.column_groups.get(index));
2069 if let Some(column_group) = column_group {
2070 let column_group = column_group.borrow();
2071 let rect = make_relative_to_row_start(dimensions.get_column_group_rect(&column_group));
2072 fragment.add_extra_background(ExtraBackground {
2073 style: column_group.shared_background_style.clone(),
2074 rect,
2075 })
2076 }
2077 if let Some(column) = column {
2078 let column = column.borrow();
2079 if !column.is_anonymous {
2080 let rect = make_relative_to_row_start(dimensions.get_column_rect(column_index));
2081 fragment.add_extra_background(ExtraBackground {
2082 style: column.shared_background_style.clone(),
2083 rect,
2084 })
2085 }
2086 }
2087 let row = self.table.rows.get(row_index);
2088 let row_group = row
2089 .and_then(|row| row.borrow().group_index)
2090 .and_then(|index| self.table.row_groups.get(index));
2091 if let Some(row_group) = row_group {
2092 let rect =
2093 make_relative_to_row_start(dimensions.get_row_group_rect(&row_group.borrow()));
2094 fragment.add_extra_background(ExtraBackground {
2095 style: row_group.borrow().shared_background_style.clone(),
2096 rect,
2097 })
2098 }
2099 if let Some(row) = row {
2100 let row = row.borrow();
2101 let rect = make_relative_to_row_start(row_fragment_layout.rect);
2102 fragment.add_extra_background(ExtraBackground {
2103 style: row.shared_background_style.clone(),
2104 rect,
2105 })
2106 }
2107
2108 let fragment = Fragment::Box(fragment.into());
2109 cell.context.base.set_fragment(fragment.clone());
2110 row_fragment_layout.fragments.push(fragment);
2111 }
2112
2113 fn make_fragments_for_columns_and_column_groups(
2114 &self,
2115 dimensions: &TableAndTrackDimensions,
2116 fragments: &mut Vec<Fragment>,
2117 ) {
2118 for column_group in self.table.column_groups.iter() {
2119 let column_group = column_group.borrow();
2120 if !column_group.is_empty() {
2121 let fragment = Fragment::Positioning(PositioningFragment::new_empty(
2122 column_group.base.base_fragment_info,
2123 dimensions
2124 .get_column_group_rect(&column_group)
2125 .as_physical(None),
2126 column_group.base.style.clone(),
2127 ));
2128 column_group.base.set_fragment(fragment.clone());
2129 fragments.push(fragment);
2130 }
2131 }
2132
2133 for (column_index, column) in self.table.columns.iter().enumerate() {
2134 let column = column.borrow();
2135 let fragment = Fragment::Positioning(PositioningFragment::new_empty(
2136 column.base.base_fragment_info,
2137 dimensions.get_column_rect(column_index).as_physical(None),
2138 column.base.style.clone(),
2139 ));
2140 column.base.set_fragment(fragment.clone());
2141 fragments.push(fragment);
2142 }
2143 }
2144
2145 fn compute_border_collapse(&mut self, writing_mode: WritingMode) {
2146 if self.table.style.get_inherited_table().border_collapse != BorderCollapse::Collapse {
2147 self.collapsed_borders = None;
2148 return;
2149 }
2150
2151 let mut collapsed_borders = LogicalVec2 {
2152 block: vec![
2153 vec![Default::default(); self.table.size.width];
2154 self.table.size.height + 1
2155 ],
2156 inline: vec![
2157 vec![Default::default(); self.table.size.height];
2158 self.table.size.width + 1
2159 ],
2160 };
2161
2162 let apply_border = |collapsed_borders: &mut CollapsedBorders,
2163 layout_style: &LayoutStyle,
2164 block: &Range<usize>,
2165 inline: &Range<usize>| {
2166 let border = CollapsedBorder::from_layout_style(layout_style, writing_mode);
2167 border
2168 .block_start
2169 .max_assign_to_slice(&mut collapsed_borders.block[block.start][inline.clone()]);
2170 border
2171 .block_end
2172 .max_assign_to_slice(&mut collapsed_borders.block[block.end][inline.clone()]);
2173 border
2174 .inline_start
2175 .max_assign_to_slice(&mut collapsed_borders.inline[inline.start][block.clone()]);
2176 border
2177 .inline_end
2178 .max_assign_to_slice(&mut collapsed_borders.inline[inline.end][block.clone()]);
2179 };
2180 let hide_inner_borders = |collapsed_borders: &mut CollapsedBorders,
2181 block: &Range<usize>,
2182 inline: &Range<usize>| {
2183 for x in inline.clone() {
2184 for y in block.clone() {
2185 if x != inline.start {
2186 collapsed_borders.inline[x][y].hide();
2187 }
2188 if y != block.start {
2189 collapsed_borders.block[y][x].hide();
2190 }
2191 }
2192 }
2193 };
2194 let all_rows = 0..self.table.size.height;
2195 let all_columns = 0..self.table.size.width;
2196 for row_index in all_rows.clone() {
2197 for column_index in all_columns.clone() {
2198 let cell = match self.table.slots[row_index][column_index] {
2199 TableSlot::Cell(ref cell) => cell,
2200 _ => continue,
2201 }
2202 .borrow();
2203 let block_range = row_index..row_index + cell.rowspan;
2204 let inline_range = column_index..column_index + cell.colspan;
2205 hide_inner_borders(&mut collapsed_borders, &block_range, &inline_range);
2206 apply_border(
2207 &mut collapsed_borders,
2208 &cell.context.layout_style(),
2209 &block_range,
2210 &inline_range,
2211 );
2212 }
2213 }
2214 for (row_index, row) in self.table.rows.iter().enumerate() {
2215 let row = row.borrow();
2216 apply_border(
2217 &mut collapsed_borders,
2218 &row.layout_style(),
2219 &(row_index..row_index + 1),
2220 &all_columns,
2221 );
2222 }
2223 for row_group in &self.table.row_groups {
2224 let row_group = row_group.borrow();
2225 apply_border(
2226 &mut collapsed_borders,
2227 &row_group.layout_style(),
2228 &row_group.track_range,
2229 &all_columns,
2230 );
2231 }
2232 for (column_index, column) in self.table.columns.iter().enumerate() {
2233 let column = column.borrow();
2234 apply_border(
2235 &mut collapsed_borders,
2236 &column.layout_style(),
2237 &all_rows,
2238 &(column_index..column_index + 1),
2239 );
2240 }
2241 for column_group in &self.table.column_groups {
2242 let column_group = column_group.borrow();
2243 apply_border(
2244 &mut collapsed_borders,
2245 &column_group.layout_style(),
2246 &all_rows,
2247 &column_group.track_range,
2248 );
2249 }
2250 apply_border(
2251 &mut collapsed_borders,
2252 &self.table.layout_style_for_grid(),
2253 &all_rows,
2254 &all_columns,
2255 );
2256
2257 self.collapsed_borders = Some(collapsed_borders);
2258 }
2259
2260 fn get_collapsed_border_widths_for_area(
2261 &self,
2262 area: LogicalSides<usize>,
2263 ) -> Option<LogicalSides<Au>> {
2264 let collapsed_borders = self.collapsed_borders.as_ref()?;
2265 let columns = || area.inline_start..area.inline_end;
2266 let rows = || area.block_start..area.block_end;
2267 let max_width = |slice: &[CollapsedBorder]| {
2268 let slice_widths = slice.iter().map(|collapsed_border| collapsed_border.width);
2269 slice_widths.max().unwrap_or_default()
2270 };
2271 Some(area.map_inline_and_block_axes(
2272 |column| max_width(&collapsed_borders.inline[*column][rows()]) / 2,
2273 |row| max_width(&collapsed_borders.block[*row][columns()]) / 2,
2274 ))
2275 }
2276}
2277
2278struct RowFragmentLayout<'a> {
2279 row: &'a TableTrack,
2280 rect: LogicalRect<Au>,
2281 containing_block: ContainingBlock<'a>,
2282 positioning_context: Option<PositioningContext>,
2283 fragments: Vec<Fragment>,
2284}
2285
2286impl<'a> RowFragmentLayout<'a> {
2287 fn new(
2288 table_row: &'a TableTrack,
2289 index: usize,
2290 dimensions: &TableAndTrackDimensions,
2291 table_style: &'a ComputedValues,
2292 ) -> Self {
2293 let rect = dimensions.get_row_rect(index);
2294 let containing_block = ContainingBlock {
2295 size: ContainingBlockSize {
2296 inline: rect.size.inline,
2297 block: SizeConstraint::Definite(rect.size.block),
2298 },
2299 style: table_style,
2300 };
2301 Self {
2302 row: table_row,
2303 rect,
2304 positioning_context: PositioningContext::new_for_layout_box_base(&table_row.base),
2305 containing_block,
2306 fragments: Vec::new(),
2307 }
2308 }
2309 fn finish(
2310 mut self,
2311 layout_context: &LayoutContext,
2312 table_positioning_context: &mut PositioningContext,
2313 containing_block_for_logical_conversion: &ContainingBlock,
2314 containing_block_for_children: &ContainingBlock,
2315 row_group_fragment_layout: &mut Option<RowGroupFragmentLayout>,
2316 ) -> Fragment {
2317 if self.positioning_context.is_some() {
2318 self.rect.start_corner +=
2319 relative_adjustement(&self.row.base.style, containing_block_for_children);
2320 }
2321
2322 let (inline_size, block_size) = if let Some(row_group_layout) = row_group_fragment_layout {
2323 self.rect.start_corner -= row_group_layout.rect.start_corner;
2324 (
2325 row_group_layout.rect.size.inline,
2326 SizeConstraint::Definite(row_group_layout.rect.size.block),
2327 )
2328 } else {
2329 (
2330 containing_block_for_logical_conversion.size.inline,
2331 containing_block_for_logical_conversion.size.block,
2332 )
2333 };
2334
2335 let row_group_containing_block = ContainingBlock {
2336 size: ContainingBlockSize {
2337 inline: inline_size,
2338 block: block_size,
2339 },
2340 style: containing_block_for_logical_conversion.style,
2341 };
2342
2343 let mut row_fragment = BoxFragment::new(
2344 self.row.base.base_fragment_info,
2345 self.row.base.style.clone(),
2346 self.fragments,
2347 self.rect.as_physical(Some(&row_group_containing_block)),
2348 PhysicalSides::zero(), PhysicalSides::zero(), PhysicalSides::zero(), None, );
2353 row_fragment.set_does_not_paint_background();
2354
2355 if let Some(mut row_positioning_context) = self.positioning_context.take() {
2356 row_positioning_context.layout_collected_children(layout_context, &mut row_fragment);
2357
2358 let parent_positioning_context = row_group_fragment_layout
2359 .as_mut()
2360 .and_then(|layout| layout.positioning_context.as_mut())
2361 .unwrap_or(table_positioning_context);
2362 parent_positioning_context.append(row_positioning_context);
2363 }
2364
2365 let fragment = Fragment::Box(row_fragment.into());
2366 self.row.base.set_fragment(fragment.clone());
2367 fragment
2368 }
2369}
2370
2371struct RowGroupFragmentLayout {
2372 row_group: ArcRefCell<TableTrackGroup>,
2373 rect: LogicalRect<Au>,
2374 positioning_context: Option<PositioningContext>,
2375 index: usize,
2376 fragments: Vec<Fragment>,
2377}
2378
2379impl RowGroupFragmentLayout {
2380 fn new(
2381 row_group: ArcRefCell<TableTrackGroup>,
2382 index: usize,
2383 dimensions: &TableAndTrackDimensions,
2384 ) -> Self {
2385 let (rect, positioning_context) = {
2386 let row_group = row_group.borrow();
2387 (
2388 dimensions.get_row_group_rect(&row_group),
2389 PositioningContext::new_for_layout_box_base(&row_group.base),
2390 )
2391 };
2392 Self {
2393 row_group,
2394 rect,
2395 positioning_context,
2396 index,
2397 fragments: Vec::new(),
2398 }
2399 }
2400
2401 fn finish(
2402 mut self,
2403 layout_context: &LayoutContext,
2404 table_positioning_context: &mut PositioningContext,
2405 containing_block_for_logical_conversion: &ContainingBlock,
2406 containing_block_for_children: &ContainingBlock,
2407 ) -> Fragment {
2408 let row_group = self.row_group.borrow();
2409 if self.positioning_context.is_some() {
2410 self.rect.start_corner +=
2411 relative_adjustement(&row_group.base.style, containing_block_for_children);
2412 }
2413
2414 let mut row_group_fragment = BoxFragment::new(
2415 row_group.base.base_fragment_info,
2416 row_group.base.style.clone(),
2417 self.fragments,
2418 self.rect
2419 .as_physical(Some(containing_block_for_logical_conversion)),
2420 PhysicalSides::zero(), PhysicalSides::zero(), PhysicalSides::zero(), None, );
2425 row_group_fragment.set_does_not_paint_background();
2426
2427 if let Some(mut row_positioning_context) = self.positioning_context.take() {
2428 row_positioning_context
2429 .layout_collected_children(layout_context, &mut row_group_fragment);
2430 table_positioning_context.append(row_positioning_context);
2431 }
2432
2433 let fragment = Fragment::Box(row_group_fragment.into());
2434 row_group.base.set_fragment(fragment.clone());
2435 fragment
2436 }
2437}
2438
2439struct TableAndTrackDimensions {
2440 table_rect: LogicalRect<Au>,
2442 table_cells_rect: LogicalRect<Au>,
2445 row_dimensions: Vec<(Au, Au)>,
2447 column_dimensions: Vec<(Au, Au)>,
2449}
2450
2451impl TableAndTrackDimensions {
2452 fn new(table_layout: &TableLayout) -> Self {
2453 let border_spacing = table_layout.table.border_spacing();
2454
2455 let fallback_inline_size = table_layout.assignable_width;
2457 let fallback_block_size = table_layout.final_table_height;
2458
2459 let mut column_dimensions = Vec::new();
2460 let mut column_offset = Au::zero();
2461 for column_index in 0..table_layout.table.size.width {
2462 if table_layout.is_column_collapsed(column_index) {
2463 column_dimensions.push((column_offset, column_offset));
2464 continue;
2465 }
2466 let start_offset = column_offset + border_spacing.inline;
2467 let end_offset = start_offset + table_layout.distributed_column_widths[column_index];
2468 column_dimensions.push((start_offset, end_offset));
2469 column_offset = end_offset;
2470 }
2471 column_offset += if table_layout.table.size.width == 0 {
2472 fallback_inline_size
2473 } else {
2474 border_spacing.inline
2475 };
2476
2477 let mut row_dimensions = Vec::new();
2478 let mut row_offset = Au::zero();
2479 for row_index in 0..table_layout.table.size.height {
2480 if table_layout.is_row_collapsed(row_index) {
2481 row_dimensions.push((row_offset, row_offset));
2482 continue;
2483 }
2484 let start_offset = row_offset + border_spacing.block;
2485 let end_offset = start_offset + table_layout.row_sizes[row_index];
2486 row_dimensions.push((start_offset, end_offset));
2487 row_offset = end_offset;
2488 }
2489 row_offset += if table_layout.table.size.height == 0 {
2490 fallback_block_size
2491 } else {
2492 border_spacing.block
2493 };
2494
2495 let table_start_corner = LogicalVec2 {
2496 inline: column_dimensions.first().map_or_else(Au::zero, |v| v.0),
2497 block: row_dimensions.first().map_or_else(Au::zero, |v| v.0),
2498 };
2499 let table_size = LogicalVec2 {
2500 inline: column_dimensions
2501 .last()
2502 .map_or(fallback_inline_size, |v| v.1),
2503 block: row_dimensions.last().map_or(fallback_block_size, |v| v.1),
2504 } - table_start_corner;
2505 let table_cells_rect = LogicalRect {
2506 start_corner: table_start_corner,
2507 size: table_size,
2508 };
2509
2510 let table_rect = LogicalRect {
2511 start_corner: LogicalVec2::zero(),
2512 size: LogicalVec2 {
2513 inline: column_offset,
2514 block: row_offset,
2515 },
2516 };
2517
2518 Self {
2519 table_rect,
2520 table_cells_rect,
2521 row_dimensions,
2522 column_dimensions,
2523 }
2524 }
2525
2526 fn get_row_rect(&self, row_index: usize) -> LogicalRect<Au> {
2527 let mut row_rect = self.table_cells_rect;
2528 let row_dimensions = self.row_dimensions[row_index];
2529 row_rect.start_corner.block = row_dimensions.0;
2530 row_rect.size.block = row_dimensions.1 - row_dimensions.0;
2531 row_rect
2532 }
2533
2534 fn get_column_rect(&self, column_index: usize) -> LogicalRect<Au> {
2535 let mut row_rect = self.table_cells_rect;
2536 let column_dimensions = self.column_dimensions[column_index];
2537 row_rect.start_corner.inline = column_dimensions.0;
2538 row_rect.size.inline = column_dimensions.1 - column_dimensions.0;
2539 row_rect
2540 }
2541
2542 fn get_row_group_rect(&self, row_group: &TableTrackGroup) -> LogicalRect<Au> {
2543 if row_group.is_empty() {
2544 return LogicalRect::zero();
2545 }
2546
2547 let mut row_group_rect = self.table_cells_rect;
2548 let block_start = self.row_dimensions[row_group.track_range.start].0;
2549 let block_end = self.row_dimensions[row_group.track_range.end - 1].1;
2550 row_group_rect.start_corner.block = block_start;
2551 row_group_rect.size.block = block_end - block_start;
2552 row_group_rect
2553 }
2554
2555 fn get_column_group_rect(&self, column_group: &TableTrackGroup) -> LogicalRect<Au> {
2556 if column_group.is_empty() {
2557 return LogicalRect::zero();
2558 }
2559
2560 let mut column_group_rect = self.table_cells_rect;
2561 let inline_start = self.column_dimensions[column_group.track_range.start].0;
2562 let inline_end = self.column_dimensions[column_group.track_range.end - 1].1;
2563 column_group_rect.start_corner.inline = inline_start;
2564 column_group_rect.size.inline = inline_end - inline_start;
2565 column_group_rect
2566 }
2567
2568 fn get_cell_rect(
2569 &self,
2570 coordinates: TableSlotCoordinates,
2571 rowspan: usize,
2572 colspan: usize,
2573 ) -> LogicalRect<Au> {
2574 let start_corner = LogicalVec2 {
2575 inline: self.column_dimensions[coordinates.x].0,
2576 block: self.row_dimensions[coordinates.y].0,
2577 };
2578 let size = LogicalVec2 {
2579 inline: self.column_dimensions[coordinates.x + colspan - 1].1,
2580 block: self.row_dimensions[coordinates.y + rowspan - 1].1,
2581 } - start_corner;
2582 LogicalRect { start_corner, size }
2583 }
2584}
2585
2586impl Table {
2587 fn border_spacing(&self) -> LogicalVec2<Au> {
2588 if self.style.clone_border_collapse() == BorderCollapse::Collapse {
2589 LogicalVec2::zero()
2590 } else {
2591 let border_spacing = self.style.clone_border_spacing();
2592 LogicalVec2 {
2593 inline: border_spacing.horizontal(),
2594 block: border_spacing.vertical(),
2595 }
2596 }
2597 }
2598
2599 fn total_border_spacing(&self) -> LogicalVec2<Au> {
2600 let border_spacing = self.border_spacing();
2601 LogicalVec2 {
2602 inline: if self.size.width > 0 {
2603 border_spacing.inline * (self.size.width as i32 + 1)
2604 } else {
2605 Au::zero()
2606 },
2607 block: if self.size.height > 0 {
2608 border_spacing.block * (self.size.height as i32 + 1)
2609 } else {
2610 Au::zero()
2611 },
2612 }
2613 }
2614
2615 fn get_column_measure_for_column_at_index(
2616 &self,
2617 writing_mode: WritingMode,
2618 column_index: usize,
2619 is_in_fixed_mode: bool,
2620 ) -> CellOrTrackMeasure {
2621 let column = match self.columns.get(column_index) {
2622 Some(column) => column,
2623 None => return CellOrTrackMeasure::zero(),
2624 }
2625 .borrow();
2626
2627 let CellOrColumnOuterSizes {
2628 preferred: preferred_size,
2629 min: min_size,
2630 max: max_size,
2631 percentage: percentage_size,
2632 } = CellOrColumnOuterSizes::new(
2633 &column.base.style,
2634 writing_mode,
2635 &Default::default(),
2636 is_in_fixed_mode,
2637 );
2638
2639 CellOrTrackMeasure {
2640 content_sizes: ContentSizes {
2641 min_content: min_size.inline,
2646 max_content: preferred_size
2650 .inline
2651 .clamp_between_extremums(min_size.inline, max_size.inline),
2652 },
2653 percentage: percentage_size.inline,
2654 }
2655 }
2656
2657 fn get_row_measure_for_row_at_index(
2658 &self,
2659 writing_mode: WritingMode,
2660 row_index: usize,
2661 ) -> CellOrTrackMeasure {
2662 let row = match self.rows.get(row_index) {
2663 Some(row) => row,
2664 None => return CellOrTrackMeasure::zero(),
2665 };
2666
2667 let row = row.borrow();
2671 let size = row.base.style.box_size(writing_mode);
2672 let max_size = row.base.style.max_box_size(writing_mode);
2673 let percentage_contribution = get_size_percentage_contribution(&size, &max_size);
2674
2675 CellOrTrackMeasure {
2676 content_sizes: size
2677 .block
2678 .to_numeric()
2679 .and_then(|size| size.to_length())
2680 .map_or_else(Au::zero, Au::from)
2681 .into(),
2682 percentage: percentage_contribution.block,
2683 }
2684 }
2685
2686 pub(crate) fn layout(
2687 &self,
2688 layout_context: &LayoutContext,
2689 positioning_context: &mut PositioningContext,
2690 containing_block_for_children: &ContainingBlock,
2691 containing_block_for_table: &ContainingBlock,
2692 ) -> IndependentFormattingContextLayoutResult {
2693 TableLayout::new(self).layout(
2694 layout_context,
2695 positioning_context,
2696 containing_block_for_children,
2697 containing_block_for_table,
2698 )
2699 }
2700
2701 pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
2702 for caption in &self.captions {
2703 caption
2704 .borrow_mut()
2705 .context
2706 .base
2707 .parent_box
2708 .replace(layout_box.clone());
2709 }
2710 for row_group in &self.row_groups {
2711 row_group
2712 .borrow_mut()
2713 .base
2714 .parent_box
2715 .replace(layout_box.clone());
2716 }
2717 for column_group in &self.column_groups {
2718 column_group
2719 .borrow_mut()
2720 .base
2721 .parent_box
2722 .replace(layout_box.clone());
2723 }
2724 for row in &self.rows {
2725 let row = &mut *row.borrow_mut();
2726 if let Some(group_index) = row.group_index {
2727 row.base.parent_box.replace(WeakLayoutBox::TableLevelBox(
2728 WeakTableLevelBox::TrackGroup(self.row_groups[group_index].downgrade()),
2729 ));
2730 } else {
2731 row.base.parent_box.replace(layout_box.clone());
2732 }
2733 }
2734 for column in &self.columns {
2735 let column = &mut *column.borrow_mut();
2736 if let Some(group_index) = column.group_index {
2737 column.base.parent_box.replace(WeakLayoutBox::TableLevelBox(
2738 WeakTableLevelBox::TrackGroup(self.column_groups[group_index].downgrade()),
2739 ));
2740 } else {
2741 column.base.parent_box.replace(layout_box.clone());
2742 }
2743 }
2744 for row_index in 0..self.size.height {
2745 let row = WeakLayoutBox::TableLevelBox(WeakTableLevelBox::Track(
2746 self.rows[row_index].downgrade(),
2747 ));
2748 for column_index in 0..self.size.width {
2749 if let TableSlot::Cell(ref cell) = self.slots[row_index][column_index] {
2750 cell.borrow_mut()
2751 .context
2752 .base
2753 .parent_box
2754 .replace(row.clone());
2755 }
2756 }
2757 }
2758 }
2759}
2760
2761impl ComputeInlineContentSizes for Table {
2762 #[servo_tracing::instrument(name = "Table::compute_inline_content_sizes", skip_all)]
2763 fn compute_inline_content_sizes(
2764 &self,
2765 layout_context: &LayoutContext,
2766 constraint_space: &ConstraintSpace,
2767 ) -> InlineContentSizesResult {
2768 let writing_mode = constraint_space.style.writing_mode;
2769 let mut layout = TableLayout::new(self);
2770 layout.compute_border_collapse(writing_mode);
2771 layout.pbm = self
2772 .layout_style(Some(&layout))
2773 .padding_border_margin_with_writing_mode_and_containing_block_inline_size(
2774 writing_mode,
2775 Au::zero(),
2776 );
2777 layout.compute_measures(layout_context, writing_mode);
2778
2779 let grid_content_sizes = layout.compute_grid_min_max();
2780
2781 let caption_content_sizes = ContentSizes::from(
2785 layout.compute_caption_minimum_inline_size(layout_context) -
2786 layout.pbm.padding_border_sums.inline,
2787 );
2788
2789 InlineContentSizesResult {
2790 sizes: grid_content_sizes.max(caption_content_sizes),
2791 depends_on_block_constraints: false,
2792 }
2793 }
2794}
2795
2796impl Table {
2797 #[inline]
2798 pub(crate) fn layout_style<'a>(
2799 &'a self,
2800 layout: Option<&'a TableLayout<'a>>,
2801 ) -> LayoutStyle<'a> {
2802 LayoutStyle::Table(TableLayoutStyle {
2803 table: self,
2804 layout,
2805 })
2806 }
2807
2808 #[inline]
2809 pub(crate) fn layout_style_for_grid(&self) -> LayoutStyle<'_> {
2810 LayoutStyle::Default(&self.grid_style)
2811 }
2812}
2813
2814impl TableTrack {
2815 #[inline]
2816 pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
2817 LayoutStyle::Default(&self.base.style)
2818 }
2819}
2820
2821impl TableTrackGroup {
2822 #[inline]
2823 pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
2824 LayoutStyle::Default(&self.base.style)
2825 }
2826}
2827
2828impl TableLayoutStyle<'_> {
2829 #[inline]
2830 pub(crate) fn style(&self) -> &ComputedValues {
2831 &self.table.style
2832 }
2833
2834 #[inline]
2835 pub(crate) fn collapses_borders(&self) -> bool {
2836 self.style().get_inherited_table().border_collapse == BorderCollapse::Collapse
2837 }
2838
2839 pub(crate) fn halved_collapsed_border_widths(&self) -> LogicalSides<Au> {
2840 debug_assert!(self.collapses_borders());
2841 let area = LogicalSides {
2842 inline_start: 0,
2843 inline_end: self.table.size.width,
2844 block_start: 0,
2845 block_end: self.table.size.height,
2846 };
2847 if let Some(layout) = self.layout {
2848 layout.get_collapsed_border_widths_for_area(area)
2849 } else {
2850 let mut layout = TableLayout::new(self.table);
2852 layout.compute_border_collapse(self.style().writing_mode);
2853 layout.get_collapsed_border_widths_for_area(area)
2854 }
2855 .expect("Collapsed borders should be computed")
2856 }
2857}
2858
2859impl TableSlotCell {
2860 fn content_alignment(&self) -> CellContentAlignment {
2861 let style_box = self.context.base.style.get_box();
2865 match style_box.baseline_shift {
2866 BaselineShift::Keyword(BaselineShiftKeyword::Top) => CellContentAlignment::Top,
2867 BaselineShift::Keyword(BaselineShiftKeyword::Bottom) => CellContentAlignment::Bottom,
2868 _ => match style_box.alignment_baseline {
2869 AlignmentBaseline::Middle => CellContentAlignment::Middle,
2870 _ => CellContentAlignment::Baseline,
2871 },
2872 }
2873 }
2874
2875 #[allow(clippy::too_many_arguments)]
2876 fn create_fragment(
2877 &self,
2878 mut layout: CellLayout,
2879 cell_rect: LogicalRect<Au>,
2880 cell_baseline: Au,
2881 positioning_context: &mut PositioningContext,
2882 table_style: &ComputedValues,
2883 containing_block: &ContainingBlock,
2884 is_collapsed: bool,
2885 ) -> BoxFragment {
2886 use style::Zero as StyleZero;
2888
2889 let cell_content_rect = cell_rect.deflate(&(layout.padding + layout.border));
2890 let content_block_size = layout.layout.content_block_size;
2891 let free_space = || Au::zero().max(cell_content_rect.size.block - content_block_size);
2892 let vertical_align_offset = match self.content_alignment() {
2893 CellContentAlignment::Top => Au::zero(),
2894 CellContentAlignment::Bottom => free_space(),
2895 CellContentAlignment::Middle => free_space().scale_by(0.5),
2896 CellContentAlignment::Baseline => {
2897 cell_baseline -
2898 (layout.padding.block_start + layout.border.block_start) -
2899 layout.ascent()
2900 },
2901 };
2902
2903 let mut base_fragment_info = self.context.base.base_fragment_info;
2904 if self.context.base.style.get_inherited_table().empty_cells == EmptyCells::Hide &&
2905 table_style.get_inherited_table().border_collapse != BorderCollapse::Collapse &&
2906 layout.is_empty_for_empty_cells()
2907 {
2908 base_fragment_info.flags.insert(FragmentFlags::DO_NOT_PAINT);
2909 }
2910
2911 if is_collapsed {
2912 base_fragment_info.flags.insert(FragmentFlags::IS_COLLAPSED);
2913 }
2914
2915 let mut vertical_align_fragment_rect = cell_content_rect;
2917 vertical_align_fragment_rect.start_corner = LogicalVec2 {
2918 inline: Au::zero(),
2919 block: vertical_align_offset,
2920 };
2921 let vertical_align_fragment = PositioningFragment::new_anonymous(
2922 self.context.base.style.clone(),
2923 vertical_align_fragment_rect.as_physical(None),
2924 layout.layout.fragments,
2925 false, );
2927
2928 let physical_cell_rect = cell_content_rect.as_physical(Some(containing_block));
2936 layout
2937 .positioning_context
2938 .adjust_static_position_of_hoisted_fragments_with_offset(
2939 &physical_cell_rect.origin.to_vector(),
2940 PositioningContextLength::zero(),
2941 );
2942 positioning_context.append(layout.positioning_context);
2943
2944 let specific_layout_info = (table_style.get_inherited_table().border_collapse ==
2945 BorderCollapse::Collapse)
2946 .then_some(SpecificLayoutInfo::TableCellWithCollapsedBorders);
2947
2948 BoxFragment::new(
2949 base_fragment_info,
2950 self.context.base.style.clone(),
2951 vec![Fragment::Positioning(vertical_align_fragment)],
2952 physical_cell_rect,
2953 layout.padding.to_physical(table_style.writing_mode),
2954 layout.border.to_physical(table_style.writing_mode),
2955 PhysicalSides::zero(), specific_layout_info,
2957 )
2958 .with_baselines(layout.layout.baselines)
2959 }
2960}
2961
2962fn get_size_percentage_contribution(
2963 size: &LogicalVec2<Size<ComputedLengthPercentage>>,
2964 max_size: &LogicalVec2<Size<ComputedLengthPercentage>>,
2965) -> LogicalVec2<Option<Percentage>> {
2966 LogicalVec2 {
2974 inline: max_two_optional_percentages(
2975 size.inline.to_percentage(),
2976 max_size.inline.to_percentage(),
2977 ),
2978 block: max_two_optional_percentages(
2979 size.block.to_percentage(),
2980 max_size.block.to_percentage(),
2981 ),
2982 }
2983}
2984
2985struct CellOrColumnOuterSizes {
2986 min: LogicalVec2<Au>,
2987 preferred: LogicalVec2<Au>,
2988 max: LogicalVec2<Option<Au>>,
2989 percentage: LogicalVec2<Option<Percentage>>,
2990}
2991
2992impl CellOrColumnOuterSizes {
2993 fn new(
2994 style: &Arc<ComputedValues>,
2995 writing_mode: WritingMode,
2996 padding_border_sums: &LogicalVec2<Au>,
2997 is_in_fixed_mode: bool,
2998 ) -> Self {
2999 let box_sizing = style.get_position().box_sizing;
3000 let outer_size = |size: LogicalVec2<Au>| match box_sizing {
3001 BoxSizing::ContentBox => size + *padding_border_sums,
3002 BoxSizing::BorderBox => LogicalVec2 {
3003 inline: size.inline.max(padding_border_sums.inline),
3004 block: size.block.max(padding_border_sums.block),
3005 },
3006 };
3007
3008 let outer_option_size = |size: LogicalVec2<Option<Au>>| match box_sizing {
3009 BoxSizing::ContentBox => size.map_inline_and_block_axes(
3010 |inline| inline.map(|inline| inline + padding_border_sums.inline),
3011 |block| block.map(|block| block + padding_border_sums.block),
3012 ),
3013 BoxSizing::BorderBox => size.map_inline_and_block_axes(
3014 |inline| inline.map(|inline| inline.max(padding_border_sums.inline)),
3015 |block| block.map(|block| block.max(padding_border_sums.block)),
3016 ),
3017 };
3018
3019 let get_size_for_axis = |size: &Size<ComputedLengthPercentage>| {
3020 size.to_numeric()
3023 .and_then(|length_percentage| length_percentage.to_length())
3024 .map(Au::from)
3025 };
3026
3027 let size = style.box_size(writing_mode);
3028 if is_in_fixed_mode {
3029 return Self {
3030 percentage: size.map(|v| v.to_percentage()),
3031 preferred: outer_option_size(size.map(get_size_for_axis))
3032 .map(|v| v.unwrap_or_default()),
3033 min: LogicalVec2::default(),
3034 max: LogicalVec2::default(),
3035 };
3036 }
3037
3038 let min_size = style.min_box_size(writing_mode);
3039 let max_size = style.max_box_size(writing_mode);
3040
3041 Self {
3042 min: outer_size(min_size.map(|v| get_size_for_axis(v).unwrap_or_default())),
3043 preferred: outer_size(size.map(|v| get_size_for_axis(v).unwrap_or_default())),
3044 max: outer_option_size(max_size.map(get_size_for_axis)),
3045 percentage: get_size_percentage_contribution(&size, &max_size),
3046 }
3047 }
3048}
3049
3050struct RowspanToDistribute<'a> {
3051 coordinates: TableSlotCoordinates,
3052 cell: AtomicRef<'a, TableSlotCell>,
3053 measure: &'a CellOrTrackMeasure,
3054}
3055
3056impl RowspanToDistribute<'_> {
3057 fn range(&self) -> Range<usize> {
3058 self.coordinates.y..self.coordinates.y + self.cell.rowspan
3059 }
3060
3061 fn fully_encloses(&self, other: &RowspanToDistribute) -> bool {
3062 other.coordinates.y > self.coordinates.y && other.range().end < self.range().end
3063 }
3064}
3065
3066#[derive(Debug)]
3069struct ColspanToDistribute {
3070 starting_column: usize,
3071 span: usize,
3072 content_sizes: ContentSizes,
3073 percentage: Option<Percentage>,
3074}
3075
3076impl ColspanToDistribute {
3077 fn comparison_for_sort(a: &Self, b: &Self) -> Ordering {
3081 a.span
3082 .cmp(&b.span)
3083 .then_with(|| a.starting_column.cmp(&b.starting_column))
3084 }
3085
3086 fn range(&self) -> Range<usize> {
3087 self.starting_column..self.starting_column + self.span
3088 }
3089}
3090
3091#[cfg(test)]
3092mod test {
3093 use app_units::MIN_AU;
3094
3095 use super::*;
3096 use crate::sizing::ContentSizes;
3097
3098 #[test]
3099 fn test_colspan_to_distribute_first_sort_by_span() {
3100 let a = ColspanToDistribute {
3101 starting_column: 0,
3102 span: 0,
3103 content_sizes: ContentSizes {
3104 min_content: MIN_AU,
3105 max_content: MIN_AU,
3106 },
3107 percentage: None,
3108 };
3109
3110 let b = ColspanToDistribute {
3111 starting_column: 0,
3112 span: 1,
3113 content_sizes: ContentSizes {
3114 min_content: MIN_AU,
3115 max_content: MIN_AU,
3116 },
3117 percentage: None,
3118 };
3119
3120 let ordering = ColspanToDistribute::comparison_for_sort(&a, &b);
3121 assert_eq!(ordering, Ordering::Less);
3122 }
3123
3124 #[test]
3125 fn test_colspan_to_distribute_if_spans_are_equal_sort_by_starting_column() {
3126 let a = ColspanToDistribute {
3127 starting_column: 0,
3128 span: 0,
3129 content_sizes: ContentSizes {
3130 min_content: MIN_AU,
3131 max_content: MIN_AU,
3132 },
3133 percentage: None,
3134 };
3135
3136 let b = ColspanToDistribute {
3137 starting_column: 1,
3138 span: 0,
3139 content_sizes: ContentSizes {
3140 min_content: MIN_AU,
3141 max_content: MIN_AU,
3142 },
3143 percentage: None,
3144 };
3145
3146 let ordering = ColspanToDistribute::comparison_for_sort(&a, &b);
3147 assert_eq!(ordering, Ordering::Less);
3148 }
3149}