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::CacheableLayoutResult;
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: CacheableLayoutResult,
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::AbsoluteOrFixedPositioned(_)))
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 = Self::distribute_width_to_columns(
624 colspan_cell_min_size,
625 &self.columns[column_range.clone()],
626 );
627 {
628 let column_span = &mut self.columns[colspan_cell_constraints.range()];
629 for (column, minimum_size) in column_span.iter_mut().zip(distributed_minimum) {
630 column.content_sizes.min_content.max_assign(minimum_size);
631 }
632 }
633
634 let colspan_cell_max_size = (colspan_cell_constraints.content_sizes.max_content -
635 total_border_spacing)
636 .max(Au::zero());
637 let distributed_maximum = Self::distribute_width_to_columns(
638 colspan_cell_max_size,
639 &self.columns[colspan_cell_constraints.range()],
640 );
641 {
642 let column_span = &mut self.columns[colspan_cell_constraints.range()];
643 for (column, maximum_size) in column_span.iter_mut().zip(distributed_maximum) {
644 column
645 .content_sizes
646 .max_content
647 .max_assign(maximum_size.max(column.content_sizes.min_content));
648 }
649 }
650 }
651
652 fn compute_measures(&mut self, layout_context: &LayoutContext, writing_mode: WritingMode) {
653 self.compute_track_constrainedness_and_has_originating_cells(writing_mode);
654 self.compute_cell_measures(layout_context, writing_mode);
655 self.compute_column_measures(writing_mode);
656 }
657
658 fn compute_grid_min_max(&self) -> ContentSizes {
660 let mut largest_percentage_column_max_size = Au::zero();
673 let mut percent_sum = 0.;
674 let mut non_percent_columns_max_sum = Au::zero();
675 let mut grid_min_max = ContentSizes::zero();
676 for column in self.columns.iter() {
677 match column.percentage {
678 Some(percentage) if !percentage.is_zero() => {
679 largest_percentage_column_max_size.max_assign(
680 column
681 .content_sizes
682 .max_content
683 .scale_by(1.0 / percentage.0),
684 );
685 percent_sum += percentage.0;
686 },
687 _ => {
688 non_percent_columns_max_sum += column.content_sizes.max_content;
689 },
690 }
691
692 grid_min_max += column.content_sizes;
693 }
694
695 grid_min_max
696 .max_content
697 .max_assign(largest_percentage_column_max_size);
698
699 if !percent_sum.is_zero() &&
703 self.table
704 .percentage_columns_allowed_for_inline_content_sizes
705 {
706 let total_inline_size =
707 non_percent_columns_max_sum.scale_by(1.0 / (1.0 - percent_sum.min(1.0)));
708 grid_min_max.max_content.max_assign(total_inline_size);
709 }
710
711 assert!(
712 grid_min_max.min_content <= grid_min_max.max_content,
713 "GRIDMAX should never be smaller than GRIDMIN {:?}",
714 grid_min_max
715 );
716
717 let inline_border_spacing = self.table.total_border_spacing().inline;
718 grid_min_max.min_content += inline_border_spacing;
719 grid_min_max.max_content += inline_border_spacing;
720 grid_min_max
721 }
722
723 fn compute_caption_minimum_inline_size(&self, layout_context: &LayoutContext) -> Au {
725 let containing_block = IndefiniteContainingBlock {
726 size: LogicalVec2::default(),
727 style: &self.table.style,
728 };
729 self.table
730 .captions
731 .iter()
732 .map(|caption| {
733 caption
734 .borrow()
735 .context
736 .outer_inline_content_sizes(
737 layout_context,
738 &containing_block,
739 &LogicalVec2::zero(),
740 false, )
742 .sizes
743 .min_content
744 })
745 .max()
746 .unwrap_or_default()
747 }
748
749 fn compute_table_width(&mut self, containing_block_for_children: &ContainingBlock) {
750 self.table_width = containing_block_for_children.size.inline;
755
756 self.assignable_width = self.table_width - self.table.total_border_spacing().inline;
760
761 self.basis_for_cell_padding_percentage =
764 self.table_width - self.table.border_spacing().inline * 2;
765 }
766
767 fn distribute_width_to_columns(target_inline_size: Au, columns: &[ColumnLayout]) -> Vec<Au> {
770 if columns.is_empty() {
773 return Vec::new();
774 }
775
776 let mut min_content_sizing_guesses = Vec::new();
805 let mut min_content_percentage_sizing_guesses = Vec::new();
806 let mut min_content_specified_sizing_guesses = Vec::new();
807 let mut max_content_sizing_guesses = Vec::new();
808
809 for column in columns {
810 let min_content_width = column.content_sizes.min_content;
811 let max_content_width = column.content_sizes.max_content;
812 let constrained = column.constrained;
813
814 let (
815 min_content_percentage_sizing_guess,
816 min_content_specified_sizing_guess,
817 max_content_sizing_guess,
818 ) = if let Some(percentage) = column.percentage {
819 let resolved = target_inline_size.scale_by(percentage.0);
820 let percent_guess = min_content_width.max(resolved);
821 (percent_guess, percent_guess, percent_guess)
822 } else if constrained {
823 (min_content_width, max_content_width, max_content_width)
824 } else {
825 (min_content_width, min_content_width, max_content_width)
826 };
827
828 min_content_sizing_guesses.push(min_content_width);
829 min_content_percentage_sizing_guesses.push(min_content_percentage_sizing_guess);
830 min_content_specified_sizing_guesses.push(min_content_specified_sizing_guess);
831 max_content_sizing_guesses.push(max_content_sizing_guess);
832 }
833
834 let max_content_sizing_sum = max_content_sizing_guesses.iter().sum();
842 if target_inline_size >= max_content_sizing_sum {
843 Self::distribute_extra_width_to_columns(
844 columns,
845 &mut max_content_sizing_guesses,
846 max_content_sizing_sum,
847 target_inline_size,
848 );
849 return max_content_sizing_guesses;
850 }
851 let min_content_specified_sizing_sum = min_content_specified_sizing_guesses.iter().sum();
852 if target_inline_size == min_content_specified_sizing_sum {
853 return min_content_specified_sizing_guesses;
854 }
855 let min_content_percentage_sizing_sum = min_content_percentage_sizing_guesses.iter().sum();
856 if target_inline_size == min_content_percentage_sizing_sum {
857 return min_content_percentage_sizing_guesses;
858 }
859 let min_content_sizes_sum = min_content_sizing_guesses.iter().sum();
860 if target_inline_size <= min_content_sizes_sum {
861 return min_content_sizing_guesses;
862 }
863
864 let bounds = |sum_a, sum_b| target_inline_size > sum_a && target_inline_size < sum_b;
865
866 let blend = |a: &[Au], sum_a: Au, b: &[Au], sum_b: Au| {
867 let weight_a = (target_inline_size - sum_b).to_f32_px() / (sum_a - sum_b).to_f32_px();
869 let weight_b = 1.0 - weight_a;
870
871 let mut remaining_assignable_width = target_inline_size;
872 let mut widths: Vec<Au> = a
873 .iter()
874 .zip(b.iter())
875 .map(|(guess_a, guess_b)| {
876 let column_width = guess_a.scale_by(weight_a) + guess_b.scale_by(weight_b);
877 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 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 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 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 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 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 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 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 let has_originating_cells_columns = all_columns.clone().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 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 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 self.cells_laid_out = if layout_context.use_rayon {
1148 self.table
1149 .slots
1150 .par_iter()
1151 .enumerate()
1152 .map(|(row_index, row_slots)| {
1153 row_slots
1154 .par_iter()
1155 .enumerate()
1156 .map(|(column_index, slot)| {
1157 layout_table_slot(
1158 TableSlotCoordinates::new(column_index, row_index),
1159 slot,
1160 )
1161 })
1162 .collect()
1163 })
1164 .collect()
1165 } else {
1166 self.table
1167 .slots
1168 .iter()
1169 .enumerate()
1170 .map(|(row_index, row_slots)| {
1171 row_slots
1172 .iter()
1173 .enumerate()
1174 .map(|(column_index, slot)| {
1175 layout_table_slot(
1176 TableSlotCoordinates::new(column_index, row_index),
1177 slot,
1178 )
1179 })
1180 .collect()
1181 })
1182 .collect()
1183 };
1184
1185 for row_index in 0..self.table.size.height {
1188 for column_index in 0..self.table.size.width {
1189 let Some(layout) = &self.cells_laid_out[row_index][column_index] else {
1190 continue;
1191 };
1192
1193 self.cell_measures[row_index][column_index]
1194 .block
1195 .content_sizes
1196 .max_assign(layout.outer_block_size().into());
1197 }
1198 }
1199 }
1200
1201 fn do_first_row_layout(&mut self, writing_mode: WritingMode) -> Vec<Au> {
1204 let mut row_sizes = (0..self.table.size.height)
1205 .map(|row_index| {
1206 let (mut max_ascent, mut max_descent, mut max_row_height) =
1207 (Au::zero(), Au::zero(), Au::zero());
1208
1209 for column_index in 0..self.table.size.width {
1210 let cell = match self.table.slots[row_index][column_index] {
1211 TableSlot::Cell(ref cell) => cell,
1212 _ => continue,
1213 };
1214
1215 let layout = match self.cells_laid_out[row_index][column_index] {
1216 Some(ref layout) => layout,
1217 None => {
1218 warn!(
1219 "Did not find a layout at a slot index with an originating cell."
1220 );
1221 continue;
1222 },
1223 };
1224
1225 let cell = cell.borrow();
1226 let outer_block_size = layout.outer_block_size();
1227 if cell.rowspan == 1 {
1228 max_row_height.max_assign(outer_block_size);
1229 }
1230
1231 if cell.content_alignment() == CellContentAlignment::Baseline {
1232 let ascent = layout.ascent();
1233 let border_padding_start =
1234 layout.border.block_start + layout.padding.block_start;
1235 let border_padding_end = layout.border.block_end + layout.padding.block_end;
1236 max_ascent.max_assign(ascent + border_padding_start);
1237
1238 if cell.rowspan == 1 {
1242 max_descent.max_assign(
1243 layout.layout.content_block_size - ascent + border_padding_end,
1244 );
1245 }
1246 }
1247 }
1248 self.row_baselines.push(max_ascent);
1249 max_row_height.max(max_ascent + max_descent)
1250 })
1251 .collect();
1252 self.calculate_row_sizes_after_first_layout(&mut row_sizes, writing_mode);
1253 row_sizes
1254 }
1255
1256 #[allow(clippy::ptr_arg)] fn calculate_row_sizes_after_first_layout(
1261 &mut self,
1262 row_sizes: &mut Vec<Au>,
1263 writing_mode: WritingMode,
1264 ) {
1265 let mut cells_to_distribute = Vec::new();
1266 let mut total_percentage = 0.;
1267 #[allow(clippy::needless_range_loop)] for row_index in 0..self.table.size.height {
1269 let row_measure = self
1270 .table
1271 .get_row_measure_for_row_at_index(writing_mode, row_index);
1272 row_sizes[row_index].max_assign(row_measure.content_sizes.min_content);
1273
1274 let mut percentage = row_measure.percentage.unwrap_or_default().0;
1275 for column_index in 0..self.table.size.width {
1276 let cell_percentage = self.cell_measures[row_index][column_index]
1277 .block
1278 .percentage
1279 .unwrap_or_default()
1280 .0;
1281 percentage = percentage.max(cell_percentage);
1282
1283 let cell_measure = &self.cell_measures[row_index][column_index].block;
1284 let cell = match self.table.slots[row_index][column_index] {
1285 TableSlot::Cell(ref cell) if cell.borrow().rowspan > 1 => cell,
1286 TableSlot::Cell(_) => {
1287 row_sizes[row_index].max_assign(cell_measure.content_sizes.max_content);
1290 continue;
1291 },
1292 _ => continue,
1293 };
1294
1295 cells_to_distribute.push(RowspanToDistribute {
1296 coordinates: TableSlotCoordinates::new(column_index, row_index),
1297 cell: cell.borrow(),
1298 measure: cell_measure,
1299 });
1300 }
1301
1302 self.rows[row_index].percent = Percentage(percentage.min(1. - total_percentage));
1303 total_percentage += self.rows[row_index].percent.0;
1304 }
1305
1306 cells_to_distribute.sort_by(|a, b| {
1307 if a.range() == b.range() {
1308 return a
1309 .measure
1310 .content_sizes
1311 .min_content
1312 .cmp(&b.measure.content_sizes.min_content);
1313 }
1314 if a.fully_encloses(b) {
1315 return std::cmp::Ordering::Greater;
1316 }
1317 if b.fully_encloses(a) {
1318 return std::cmp::Ordering::Less;
1319 }
1320 a.coordinates.y.cmp(&b.coordinates.y)
1321 });
1322
1323 for rowspan_to_distribute in cells_to_distribute {
1324 let rows_spanned = rowspan_to_distribute.range();
1325 let current_rows_size = rows_spanned.clone().map(|index| row_sizes[index]).sum();
1326 let border_spacing_spanned =
1327 self.table.border_spacing().block * (rows_spanned.len() - 1) as i32;
1328 let excess_size = (rowspan_to_distribute.measure.content_sizes.min_content -
1329 current_rows_size -
1330 border_spacing_spanned)
1331 .max(Au::zero());
1332
1333 self.distribute_extra_size_to_rows(
1334 excess_size,
1335 rows_spanned,
1336 row_sizes,
1337 None,
1338 true, );
1340 }
1341 }
1342
1343 fn distribute_extra_size_to_rows(
1346 &self,
1347 mut excess_size: Au,
1348 track_range: Range<usize>,
1349 track_sizes: &mut [Au],
1350 percentage_resolution_size: Option<Au>,
1351 rowspan_distribution: bool,
1352 ) {
1353 if excess_size.is_zero() {
1354 return;
1355 }
1356
1357 let is_constrained = |track_index: &usize| self.rows[*track_index].constrained;
1358 let is_unconstrained = |track_index: &usize| !is_constrained(track_index);
1359 let is_empty: Vec<bool> = track_sizes.iter().map(|size| size.is_zero()).collect();
1360 let is_not_empty = |track_index: &usize| !is_empty[*track_index];
1361 let other_row_that_starts_a_rowspan = |track_index: &usize| {
1362 *track_index != track_range.start &&
1363 self.rows[*track_index].has_cell_with_span_greater_than_one
1364 };
1365
1366 if let Some(percentage_resolution_size) = percentage_resolution_size {
1370 let get_percent_block_size_deficit = |row_index: usize, track_size: Au| {
1371 let size_needed_for_percent =
1372 percentage_resolution_size.scale_by(self.rows[row_index].percent.0);
1373 (size_needed_for_percent - track_size).max(Au::zero())
1374 };
1375 let percent_block_size_deficit: Au = track_range
1376 .clone()
1377 .map(|index| get_percent_block_size_deficit(index, track_sizes[index]))
1378 .sum();
1379 let percent_distributable_block_size = percent_block_size_deficit.min(excess_size);
1380 if percent_distributable_block_size > Au::zero() {
1381 for track_index in track_range.clone() {
1382 let row_deficit =
1383 get_percent_block_size_deficit(track_index, track_sizes[track_index]);
1384 if row_deficit > Au::zero() {
1385 let ratio =
1386 row_deficit.to_f32_px() / percent_block_size_deficit.to_f32_px();
1387 let size = percent_distributable_block_size.scale_by(ratio);
1388 track_sizes[track_index] += size;
1389 excess_size -= size;
1390 }
1391 }
1392 }
1393 }
1394
1395 if rowspan_distribution {
1398 let rows_that_start_rowspan: Vec<usize> = track_range
1399 .clone()
1400 .filter(other_row_that_starts_a_rowspan)
1401 .collect();
1402 if !rows_that_start_rowspan.is_empty() {
1403 let scale = 1.0 / rows_that_start_rowspan.len() as f32;
1404 for track_index in rows_that_start_rowspan.iter() {
1405 track_sizes[*track_index] += excess_size.scale_by(scale);
1406 }
1407 return;
1408 }
1409 }
1410
1411 let unconstrained_non_empty_rows: Vec<usize> = track_range
1413 .clone()
1414 .filter(is_unconstrained)
1415 .filter(is_not_empty)
1416 .collect();
1417 if !unconstrained_non_empty_rows.is_empty() {
1418 let total_size: Au = unconstrained_non_empty_rows
1419 .iter()
1420 .map(|index| track_sizes[*index])
1421 .sum();
1422 for track_index in unconstrained_non_empty_rows.iter() {
1423 let scale = track_sizes[*track_index].to_f32_px() / total_size.to_f32_px();
1424 track_sizes[*track_index] += excess_size.scale_by(scale);
1425 }
1426 return;
1427 }
1428
1429 let (non_empty_rows, empty_rows): (Vec<usize>, Vec<usize>) =
1430 track_range.clone().partition(is_not_empty);
1431 let only_have_empty_rows = empty_rows.len() == track_range.len();
1432 if !empty_rows.is_empty() {
1433 if rowspan_distribution && only_have_empty_rows {
1436 track_sizes[*empty_rows.last().unwrap()] += excess_size;
1437 return;
1438 }
1439
1440 let non_empty_rows_all_constrained = !non_empty_rows.iter().any(is_unconstrained);
1443 if only_have_empty_rows || non_empty_rows_all_constrained {
1444 let mut rows_to_grow = &empty_rows;
1447 let unconstrained_empty_rows: Vec<usize> = rows_to_grow
1448 .iter()
1449 .copied()
1450 .filter(is_unconstrained)
1451 .collect();
1452 if !unconstrained_empty_rows.is_empty() {
1453 rows_to_grow = &unconstrained_empty_rows;
1454 }
1455
1456 let scale = 1.0 / rows_to_grow.len() as f32;
1458 for track_index in rows_to_grow.iter() {
1459 track_sizes[*track_index] += excess_size.scale_by(scale);
1460 }
1461 return;
1462 }
1463 }
1464
1465 if !non_empty_rows.is_empty() {
1468 let total_size: Au = non_empty_rows.iter().map(|index| track_sizes[*index]).sum();
1469 for track_index in non_empty_rows.iter() {
1470 let scale = track_sizes[*track_index].to_f32_px() / total_size.to_f32_px();
1471 track_sizes[*track_index] += excess_size.scale_by(scale);
1472 }
1473 }
1474 }
1475
1476 fn compute_table_height_and_final_row_heights(
1479 &mut self,
1480 mut row_sizes: Vec<Au>,
1481 containing_block_for_children: &ContainingBlock,
1482 ) {
1483 let table_height_from_style = containing_block_for_children.size.block.definite_or_min();
1490
1491 let block_border_spacing = self.table.total_border_spacing().block;
1492 let table_height_from_rows = row_sizes.iter().sum::<Au>() + block_border_spacing;
1493 self.final_table_height = table_height_from_rows.max(table_height_from_style);
1494
1495 if self.final_table_height == table_height_from_rows {
1498 self.row_sizes = row_sizes;
1499 return;
1500 }
1501
1502 self.distribute_extra_size_to_rows(
1507 self.final_table_height - table_height_from_rows,
1508 0..self.table.size.height,
1509 &mut row_sizes,
1510 Some(self.final_table_height),
1511 false, );
1513 self.row_sizes = row_sizes;
1514 }
1515
1516 fn layout_caption(
1517 &self,
1518 caption: &TableCaption,
1519 layout_context: &LayoutContext,
1520 parent_positioning_context: &mut PositioningContext,
1521 ) -> BoxFragment {
1522 let containing_block = &ContainingBlock {
1523 size: ContainingBlockSize {
1524 inline: self.table_width + self.pbm.padding_border_sums.inline,
1525 block: SizeConstraint::default(),
1526 },
1527 style: &self.table.style,
1528 };
1529
1530 let ignore_block_margins_for_stretch = LogicalSides1D::new(false, false);
1534
1535 let mut positioning_context =
1536 PositioningContext::new_for_layout_box_base(&caption.context.base);
1537 let mut box_fragment = caption.context.layout_in_flow_block_level(
1538 layout_context,
1539 positioning_context
1540 .as_mut()
1541 .unwrap_or(parent_positioning_context),
1542 containing_block,
1543 None, ignore_block_margins_for_stretch,
1545 );
1546
1547 if let Some(mut positioning_context) = positioning_context.take() {
1548 positioning_context.layout_collected_children(layout_context, &mut box_fragment);
1549 parent_positioning_context.append(positioning_context);
1550 }
1551
1552 box_fragment
1553 }
1554
1555 #[servo_tracing::instrument(name = "Table::layout", skip_all)]
1558 fn layout(
1559 mut self,
1560 layout_context: &LayoutContext,
1561 positioning_context: &mut PositioningContext,
1562 containing_block_for_children: &ContainingBlock,
1563 containing_block_for_table: &ContainingBlock,
1564 ) -> CacheableLayoutResult {
1565 let table_writing_mode = containing_block_for_children.style.writing_mode;
1566 self.compute_border_collapse(table_writing_mode);
1567 let layout_style = self.table.layout_style(Some(&self));
1568
1569 self.pbm = layout_style
1570 .padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1571 table_writing_mode,
1572 containing_block_for_table.size.inline,
1573 );
1574 self.compute_measures(layout_context, table_writing_mode);
1575 self.compute_table_width(containing_block_for_children);
1576
1577 let containing_block_for_logical_conversion = ContainingBlock {
1592 size: ContainingBlockSize {
1593 inline: self.table_width,
1594 block: containing_block_for_table.size.block,
1595 },
1596 style: containing_block_for_children.style,
1597 };
1598 let offset_from_wrapper = -self.pbm.padding - self.pbm.border;
1599 let mut current_block_offset = offset_from_wrapper.block_start;
1600
1601 let mut table_layout = CacheableLayoutResult {
1602 fragments: Vec::new(),
1603 content_block_size: Zero::zero(),
1604 content_inline_size_for_table: None,
1605 baselines: Baselines::default(),
1606 depends_on_block_constraints: true,
1607 specific_layout_info: Some(SpecificLayoutInfo::TableWrapper),
1608 collapsible_margins_in_children: CollapsedBlockMargins::zero(),
1609 };
1610
1611 #[derive(EnumIter, PartialEq)]
1612 enum TableWrapperSection {
1613 TopCaptions,
1614 Grid,
1615 BottomCaptions,
1616 }
1617 impl TableWrapperSection {
1618 fn accepts_caption(&self, caption: &TableCaption) -> bool {
1619 match caption.context.style().clone_caption_side() {
1620 CaptionSide::Top => *self == TableWrapperSection::TopCaptions,
1621 CaptionSide::Bottom => *self == TableWrapperSection::BottomCaptions,
1622 }
1623 }
1624 }
1625
1626 for section in TableWrapperSection::iter() {
1627 if section == TableWrapperSection::Grid {
1628 let original_positioning_context_length = positioning_context.len();
1629 let mut grid_fragment = self.layout_grid(
1630 layout_context,
1631 positioning_context,
1632 &containing_block_for_logical_conversion,
1633 containing_block_for_children,
1634 );
1635
1636 let logical_grid_content_rect = grid_fragment
1639 .content_rect()
1640 .to_logical(&containing_block_for_logical_conversion);
1641 let grid_pbm = grid_fragment
1642 .padding_border_margin()
1643 .to_logical(table_writing_mode);
1644 table_layout.baselines = grid_fragment.baselines(table_writing_mode).offset(
1645 current_block_offset +
1646 logical_grid_content_rect.start_corner.block +
1647 grid_pbm.block_start,
1648 );
1649
1650 grid_fragment.base.rect = LogicalRect {
1651 start_corner: LogicalVec2 {
1652 inline: offset_from_wrapper.inline_start + grid_pbm.inline_start,
1653 block: current_block_offset + grid_pbm.block_start,
1654 },
1655 size: grid_fragment.base.rect.size.to_logical(table_writing_mode),
1656 }
1657 .as_physical(Some(&containing_block_for_logical_conversion));
1658
1659 current_block_offset += grid_fragment
1660 .border_rect()
1661 .size
1662 .to_logical(table_writing_mode)
1663 .block;
1664 if logical_grid_content_rect.size.inline < self.table_width {
1665 table_layout.content_inline_size_for_table =
1667 Some(logical_grid_content_rect.size.inline);
1668 }
1669
1670 let grid_fragment = Fragment::Box(ArcRefCell::new(grid_fragment));
1671 positioning_context.adjust_static_position_of_hoisted_fragments(
1672 &grid_fragment,
1673 original_positioning_context_length,
1674 );
1675 table_layout.fragments.push(grid_fragment);
1676 } else {
1677 let caption_fragments = self.table.captions.iter().filter_map(|caption| {
1678 let caption = caption.borrow();
1679 if !section.accepts_caption(&caption) {
1680 return None;
1681 }
1682
1683 let original_positioning_context_length = positioning_context.len();
1684 let mut caption_fragment =
1685 self.layout_caption(&caption, layout_context, positioning_context);
1686
1687 let caption_pbm = caption_fragment
1690 .padding_border_margin()
1691 .to_logical(table_writing_mode);
1692
1693 let caption_style = caption_fragment.style().clone();
1694 let caption_relative_offset = match caption_style.clone_position() {
1695 Position::Relative => {
1696 relative_adjustement(&caption_style, containing_block_for_children)
1697 },
1698 _ => LogicalVec2::zero(),
1699 };
1700
1701 caption_fragment.base.rect = LogicalRect {
1702 start_corner: LogicalVec2 {
1703 inline: offset_from_wrapper.inline_start + caption_pbm.inline_start,
1704 block: current_block_offset + caption_pbm.block_start,
1705 } + caption_relative_offset,
1706 size: caption_fragment
1707 .content_rect()
1708 .size
1709 .to_logical(table_writing_mode),
1710 }
1711 .as_physical(Some(&containing_block_for_logical_conversion));
1712
1713 current_block_offset += caption_fragment
1714 .margin_rect()
1715 .size
1716 .to_logical(table_writing_mode)
1717 .block;
1718
1719 let caption_fragment = Fragment::Box(ArcRefCell::new(caption_fragment));
1720 positioning_context.adjust_static_position_of_hoisted_fragments(
1721 &caption_fragment,
1722 original_positioning_context_length,
1723 );
1724
1725 caption.context.base.set_fragment(caption_fragment.clone());
1726 Some(caption_fragment)
1727 });
1728 table_layout.fragments.extend(caption_fragments);
1729 }
1730 }
1731
1732 table_layout.content_block_size = current_block_offset + offset_from_wrapper.block_end;
1733 table_layout
1734 }
1735
1736 fn layout_grid(
1739 &mut self,
1740 layout_context: &LayoutContext,
1741 positioning_context: &mut PositioningContext,
1742 containing_block_for_logical_conversion: &ContainingBlock,
1743 containing_block_for_children: &ContainingBlock,
1744 ) -> BoxFragment {
1745 self.distributed_column_widths =
1746 Self::distribute_width_to_columns(self.assignable_width, &self.columns);
1747 self.layout_cells_in_row(layout_context, containing_block_for_children);
1748 let table_writing_mode = containing_block_for_children.style.writing_mode;
1749 let first_layout_row_heights = self.do_first_row_layout(table_writing_mode);
1750 self.compute_table_height_and_final_row_heights(
1751 first_layout_row_heights,
1752 containing_block_for_children,
1753 );
1754
1755 assert_eq!(self.table.size.height, self.row_sizes.len());
1756 assert_eq!(self.table.size.width, self.distributed_column_widths.len());
1757
1758 if self.table.size.width == 0 && self.table.size.height == 0 {
1759 let content_rect = LogicalRect {
1760 start_corner: LogicalVec2::zero(),
1761 size: LogicalVec2 {
1762 inline: self.table_width,
1763 block: self.final_table_height,
1764 },
1765 }
1766 .as_physical(Some(containing_block_for_logical_conversion));
1767 return BoxFragment::new(
1768 self.table.grid_base_fragment_info,
1769 self.table.grid_style.clone(),
1770 Vec::new(),
1771 content_rect,
1772 self.pbm.padding.to_physical(table_writing_mode),
1773 self.pbm.border.to_physical(table_writing_mode),
1774 PhysicalSides::zero(),
1775 self.specific_layout_info_for_grid(),
1776 );
1777 }
1778
1779 let mut table_fragments = Vec::new();
1780 let table_and_track_dimensions = TableAndTrackDimensions::new(self);
1781 self.make_fragments_for_columns_and_column_groups(
1782 &table_and_track_dimensions,
1783 &mut table_fragments,
1784 );
1785
1786 let mut baselines = Baselines::default();
1787 let mut row_group_fragment_layout = None;
1788 for row_index in 0..self.table.size.height {
1789 if row_index == 0 {
1799 let row_end = table_and_track_dimensions
1800 .get_row_rect(0)
1801 .max_block_position();
1802 baselines.first = Some(row_end);
1803 baselines.last = Some(row_end);
1804 }
1805
1806 let row_is_collapsed = self.is_row_collapsed(row_index);
1807 let table_row = self.table.rows[row_index].borrow();
1808 let mut row_fragment_layout = RowFragmentLayout::new(
1809 &table_row,
1810 row_index,
1811 &table_and_track_dimensions,
1812 &self.table.style,
1813 );
1814
1815 let old_row_group_index = row_group_fragment_layout
1816 .as_ref()
1817 .map(|layout: &RowGroupFragmentLayout| layout.index);
1818 if table_row.group_index != old_row_group_index {
1819 if let Some(old_row_group_layout) = row_group_fragment_layout.take() {
1821 table_fragments.push(old_row_group_layout.finish(
1822 layout_context,
1823 positioning_context,
1824 containing_block_for_logical_conversion,
1825 containing_block_for_children,
1826 ));
1827 }
1828
1829 if let Some(new_group_index) = table_row.group_index {
1831 row_group_fragment_layout = Some(RowGroupFragmentLayout::new(
1832 self.table.row_groups[new_group_index].clone(),
1833 new_group_index,
1834 &table_and_track_dimensions,
1835 ));
1836 }
1837 }
1838
1839 let column_indices = 0..self.table.size.width;
1840 row_fragment_layout.fragments.reserve(self.table.size.width);
1841 for column_index in column_indices {
1842 self.do_final_cell_layout(
1843 row_index,
1844 column_index,
1845 &table_and_track_dimensions,
1846 &mut baselines,
1847 &mut row_fragment_layout,
1848 row_group_fragment_layout.as_mut(),
1849 positioning_context,
1850 self.is_column_collapsed(column_index) || row_is_collapsed,
1851 );
1852 }
1853
1854 let row_fragment = row_fragment_layout.finish(
1855 layout_context,
1856 positioning_context,
1857 containing_block_for_logical_conversion,
1858 containing_block_for_children,
1859 &mut row_group_fragment_layout,
1860 );
1861
1862 match row_group_fragment_layout.as_mut() {
1863 Some(layout) => layout.fragments.push(row_fragment),
1864 None => table_fragments.push(row_fragment),
1865 }
1866 }
1867
1868 if let Some(row_group_layout) = row_group_fragment_layout.take() {
1869 table_fragments.push(row_group_layout.finish(
1870 layout_context,
1871 positioning_context,
1872 containing_block_for_logical_conversion,
1873 containing_block_for_children,
1874 ));
1875 }
1876
1877 let content_rect = LogicalRect {
1878 start_corner: LogicalVec2::zero(),
1879 size: LogicalVec2 {
1880 inline: table_and_track_dimensions.table_rect.max_inline_position(),
1881 block: table_and_track_dimensions.table_rect.max_block_position(),
1882 },
1883 }
1884 .as_physical(Some(containing_block_for_logical_conversion));
1885 BoxFragment::new(
1886 self.table.grid_base_fragment_info,
1887 self.table.grid_style.clone(),
1888 table_fragments,
1889 content_rect,
1890 self.pbm.padding.to_physical(table_writing_mode),
1891 self.pbm.border.to_physical(table_writing_mode),
1892 PhysicalSides::zero(),
1893 self.specific_layout_info_for_grid(),
1894 )
1895 .with_baselines(baselines)
1896 }
1897
1898 fn specific_layout_info_for_grid(&mut self) -> Option<SpecificLayoutInfo> {
1899 mem::take(&mut self.collapsed_borders).map(|mut collapsed_borders| {
1900 let mut track_sizes = LogicalVec2 {
1903 inline: mem::take(&mut self.distributed_column_widths),
1904 block: mem::take(&mut self.row_sizes),
1905 };
1906 for (column_index, column_size) in track_sizes.inline.iter_mut().enumerate() {
1907 if self.is_column_collapsed(column_index) {
1908 mem::take(column_size);
1909 }
1910 }
1911 for (row_index, row_size) in track_sizes.block.iter_mut().enumerate() {
1912 if self.is_row_collapsed(row_index) {
1913 mem::take(row_size);
1914 }
1915 }
1916 let writing_mode = self.table.style.writing_mode;
1917 if !writing_mode.is_bidi_ltr() {
1918 track_sizes.inline.reverse();
1919 collapsed_borders.inline.reverse();
1920 for border_line in &mut collapsed_borders.block {
1921 border_line.reverse();
1922 }
1923 }
1924 SpecificLayoutInfo::TableGridWithCollapsedBorders(Box::new(SpecificTableGridInfo {
1925 collapsed_borders: if writing_mode.is_horizontal() {
1926 PhysicalVec::new(collapsed_borders.inline, collapsed_borders.block)
1927 } else {
1928 PhysicalVec::new(collapsed_borders.block, collapsed_borders.inline)
1929 },
1930 track_sizes: if writing_mode.is_horizontal() {
1931 PhysicalVec::new(track_sizes.inline, track_sizes.block)
1932 } else {
1933 PhysicalVec::new(track_sizes.block, track_sizes.inline)
1934 },
1935 }))
1936 })
1937 }
1938
1939 fn is_row_collapsed(&self, row_index: usize) -> bool {
1940 let Some(row) = &self.table.rows.get(row_index) else {
1941 return false;
1942 };
1943
1944 let row = row.borrow();
1945 if row.base.style.get_inherited_box().visibility == Visibility::Collapse {
1946 return true;
1947 }
1948 let row_group = match row.group_index {
1949 Some(group_index) => self.table.row_groups[group_index].borrow(),
1950 None => return false,
1951 };
1952 row_group.base.style.get_inherited_box().visibility == Visibility::Collapse
1953 }
1954
1955 fn is_column_collapsed(&self, column_index: usize) -> bool {
1956 let Some(column) = &self.table.columns.get(column_index) else {
1957 return false;
1958 };
1959 let column = column.borrow();
1960 if column.base.style.get_inherited_box().visibility == Visibility::Collapse {
1961 return true;
1962 }
1963 let col_group = match column.group_index {
1964 Some(group_index) => self.table.column_groups[group_index].borrow(),
1965 None => return false,
1966 };
1967 col_group.base.style.get_inherited_box().visibility == Visibility::Collapse
1968 }
1969
1970 #[allow(clippy::too_many_arguments)]
1971 fn do_final_cell_layout(
1972 &mut self,
1973 row_index: usize,
1974 column_index: usize,
1975 dimensions: &TableAndTrackDimensions,
1976 baselines: &mut Baselines,
1977 row_fragment_layout: &mut RowFragmentLayout,
1978 row_group_fragment_layout: Option<&mut RowGroupFragmentLayout>,
1979 positioning_context_for_table: &mut PositioningContext,
1980 is_collapsed: bool,
1981 ) {
1982 let row_group_positioning_context =
1985 row_group_fragment_layout.and_then(|layout| layout.positioning_context.as_mut());
1986 let positioning_context = row_fragment_layout
1987 .positioning_context
1988 .as_mut()
1989 .or(row_group_positioning_context)
1990 .unwrap_or(positioning_context_for_table);
1991
1992 let layout = match self.cells_laid_out[row_index][column_index].take() {
1993 Some(layout) => layout,
1994 None => {
1995 return;
1996 },
1997 };
1998 let cell = match self.table.slots[row_index][column_index] {
1999 TableSlot::Cell(ref cell) => cell,
2000 _ => {
2001 warn!("Did not find a non-spanned cell at index with layout.");
2002 return;
2003 },
2004 }
2005 .borrow();
2006
2007 let row_block_offset = row_fragment_layout.rect.start_corner.block;
2009 let row_baseline = self.row_baselines[row_index];
2010 if cell.content_alignment() == CellContentAlignment::Baseline && !layout.is_empty() {
2011 let baseline = row_block_offset + row_baseline;
2012 if row_index == 0 {
2013 baselines.first = Some(baseline);
2014 }
2015 baselines.last = Some(baseline);
2016 }
2017 let mut row_relative_cell_rect = dimensions.get_cell_rect(
2018 TableSlotCoordinates::new(column_index, row_index),
2019 cell.rowspan,
2020 cell.colspan,
2021 );
2022 row_relative_cell_rect.start_corner -= row_fragment_layout.rect.start_corner;
2023 let mut fragment = cell.create_fragment(
2024 layout,
2025 row_relative_cell_rect,
2026 row_baseline,
2027 positioning_context,
2028 &self.table.style,
2029 &row_fragment_layout.containing_block,
2030 is_collapsed,
2031 );
2032
2033 let make_relative_to_row_start = |mut rect: LogicalRect<Au>| {
2042 rect.start_corner -= row_fragment_layout.rect.start_corner;
2043 let writing_mode = self.table.style.writing_mode;
2044 PhysicalRect::new(
2045 if writing_mode.is_horizontal() {
2046 PhysicalPoint::new(rect.start_corner.inline, rect.start_corner.block)
2047 } else {
2048 PhysicalPoint::new(rect.start_corner.block, rect.start_corner.inline)
2049 },
2050 rect.size.to_physical_size(writing_mode),
2051 )
2052 };
2053
2054 let column = self.table.columns.get(column_index);
2055 let column_group = column
2056 .and_then(|column| column.borrow().group_index)
2057 .and_then(|index| self.table.column_groups.get(index));
2058 if let Some(column_group) = column_group {
2059 let column_group = column_group.borrow();
2060 let rect = make_relative_to_row_start(dimensions.get_column_group_rect(&column_group));
2061 fragment.add_extra_background(ExtraBackground {
2062 style: column_group.shared_background_style.clone(),
2063 rect,
2064 })
2065 }
2066 if let Some(column) = column {
2067 let column = column.borrow();
2068 if !column.is_anonymous {
2069 let rect = make_relative_to_row_start(dimensions.get_column_rect(column_index));
2070 fragment.add_extra_background(ExtraBackground {
2071 style: column.shared_background_style.clone(),
2072 rect,
2073 })
2074 }
2075 }
2076 let row = self.table.rows.get(row_index);
2077 let row_group = row
2078 .and_then(|row| row.borrow().group_index)
2079 .and_then(|index| self.table.row_groups.get(index));
2080 if let Some(row_group) = row_group {
2081 let rect =
2082 make_relative_to_row_start(dimensions.get_row_group_rect(&row_group.borrow()));
2083 fragment.add_extra_background(ExtraBackground {
2084 style: row_group.borrow().shared_background_style.clone(),
2085 rect,
2086 })
2087 }
2088 if let Some(row) = row {
2089 let row = row.borrow();
2090 let rect = make_relative_to_row_start(row_fragment_layout.rect);
2091 fragment.add_extra_background(ExtraBackground {
2092 style: row.shared_background_style.clone(),
2093 rect,
2094 })
2095 }
2096
2097 let fragment = Fragment::Box(ArcRefCell::new(fragment));
2098 cell.context.base.set_fragment(fragment.clone());
2099 row_fragment_layout.fragments.push(fragment);
2100 }
2101
2102 fn make_fragments_for_columns_and_column_groups(
2103 &self,
2104 dimensions: &TableAndTrackDimensions,
2105 fragments: &mut Vec<Fragment>,
2106 ) {
2107 for column_group in self.table.column_groups.iter() {
2108 let column_group = column_group.borrow();
2109 if !column_group.is_empty() {
2110 let fragment = Fragment::Positioning(PositioningFragment::new_empty(
2111 column_group.base.base_fragment_info,
2112 dimensions
2113 .get_column_group_rect(&column_group)
2114 .as_physical(None),
2115 column_group.base.style.clone(),
2116 ));
2117 column_group.base.set_fragment(fragment.clone());
2118 fragments.push(fragment);
2119 }
2120 }
2121
2122 for (column_index, column) in self.table.columns.iter().enumerate() {
2123 let column = column.borrow();
2124 let fragment = Fragment::Positioning(PositioningFragment::new_empty(
2125 column.base.base_fragment_info,
2126 dimensions.get_column_rect(column_index).as_physical(None),
2127 column.base.style.clone(),
2128 ));
2129 column.base.set_fragment(fragment.clone());
2130 fragments.push(fragment);
2131 }
2132 }
2133
2134 fn compute_border_collapse(&mut self, writing_mode: WritingMode) {
2135 if self.table.style.get_inherited_table().border_collapse != BorderCollapse::Collapse {
2136 self.collapsed_borders = None;
2137 return;
2138 }
2139
2140 let mut collapsed_borders = LogicalVec2 {
2141 block: vec![
2142 vec![Default::default(); self.table.size.width];
2143 self.table.size.height + 1
2144 ],
2145 inline: vec![
2146 vec![Default::default(); self.table.size.height];
2147 self.table.size.width + 1
2148 ],
2149 };
2150
2151 let apply_border = |collapsed_borders: &mut CollapsedBorders,
2152 layout_style: &LayoutStyle,
2153 block: &Range<usize>,
2154 inline: &Range<usize>| {
2155 let border = CollapsedBorder::from_layout_style(layout_style, writing_mode);
2156 border
2157 .block_start
2158 .max_assign_to_slice(&mut collapsed_borders.block[block.start][inline.clone()]);
2159 border
2160 .block_end
2161 .max_assign_to_slice(&mut collapsed_borders.block[block.end][inline.clone()]);
2162 border
2163 .inline_start
2164 .max_assign_to_slice(&mut collapsed_borders.inline[inline.start][block.clone()]);
2165 border
2166 .inline_end
2167 .max_assign_to_slice(&mut collapsed_borders.inline[inline.end][block.clone()]);
2168 };
2169 let hide_inner_borders = |collapsed_borders: &mut CollapsedBorders,
2170 block: &Range<usize>,
2171 inline: &Range<usize>| {
2172 for x in inline.clone() {
2173 for y in block.clone() {
2174 if x != inline.start {
2175 collapsed_borders.inline[x][y].hide();
2176 }
2177 if y != block.start {
2178 collapsed_borders.block[y][x].hide();
2179 }
2180 }
2181 }
2182 };
2183 let all_rows = 0..self.table.size.height;
2184 let all_columns = 0..self.table.size.width;
2185 for row_index in all_rows.clone() {
2186 for column_index in all_columns.clone() {
2187 let cell = match self.table.slots[row_index][column_index] {
2188 TableSlot::Cell(ref cell) => cell,
2189 _ => continue,
2190 }
2191 .borrow();
2192 let block_range = row_index..row_index + cell.rowspan;
2193 let inline_range = column_index..column_index + cell.colspan;
2194 hide_inner_borders(&mut collapsed_borders, &block_range, &inline_range);
2195 apply_border(
2196 &mut collapsed_borders,
2197 &cell.context.layout_style(),
2198 &block_range,
2199 &inline_range,
2200 );
2201 }
2202 }
2203 for (row_index, row) in self.table.rows.iter().enumerate() {
2204 let row = row.borrow();
2205 apply_border(
2206 &mut collapsed_borders,
2207 &row.layout_style(),
2208 &(row_index..row_index + 1),
2209 &all_columns,
2210 );
2211 }
2212 for row_group in &self.table.row_groups {
2213 let row_group = row_group.borrow();
2214 apply_border(
2215 &mut collapsed_borders,
2216 &row_group.layout_style(),
2217 &row_group.track_range,
2218 &all_columns,
2219 );
2220 }
2221 for (column_index, column) in self.table.columns.iter().enumerate() {
2222 let column = column.borrow();
2223 apply_border(
2224 &mut collapsed_borders,
2225 &column.layout_style(),
2226 &all_rows,
2227 &(column_index..column_index + 1),
2228 );
2229 }
2230 for column_group in &self.table.column_groups {
2231 let column_group = column_group.borrow();
2232 apply_border(
2233 &mut collapsed_borders,
2234 &column_group.layout_style(),
2235 &all_rows,
2236 &column_group.track_range,
2237 );
2238 }
2239 apply_border(
2240 &mut collapsed_borders,
2241 &self.table.layout_style_for_grid(),
2242 &all_rows,
2243 &all_columns,
2244 );
2245
2246 self.collapsed_borders = Some(collapsed_borders);
2247 }
2248
2249 fn get_collapsed_border_widths_for_area(
2250 &self,
2251 area: LogicalSides<usize>,
2252 ) -> Option<LogicalSides<Au>> {
2253 let collapsed_borders = self.collapsed_borders.as_ref()?;
2254 let columns = || area.inline_start..area.inline_end;
2255 let rows = || area.block_start..area.block_end;
2256 let max_width = |slice: &[CollapsedBorder]| {
2257 let slice_widths = slice.iter().map(|collapsed_border| collapsed_border.width);
2258 slice_widths.max().unwrap_or_default()
2259 };
2260 Some(area.map_inline_and_block_axes(
2261 |column| max_width(&collapsed_borders.inline[*column][rows()]) / 2,
2262 |row| max_width(&collapsed_borders.block[*row][columns()]) / 2,
2263 ))
2264 }
2265}
2266
2267struct RowFragmentLayout<'a> {
2268 row: &'a TableTrack,
2269 rect: LogicalRect<Au>,
2270 containing_block: ContainingBlock<'a>,
2271 positioning_context: Option<PositioningContext>,
2272 fragments: Vec<Fragment>,
2273}
2274
2275impl<'a> RowFragmentLayout<'a> {
2276 fn new(
2277 table_row: &'a TableTrack,
2278 index: usize,
2279 dimensions: &TableAndTrackDimensions,
2280 table_style: &'a ComputedValues,
2281 ) -> Self {
2282 let rect = dimensions.get_row_rect(index);
2283 let containing_block = ContainingBlock {
2284 size: ContainingBlockSize {
2285 inline: rect.size.inline,
2286 block: SizeConstraint::Definite(rect.size.block),
2287 },
2288 style: table_style,
2289 };
2290 Self {
2291 row: table_row,
2292 rect,
2293 positioning_context: PositioningContext::new_for_layout_box_base(&table_row.base),
2294 containing_block,
2295 fragments: Vec::new(),
2296 }
2297 }
2298 fn finish(
2299 mut self,
2300 layout_context: &LayoutContext,
2301 table_positioning_context: &mut PositioningContext,
2302 containing_block_for_logical_conversion: &ContainingBlock,
2303 containing_block_for_children: &ContainingBlock,
2304 row_group_fragment_layout: &mut Option<RowGroupFragmentLayout>,
2305 ) -> Fragment {
2306 if self.positioning_context.is_some() {
2307 self.rect.start_corner +=
2308 relative_adjustement(&self.row.base.style, containing_block_for_children);
2309 }
2310
2311 let (inline_size, block_size) = if let Some(row_group_layout) = row_group_fragment_layout {
2312 self.rect.start_corner -= row_group_layout.rect.start_corner;
2313 (
2314 row_group_layout.rect.size.inline,
2315 SizeConstraint::Definite(row_group_layout.rect.size.block),
2316 )
2317 } else {
2318 (
2319 containing_block_for_logical_conversion.size.inline,
2320 containing_block_for_logical_conversion.size.block,
2321 )
2322 };
2323
2324 let row_group_containing_block = ContainingBlock {
2325 size: ContainingBlockSize {
2326 inline: inline_size,
2327 block: block_size,
2328 },
2329 style: containing_block_for_logical_conversion.style,
2330 };
2331
2332 let mut row_fragment = BoxFragment::new(
2333 self.row.base.base_fragment_info,
2334 self.row.base.style.clone(),
2335 self.fragments,
2336 self.rect.as_physical(Some(&row_group_containing_block)),
2337 PhysicalSides::zero(), PhysicalSides::zero(), PhysicalSides::zero(), None, );
2342 row_fragment.set_does_not_paint_background();
2343
2344 if let Some(mut row_positioning_context) = self.positioning_context.take() {
2345 row_positioning_context.layout_collected_children(layout_context, &mut row_fragment);
2346
2347 let parent_positioning_context = row_group_fragment_layout
2348 .as_mut()
2349 .and_then(|layout| layout.positioning_context.as_mut())
2350 .unwrap_or(table_positioning_context);
2351 parent_positioning_context.append(row_positioning_context);
2352 }
2353
2354 let fragment = Fragment::Box(ArcRefCell::new(row_fragment));
2355 self.row.base.set_fragment(fragment.clone());
2356 fragment
2357 }
2358}
2359
2360struct RowGroupFragmentLayout {
2361 row_group: ArcRefCell<TableTrackGroup>,
2362 rect: LogicalRect<Au>,
2363 positioning_context: Option<PositioningContext>,
2364 index: usize,
2365 fragments: Vec<Fragment>,
2366}
2367
2368impl RowGroupFragmentLayout {
2369 fn new(
2370 row_group: ArcRefCell<TableTrackGroup>,
2371 index: usize,
2372 dimensions: &TableAndTrackDimensions,
2373 ) -> Self {
2374 let (rect, positioning_context) = {
2375 let row_group = row_group.borrow();
2376 (
2377 dimensions.get_row_group_rect(&row_group),
2378 PositioningContext::new_for_layout_box_base(&row_group.base),
2379 )
2380 };
2381 Self {
2382 row_group,
2383 rect,
2384 positioning_context,
2385 index,
2386 fragments: Vec::new(),
2387 }
2388 }
2389
2390 fn finish(
2391 mut self,
2392 layout_context: &LayoutContext,
2393 table_positioning_context: &mut PositioningContext,
2394 containing_block_for_logical_conversion: &ContainingBlock,
2395 containing_block_for_children: &ContainingBlock,
2396 ) -> Fragment {
2397 let row_group = self.row_group.borrow();
2398 if self.positioning_context.is_some() {
2399 self.rect.start_corner +=
2400 relative_adjustement(&row_group.base.style, containing_block_for_children);
2401 }
2402
2403 let mut row_group_fragment = BoxFragment::new(
2404 row_group.base.base_fragment_info,
2405 row_group.base.style.clone(),
2406 self.fragments,
2407 self.rect
2408 .as_physical(Some(containing_block_for_logical_conversion)),
2409 PhysicalSides::zero(), PhysicalSides::zero(), PhysicalSides::zero(), None, );
2414 row_group_fragment.set_does_not_paint_background();
2415
2416 if let Some(mut row_positioning_context) = self.positioning_context.take() {
2417 row_positioning_context
2418 .layout_collected_children(layout_context, &mut row_group_fragment);
2419 table_positioning_context.append(row_positioning_context);
2420 }
2421
2422 let fragment = Fragment::Box(ArcRefCell::new(row_group_fragment));
2423 row_group.base.set_fragment(fragment.clone());
2424 fragment
2425 }
2426}
2427
2428struct TableAndTrackDimensions {
2429 table_rect: LogicalRect<Au>,
2431 table_cells_rect: LogicalRect<Au>,
2434 row_dimensions: Vec<(Au, Au)>,
2436 column_dimensions: Vec<(Au, Au)>,
2438}
2439
2440impl TableAndTrackDimensions {
2441 fn new(table_layout: &TableLayout) -> Self {
2442 let border_spacing = table_layout.table.border_spacing();
2443
2444 let fallback_inline_size = table_layout.assignable_width;
2446 let fallback_block_size = table_layout.final_table_height;
2447
2448 let mut column_dimensions = Vec::new();
2449 let mut column_offset = Au::zero();
2450 for column_index in 0..table_layout.table.size.width {
2451 if table_layout.is_column_collapsed(column_index) {
2452 column_dimensions.push((column_offset, column_offset));
2453 continue;
2454 }
2455 let start_offset = column_offset + border_spacing.inline;
2456 let end_offset = start_offset + table_layout.distributed_column_widths[column_index];
2457 column_dimensions.push((start_offset, end_offset));
2458 column_offset = end_offset;
2459 }
2460 column_offset += if table_layout.table.size.width == 0 {
2461 fallback_inline_size
2462 } else {
2463 border_spacing.inline
2464 };
2465
2466 let mut row_dimensions = Vec::new();
2467 let mut row_offset = Au::zero();
2468 for row_index in 0..table_layout.table.size.height {
2469 if table_layout.is_row_collapsed(row_index) {
2470 row_dimensions.push((row_offset, row_offset));
2471 continue;
2472 }
2473 let start_offset = row_offset + border_spacing.block;
2474 let end_offset = start_offset + table_layout.row_sizes[row_index];
2475 row_dimensions.push((start_offset, end_offset));
2476 row_offset = end_offset;
2477 }
2478 row_offset += if table_layout.table.size.height == 0 {
2479 fallback_block_size
2480 } else {
2481 border_spacing.block
2482 };
2483
2484 let table_start_corner = LogicalVec2 {
2485 inline: column_dimensions.first().map_or_else(Au::zero, |v| v.0),
2486 block: row_dimensions.first().map_or_else(Au::zero, |v| v.0),
2487 };
2488 let table_size = LogicalVec2 {
2489 inline: column_dimensions
2490 .last()
2491 .map_or(fallback_inline_size, |v| v.1),
2492 block: row_dimensions.last().map_or(fallback_block_size, |v| v.1),
2493 } - table_start_corner;
2494 let table_cells_rect = LogicalRect {
2495 start_corner: table_start_corner,
2496 size: table_size,
2497 };
2498
2499 let table_rect = LogicalRect {
2500 start_corner: LogicalVec2::zero(),
2501 size: LogicalVec2 {
2502 inline: column_offset,
2503 block: row_offset,
2504 },
2505 };
2506
2507 Self {
2508 table_rect,
2509 table_cells_rect,
2510 row_dimensions,
2511 column_dimensions,
2512 }
2513 }
2514
2515 fn get_row_rect(&self, row_index: usize) -> LogicalRect<Au> {
2516 let mut row_rect = self.table_cells_rect;
2517 let row_dimensions = self.row_dimensions[row_index];
2518 row_rect.start_corner.block = row_dimensions.0;
2519 row_rect.size.block = row_dimensions.1 - row_dimensions.0;
2520 row_rect
2521 }
2522
2523 fn get_column_rect(&self, column_index: usize) -> LogicalRect<Au> {
2524 let mut row_rect = self.table_cells_rect;
2525 let column_dimensions = self.column_dimensions[column_index];
2526 row_rect.start_corner.inline = column_dimensions.0;
2527 row_rect.size.inline = column_dimensions.1 - column_dimensions.0;
2528 row_rect
2529 }
2530
2531 fn get_row_group_rect(&self, row_group: &TableTrackGroup) -> LogicalRect<Au> {
2532 if row_group.is_empty() {
2533 return LogicalRect::zero();
2534 }
2535
2536 let mut row_group_rect = self.table_cells_rect;
2537 let block_start = self.row_dimensions[row_group.track_range.start].0;
2538 let block_end = self.row_dimensions[row_group.track_range.end - 1].1;
2539 row_group_rect.start_corner.block = block_start;
2540 row_group_rect.size.block = block_end - block_start;
2541 row_group_rect
2542 }
2543
2544 fn get_column_group_rect(&self, column_group: &TableTrackGroup) -> LogicalRect<Au> {
2545 if column_group.is_empty() {
2546 return LogicalRect::zero();
2547 }
2548
2549 let mut column_group_rect = self.table_cells_rect;
2550 let inline_start = self.column_dimensions[column_group.track_range.start].0;
2551 let inline_end = self.column_dimensions[column_group.track_range.end - 1].1;
2552 column_group_rect.start_corner.inline = inline_start;
2553 column_group_rect.size.inline = inline_end - inline_start;
2554 column_group_rect
2555 }
2556
2557 fn get_cell_rect(
2558 &self,
2559 coordinates: TableSlotCoordinates,
2560 rowspan: usize,
2561 colspan: usize,
2562 ) -> LogicalRect<Au> {
2563 let start_corner = LogicalVec2 {
2564 inline: self.column_dimensions[coordinates.x].0,
2565 block: self.row_dimensions[coordinates.y].0,
2566 };
2567 let size = LogicalVec2 {
2568 inline: self.column_dimensions[coordinates.x + colspan - 1].1,
2569 block: self.row_dimensions[coordinates.y + rowspan - 1].1,
2570 } - start_corner;
2571 LogicalRect { start_corner, size }
2572 }
2573}
2574
2575impl Table {
2576 fn border_spacing(&self) -> LogicalVec2<Au> {
2577 if self.style.clone_border_collapse() == BorderCollapse::Collapse {
2578 LogicalVec2::zero()
2579 } else {
2580 let border_spacing = self.style.clone_border_spacing();
2581 LogicalVec2 {
2582 inline: border_spacing.horizontal(),
2583 block: border_spacing.vertical(),
2584 }
2585 }
2586 }
2587
2588 fn total_border_spacing(&self) -> LogicalVec2<Au> {
2589 let border_spacing = self.border_spacing();
2590 LogicalVec2 {
2591 inline: if self.size.width > 0 {
2592 border_spacing.inline * (self.size.width as i32 + 1)
2593 } else {
2594 Au::zero()
2595 },
2596 block: if self.size.height > 0 {
2597 border_spacing.block * (self.size.height as i32 + 1)
2598 } else {
2599 Au::zero()
2600 },
2601 }
2602 }
2603
2604 fn get_column_measure_for_column_at_index(
2605 &self,
2606 writing_mode: WritingMode,
2607 column_index: usize,
2608 is_in_fixed_mode: bool,
2609 ) -> CellOrTrackMeasure {
2610 let column = match self.columns.get(column_index) {
2611 Some(column) => column,
2612 None => return CellOrTrackMeasure::zero(),
2613 }
2614 .borrow();
2615
2616 let CellOrColumnOuterSizes {
2617 preferred: preferred_size,
2618 min: min_size,
2619 max: max_size,
2620 percentage: percentage_size,
2621 } = CellOrColumnOuterSizes::new(
2622 &column.base.style,
2623 writing_mode,
2624 &Default::default(),
2625 is_in_fixed_mode,
2626 );
2627
2628 CellOrTrackMeasure {
2629 content_sizes: ContentSizes {
2630 min_content: min_size.inline,
2635 max_content: preferred_size
2639 .inline
2640 .clamp_between_extremums(min_size.inline, max_size.inline),
2641 },
2642 percentage: percentage_size.inline,
2643 }
2644 }
2645
2646 fn get_row_measure_for_row_at_index(
2647 &self,
2648 writing_mode: WritingMode,
2649 row_index: usize,
2650 ) -> CellOrTrackMeasure {
2651 let row = match self.rows.get(row_index) {
2652 Some(row) => row,
2653 None => return CellOrTrackMeasure::zero(),
2654 };
2655
2656 let row = row.borrow();
2660 let size = row.base.style.box_size(writing_mode);
2661 let max_size = row.base.style.max_box_size(writing_mode);
2662 let percentage_contribution = get_size_percentage_contribution(&size, &max_size);
2663
2664 CellOrTrackMeasure {
2665 content_sizes: size
2666 .block
2667 .to_numeric()
2668 .and_then(|size| size.to_length())
2669 .map_or_else(Au::zero, Au::from)
2670 .into(),
2671 percentage: percentage_contribution.block,
2672 }
2673 }
2674
2675 pub(crate) fn layout(
2676 &self,
2677 layout_context: &LayoutContext,
2678 positioning_context: &mut PositioningContext,
2679 containing_block_for_children: &ContainingBlock,
2680 containing_block_for_table: &ContainingBlock,
2681 ) -> CacheableLayoutResult {
2682 TableLayout::new(self).layout(
2683 layout_context,
2684 positioning_context,
2685 containing_block_for_children,
2686 containing_block_for_table,
2687 )
2688 }
2689
2690 pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
2691 for caption in &self.captions {
2692 caption
2693 .borrow_mut()
2694 .context
2695 .base
2696 .parent_box
2697 .replace(layout_box.clone());
2698 }
2699 for row_group in &self.row_groups {
2700 row_group
2701 .borrow_mut()
2702 .base
2703 .parent_box
2704 .replace(layout_box.clone());
2705 }
2706 for column_group in &self.column_groups {
2707 column_group
2708 .borrow_mut()
2709 .base
2710 .parent_box
2711 .replace(layout_box.clone());
2712 }
2713 for row in &self.rows {
2714 let row = &mut *row.borrow_mut();
2715 if let Some(group_index) = row.group_index {
2716 row.base.parent_box.replace(WeakLayoutBox::TableLevelBox(
2717 WeakTableLevelBox::TrackGroup(self.row_groups[group_index].downgrade()),
2718 ));
2719 } else {
2720 row.base.parent_box.replace(layout_box.clone());
2721 }
2722 }
2723 for column in &self.columns {
2724 let column = &mut *column.borrow_mut();
2725 if let Some(group_index) = column.group_index {
2726 column.base.parent_box.replace(WeakLayoutBox::TableLevelBox(
2727 WeakTableLevelBox::TrackGroup(self.column_groups[group_index].downgrade()),
2728 ));
2729 } else {
2730 column.base.parent_box.replace(layout_box.clone());
2731 }
2732 }
2733 for row_index in 0..self.size.height {
2734 let row = WeakLayoutBox::TableLevelBox(WeakTableLevelBox::Track(
2735 self.rows[row_index].downgrade(),
2736 ));
2737 for column_index in 0..self.size.width {
2738 if let TableSlot::Cell(ref cell) = self.slots[row_index][column_index] {
2739 cell.borrow_mut()
2740 .context
2741 .base
2742 .parent_box
2743 .replace(row.clone());
2744 }
2745 }
2746 }
2747 }
2748}
2749
2750impl ComputeInlineContentSizes for Table {
2751 #[servo_tracing::instrument(name = "Table::compute_inline_content_sizes", skip_all)]
2752 fn compute_inline_content_sizes(
2753 &self,
2754 layout_context: &LayoutContext,
2755 constraint_space: &ConstraintSpace,
2756 ) -> InlineContentSizesResult {
2757 let writing_mode = constraint_space.style.writing_mode;
2758 let mut layout = TableLayout::new(self);
2759 layout.compute_border_collapse(writing_mode);
2760 layout.pbm = self
2761 .layout_style(Some(&layout))
2762 .padding_border_margin_with_writing_mode_and_containing_block_inline_size(
2763 writing_mode,
2764 Au::zero(),
2765 );
2766 layout.compute_measures(layout_context, writing_mode);
2767
2768 let grid_content_sizes = layout.compute_grid_min_max();
2769
2770 let caption_content_sizes = ContentSizes::from(
2774 layout.compute_caption_minimum_inline_size(layout_context) -
2775 layout.pbm.padding_border_sums.inline,
2776 );
2777
2778 InlineContentSizesResult {
2779 sizes: grid_content_sizes.max(caption_content_sizes),
2780 depends_on_block_constraints: false,
2781 }
2782 }
2783}
2784
2785impl Table {
2786 #[inline]
2787 pub(crate) fn layout_style<'a>(
2788 &'a self,
2789 layout: Option<&'a TableLayout<'a>>,
2790 ) -> LayoutStyle<'a> {
2791 LayoutStyle::Table(TableLayoutStyle {
2792 table: self,
2793 layout,
2794 })
2795 }
2796
2797 #[inline]
2798 pub(crate) fn layout_style_for_grid(&self) -> LayoutStyle<'_> {
2799 LayoutStyle::Default(&self.grid_style)
2800 }
2801}
2802
2803impl TableTrack {
2804 #[inline]
2805 pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
2806 LayoutStyle::Default(&self.base.style)
2807 }
2808}
2809
2810impl TableTrackGroup {
2811 #[inline]
2812 pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
2813 LayoutStyle::Default(&self.base.style)
2814 }
2815}
2816
2817impl TableLayoutStyle<'_> {
2818 #[inline]
2819 pub(crate) fn style(&self) -> &ComputedValues {
2820 &self.table.style
2821 }
2822
2823 #[inline]
2824 pub(crate) fn collapses_borders(&self) -> bool {
2825 self.style().get_inherited_table().border_collapse == BorderCollapse::Collapse
2826 }
2827
2828 pub(crate) fn halved_collapsed_border_widths(&self) -> LogicalSides<Au> {
2829 debug_assert!(self.collapses_borders());
2830 let area = LogicalSides {
2831 inline_start: 0,
2832 inline_end: self.table.size.width,
2833 block_start: 0,
2834 block_end: self.table.size.height,
2835 };
2836 if let Some(layout) = self.layout {
2837 layout.get_collapsed_border_widths_for_area(area)
2838 } else {
2839 let mut layout = TableLayout::new(self.table);
2841 layout.compute_border_collapse(self.style().writing_mode);
2842 layout.get_collapsed_border_widths_for_area(area)
2843 }
2844 .expect("Collapsed borders should be computed")
2845 }
2846}
2847
2848impl TableSlotCell {
2849 fn content_alignment(&self) -> CellContentAlignment {
2850 let style_box = self.context.base.style.get_box();
2854 match style_box.baseline_shift {
2855 BaselineShift::Keyword(BaselineShiftKeyword::Top) => CellContentAlignment::Top,
2856 BaselineShift::Keyword(BaselineShiftKeyword::Bottom) => CellContentAlignment::Bottom,
2857 _ => match style_box.alignment_baseline {
2858 AlignmentBaseline::Middle => CellContentAlignment::Middle,
2859 _ => CellContentAlignment::Baseline,
2860 },
2861 }
2862 }
2863
2864 #[allow(clippy::too_many_arguments)]
2865 fn create_fragment(
2866 &self,
2867 mut layout: CellLayout,
2868 cell_rect: LogicalRect<Au>,
2869 cell_baseline: Au,
2870 positioning_context: &mut PositioningContext,
2871 table_style: &ComputedValues,
2872 containing_block: &ContainingBlock,
2873 is_collapsed: bool,
2874 ) -> BoxFragment {
2875 use style::Zero as StyleZero;
2877
2878 let cell_content_rect = cell_rect.deflate(&(layout.padding + layout.border));
2879 let content_block_size = layout.layout.content_block_size;
2880 let free_space = || Au::zero().max(cell_content_rect.size.block - content_block_size);
2881 let vertical_align_offset = match self.content_alignment() {
2882 CellContentAlignment::Top => Au::zero(),
2883 CellContentAlignment::Bottom => free_space(),
2884 CellContentAlignment::Middle => free_space().scale_by(0.5),
2885 CellContentAlignment::Baseline => {
2886 cell_baseline -
2887 (layout.padding.block_start + layout.border.block_start) -
2888 layout.ascent()
2889 },
2890 };
2891
2892 let mut base_fragment_info = self.context.base.base_fragment_info;
2893 if self.context.base.style.get_inherited_table().empty_cells == EmptyCells::Hide &&
2894 table_style.get_inherited_table().border_collapse != BorderCollapse::Collapse &&
2895 layout.is_empty_for_empty_cells()
2896 {
2897 base_fragment_info.flags.insert(FragmentFlags::DO_NOT_PAINT);
2898 }
2899
2900 if is_collapsed {
2901 base_fragment_info.flags.insert(FragmentFlags::IS_COLLAPSED);
2902 }
2903
2904 let mut vertical_align_fragment_rect = cell_content_rect;
2906 vertical_align_fragment_rect.start_corner = LogicalVec2 {
2907 inline: Au::zero(),
2908 block: vertical_align_offset,
2909 };
2910 let vertical_align_fragment = PositioningFragment::new_anonymous(
2911 self.context.base.style.clone(),
2912 vertical_align_fragment_rect.as_physical(None),
2913 layout.layout.fragments,
2914 );
2915
2916 let physical_cell_rect = cell_content_rect.as_physical(Some(containing_block));
2924 layout
2925 .positioning_context
2926 .adjust_static_position_of_hoisted_fragments_with_offset(
2927 &physical_cell_rect.origin.to_vector(),
2928 PositioningContextLength::zero(),
2929 );
2930 positioning_context.append(layout.positioning_context);
2931
2932 let specific_layout_info = (table_style.get_inherited_table().border_collapse ==
2933 BorderCollapse::Collapse)
2934 .then_some(SpecificLayoutInfo::TableCellWithCollapsedBorders);
2935
2936 BoxFragment::new(
2937 base_fragment_info,
2938 self.context.base.style.clone(),
2939 vec![Fragment::Positioning(vertical_align_fragment)],
2940 physical_cell_rect,
2941 layout.padding.to_physical(table_style.writing_mode),
2942 layout.border.to_physical(table_style.writing_mode),
2943 PhysicalSides::zero(), specific_layout_info,
2945 )
2946 .with_baselines(layout.layout.baselines)
2947 }
2948}
2949
2950fn get_size_percentage_contribution(
2951 size: &LogicalVec2<Size<ComputedLengthPercentage>>,
2952 max_size: &LogicalVec2<Size<ComputedLengthPercentage>>,
2953) -> LogicalVec2<Option<Percentage>> {
2954 LogicalVec2 {
2962 inline: max_two_optional_percentages(
2963 size.inline.to_percentage(),
2964 max_size.inline.to_percentage(),
2965 ),
2966 block: max_two_optional_percentages(
2967 size.block.to_percentage(),
2968 max_size.block.to_percentage(),
2969 ),
2970 }
2971}
2972
2973struct CellOrColumnOuterSizes {
2974 min: LogicalVec2<Au>,
2975 preferred: LogicalVec2<Au>,
2976 max: LogicalVec2<Option<Au>>,
2977 percentage: LogicalVec2<Option<Percentage>>,
2978}
2979
2980impl CellOrColumnOuterSizes {
2981 fn new(
2982 style: &Arc<ComputedValues>,
2983 writing_mode: WritingMode,
2984 padding_border_sums: &LogicalVec2<Au>,
2985 is_in_fixed_mode: bool,
2986 ) -> Self {
2987 let box_sizing = style.get_position().box_sizing;
2988 let outer_size = |size: LogicalVec2<Au>| match box_sizing {
2989 BoxSizing::ContentBox => size + *padding_border_sums,
2990 BoxSizing::BorderBox => LogicalVec2 {
2991 inline: size.inline.max(padding_border_sums.inline),
2992 block: size.block.max(padding_border_sums.block),
2993 },
2994 };
2995
2996 let outer_option_size = |size: LogicalVec2<Option<Au>>| match box_sizing {
2997 BoxSizing::ContentBox => size.map_inline_and_block_axes(
2998 |inline| inline.map(|inline| inline + padding_border_sums.inline),
2999 |block| block.map(|block| block + padding_border_sums.block),
3000 ),
3001 BoxSizing::BorderBox => size.map_inline_and_block_axes(
3002 |inline| inline.map(|inline| inline.max(padding_border_sums.inline)),
3003 |block| block.map(|block| block.max(padding_border_sums.block)),
3004 ),
3005 };
3006
3007 let get_size_for_axis = |size: &Size<ComputedLengthPercentage>| {
3008 size.to_numeric()
3011 .and_then(|length_percentage| length_percentage.to_length())
3012 .map(Au::from)
3013 };
3014
3015 let size = style.box_size(writing_mode);
3016 if is_in_fixed_mode {
3017 return Self {
3018 percentage: size.map(|v| v.to_percentage()),
3019 preferred: outer_option_size(size.map(get_size_for_axis))
3020 .map(|v| v.unwrap_or_default()),
3021 min: LogicalVec2::default(),
3022 max: LogicalVec2::default(),
3023 };
3024 }
3025
3026 let min_size = style.min_box_size(writing_mode);
3027 let max_size = style.max_box_size(writing_mode);
3028
3029 Self {
3030 min: outer_size(min_size.map(|v| get_size_for_axis(v).unwrap_or_default())),
3031 preferred: outer_size(size.map(|v| get_size_for_axis(v).unwrap_or_default())),
3032 max: outer_option_size(max_size.map(get_size_for_axis)),
3033 percentage: get_size_percentage_contribution(&size, &max_size),
3034 }
3035 }
3036}
3037
3038struct RowspanToDistribute<'a> {
3039 coordinates: TableSlotCoordinates,
3040 cell: AtomicRef<'a, TableSlotCell>,
3041 measure: &'a CellOrTrackMeasure,
3042}
3043
3044impl RowspanToDistribute<'_> {
3045 fn range(&self) -> Range<usize> {
3046 self.coordinates.y..self.coordinates.y + self.cell.rowspan
3047 }
3048
3049 fn fully_encloses(&self, other: &RowspanToDistribute) -> bool {
3050 other.coordinates.y > self.coordinates.y && other.range().end < self.range().end
3051 }
3052}
3053
3054#[derive(Debug)]
3057struct ColspanToDistribute {
3058 starting_column: usize,
3059 span: usize,
3060 content_sizes: ContentSizes,
3061 percentage: Option<Percentage>,
3062}
3063
3064impl ColspanToDistribute {
3065 fn comparison_for_sort(a: &Self, b: &Self) -> Ordering {
3069 a.span
3070 .cmp(&b.span)
3071 .then_with(|| a.starting_column.cmp(&b.starting_column))
3072 }
3073
3074 fn range(&self) -> Range<usize> {
3075 self.starting_column..self.starting_column + self.span
3076 }
3077}
3078
3079#[cfg(test)]
3080mod test {
3081 use app_units::MIN_AU;
3082
3083 use super::*;
3084 use crate::sizing::ContentSizes;
3085
3086 #[test]
3087 fn test_colspan_to_distribute_first_sort_by_span() {
3088 let a = ColspanToDistribute {
3089 starting_column: 0,
3090 span: 0,
3091 content_sizes: ContentSizes {
3092 min_content: MIN_AU,
3093 max_content: MIN_AU,
3094 },
3095 percentage: None,
3096 };
3097
3098 let b = ColspanToDistribute {
3099 starting_column: 0,
3100 span: 1,
3101 content_sizes: ContentSizes {
3102 min_content: MIN_AU,
3103 max_content: MIN_AU,
3104 },
3105 percentage: None,
3106 };
3107
3108 let ordering = ColspanToDistribute::comparison_for_sort(&a, &b);
3109 assert_eq!(ordering, Ordering::Less);
3110 }
3111
3112 #[test]
3113 fn test_colspan_to_distribute_if_spans_are_equal_sort_by_starting_column() {
3114 let a = ColspanToDistribute {
3115 starting_column: 0,
3116 span: 0,
3117 content_sizes: ContentSizes {
3118 min_content: MIN_AU,
3119 max_content: MIN_AU,
3120 },
3121 percentage: None,
3122 };
3123
3124 let b = ColspanToDistribute {
3125 starting_column: 1,
3126 span: 0,
3127 content_sizes: ContentSizes {
3128 min_content: MIN_AU,
3129 max_content: MIN_AU,
3130 },
3131 percentage: None,
3132 };
3133
3134 let ordering = ColspanToDistribute::comparison_for_sort(&a, &b);
3135 assert_eq!(ordering, Ordering::Less);
3136 }
3137}