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 BorderStyle, LengthPercentage as ComputedLengthPercentage, Percentage,
26};
27use style::values::generics::box_::{GenericVerticalAlign as VerticalAlign, VerticalAlignKeyword};
28
29use super::{
30 ArcRefCell, CollapsedBorder, CollapsedBorderLine, SpecificTableGridInfo, Table, TableCaption,
31 TableLayoutStyle, TableSlot, TableSlotCell, TableSlotCoordinates, TableTrack, TableTrackGroup,
32};
33use crate::context::LayoutContext;
34use crate::dom::WeakLayoutBox;
35use crate::formatting_contexts::Baselines;
36use crate::fragment_tree::{
37 BoxFragment, CollapsedBlockMargins, ExtraBackground, Fragment, FragmentFlags,
38 PositioningFragment, SpecificLayoutInfo,
39};
40use crate::geom::{
41 LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, PhysicalRect,
42 PhysicalSides, PhysicalVec, ToLogical, ToLogicalWithContainingBlock,
43};
44use crate::layout_box_base::CacheableLayoutResult;
45use crate::positioned::{PositioningContext, PositioningContextLength, relative_adjustement};
46use crate::sizing::{
47 ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, Size, SizeConstraint,
48};
49use crate::style_ext::{
50 BorderStyleColor, Clamp, ComputedValuesExt, LayoutStyle, PaddingBorderMargin,
51};
52use crate::table::WeakTableLevelBox;
53use crate::{
54 ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock, WritingMode,
55};
56
57struct CellLayout {
61 layout: CacheableLayoutResult,
62 padding: LogicalSides<Au>,
63 border: LogicalSides<Au>,
64 positioning_context: PositioningContext,
65}
66
67impl CellLayout {
68 fn ascent(&self) -> Au {
69 self.layout
70 .baselines
71 .first
72 .unwrap_or(self.layout.content_block_size)
73 }
74
75 fn outer_block_size(&self) -> Au {
77 self.layout.content_block_size + self.border.block_sum() + self.padding.block_sum()
78 }
79
80 fn is_empty(&self) -> bool {
83 self.layout.fragments.is_empty()
84 }
85
86 fn is_empty_for_empty_cells(&self) -> bool {
88 self.layout
89 .fragments
90 .iter()
91 .all(|fragment| matches!(fragment, Fragment::AbsoluteOrFixedPositioned(_)))
92 }
93}
94
95#[derive(Clone, Debug, Default)]
97struct RowLayout {
98 constrained: bool,
99 has_cell_with_span_greater_than_one: bool,
100 percent: Percentage,
101}
102
103#[derive(Clone, Debug, Default)]
105struct ColumnLayout {
106 constrained: bool,
107 has_originating_cells: bool,
108 content_sizes: ContentSizes,
109 percentage: Option<Percentage>,
110}
111
112fn max_two_optional_percentages(
113 a: Option<Percentage>,
114 b: Option<Percentage>,
115) -> Option<Percentage> {
116 match (a, b) {
117 (Some(a), Some(b)) => Some(Percentage(a.0.max(b.0))),
118 _ => a.or(b),
119 }
120}
121
122impl ColumnLayout {
123 fn incorporate_cell_measure(&mut self, cell_measure: &CellOrTrackMeasure) {
124 self.content_sizes.max_assign(cell_measure.content_sizes);
125 self.percentage = max_two_optional_percentages(self.percentage, cell_measure.percentage);
126 }
127}
128
129impl CollapsedBorder {
130 fn new(style_color: BorderStyleColor, width: Au) -> Self {
131 Self { style_color, width }
132 }
133
134 fn from_layout_style(
135 layout_style: &LayoutStyle,
136 writing_mode: WritingMode,
137 ) -> LogicalSides<Self> {
138 let border_style_color = layout_style.style().border_style_color(writing_mode);
139 let border_width = layout_style.border_width(writing_mode);
140 LogicalSides {
141 inline_start: Self::new(border_style_color.inline_start, border_width.inline_start),
142 inline_end: Self::new(border_style_color.inline_end, border_width.inline_end),
143 block_start: Self::new(border_style_color.block_start, border_width.block_start),
144 block_end: Self::new(border_style_color.block_end, border_width.block_end),
145 }
146 }
147
148 fn max_assign(&mut self, other: &Self) {
149 if *self < *other {
150 *self = other.clone();
151 }
152 }
153
154 fn max_assign_to_slice(&self, slice: &mut [CollapsedBorder]) {
155 for collapsed_border in slice {
156 collapsed_border.max_assign(self)
157 }
158 }
159
160 fn hide(&mut self) {
161 self.style_color = BorderStyleColor::hidden();
162 self.width = Au::zero();
163 }
164}
165
166impl PartialOrd for CollapsedBorder {
173 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
174 let is_hidden = |border: &Self| border.style_color.style == BorderStyle::Hidden;
175 let style_specificity = |border: &Self| match border.style_color.style {
176 BorderStyle::None => 0,
177 BorderStyle::Inset => 1,
178 BorderStyle::Groove => 2,
179 BorderStyle::Outset => 3,
180 BorderStyle::Ridge => 4,
181 BorderStyle::Dotted => 5,
182 BorderStyle::Dashed => 6,
183 BorderStyle::Solid => 7,
184 BorderStyle::Double => 8,
185 BorderStyle::Hidden => 9,
186 };
187 let candidate = (is_hidden(self).cmp(&is_hidden(other)))
188 .then_with(|| self.width.cmp(&other.width))
189 .then_with(|| style_specificity(self).cmp(&style_specificity(other)));
190 if !candidate.is_eq() || self.style_color.color == other.style_color.color {
191 Some(candidate)
192 } else {
193 None
194 }
195 }
196}
197
198impl Eq for CollapsedBorder {}
199
200type CollapsedBorders = LogicalVec2<Vec<CollapsedBorderLine>>;
201
202pub(crate) struct TableLayout<'a> {
206 table: &'a Table,
207 pbm: PaddingBorderMargin,
208 rows: Vec<RowLayout>,
209 columns: Vec<ColumnLayout>,
210 cell_measures: Vec<Vec<LogicalVec2<CellOrTrackMeasure>>>,
211 table_width: Au,
214 assignable_width: Au,
217 final_table_height: Au,
218 distributed_column_widths: Vec<Au>,
219 row_sizes: Vec<Au>,
220 row_baselines: Vec<Au>,
222 cells_laid_out: Vec<Vec<Option<CellLayout>>>,
223 basis_for_cell_padding_percentage: Au,
224 collapsed_borders: Option<CollapsedBorders>,
226 is_in_fixed_mode: bool,
227}
228
229#[derive(Clone, Debug)]
230struct CellOrTrackMeasure {
231 content_sizes: ContentSizes,
232 percentage: Option<Percentage>,
233}
234
235impl Zero for CellOrTrackMeasure {
236 fn zero() -> Self {
237 Self {
238 content_sizes: ContentSizes::zero(),
239 percentage: None,
240 }
241 }
242
243 fn is_zero(&self) -> bool {
244 self.content_sizes.is_zero() && self.percentage.is_none()
245 }
246}
247
248impl<'a> TableLayout<'a> {
249 fn new(table: &'a Table) -> TableLayout<'a> {
250 let style = &table.style;
253 let is_in_fixed_mode = style.get_table().table_layout == TableLayoutMode::Fixed &&
254 !matches!(
255 style.box_size(style.writing_mode).inline,
256 Size::Initial | Size::MaxContent
257 );
258 Self {
259 table,
260 pbm: PaddingBorderMargin::zero(),
261 rows: Vec::new(),
262 columns: Vec::new(),
263 cell_measures: Vec::new(),
264 table_width: Au::zero(),
265 assignable_width: Au::zero(),
266 final_table_height: Au::zero(),
267 distributed_column_widths: Vec::new(),
268 row_sizes: Vec::new(),
269 row_baselines: Vec::new(),
270 cells_laid_out: Vec::new(),
271 basis_for_cell_padding_percentage: Au::zero(),
272 collapsed_borders: None,
273 is_in_fixed_mode,
274 }
275 }
276
277 pub(crate) fn compute_cell_measures(
280 &mut self,
281 layout_context: &LayoutContext,
282 writing_mode: WritingMode,
283 ) {
284 let row_measures = vec![LogicalVec2::zero(); self.table.size.width];
285 self.cell_measures = vec![row_measures; self.table.size.height];
286
287 for row_index in 0..self.table.size.height {
288 for column_index in 0..self.table.size.width {
289 let cell = match self.table.slots[row_index][column_index] {
290 TableSlot::Cell(ref cell) => cell,
291 _ => continue,
292 }
293 .borrow();
294
295 let layout_style = cell.layout_style();
296 let padding = layout_style
297 .padding(writing_mode)
298 .percentages_relative_to(Au::zero());
299 let border = self
300 .get_collapsed_border_widths_for_area(LogicalSides {
301 inline_start: column_index,
302 inline_end: column_index + cell.colspan,
303 block_start: row_index,
304 block_end: row_index + cell.rowspan,
305 })
306 .unwrap_or_else(|| layout_style.border_width(writing_mode));
307
308 let padding_border_sums = LogicalVec2 {
309 inline: padding.inline_sum() + border.inline_sum(),
310 block: padding.block_sum() + border.block_sum(),
311 };
312
313 let CellOrColumnOuterSizes {
314 preferred: preferred_size,
315 min: min_size,
316 max: max_size,
317 percentage: percentage_size,
318 } = CellOrColumnOuterSizes::new(
319 &cell.base.style,
320 writing_mode,
321 &padding_border_sums,
322 self.is_in_fixed_mode,
323 );
324
325 let inline_measure = if self.is_in_fixed_mode {
330 if row_index > 0 {
331 CellOrTrackMeasure::zero()
332 } else {
333 CellOrTrackMeasure {
334 content_sizes: preferred_size.inline.into(),
335 percentage: percentage_size.inline,
336 }
337 }
338 } else {
339 let inline_content_sizes = cell.inline_content_sizes(layout_context) +
340 padding_border_sums.inline.into();
341 assert!(
342 inline_content_sizes.max_content >= inline_content_sizes.min_content,
343 "the max-content size should never be smaller than the min-content size"
344 );
345
346 let outer_min_content_width = inline_content_sizes
348 .min_content
349 .clamp_between_extremums(min_size.inline, max_size.inline);
350 let outer_max_content_width = if self.columns[column_index].constrained {
351 inline_content_sizes
352 .min_content
353 .max(preferred_size.inline)
354 .clamp_between_extremums(min_size.inline, max_size.inline)
355 } else {
356 inline_content_sizes
357 .max_content
358 .max(preferred_size.inline)
359 .clamp_between_extremums(min_size.inline, max_size.inline)
360 };
361 assert!(outer_min_content_width <= outer_max_content_width);
362
363 CellOrTrackMeasure {
364 content_sizes: ContentSizes {
365 min_content: outer_min_content_width,
366 max_content: outer_max_content_width,
367 },
368 percentage: percentage_size.inline,
369 }
370 };
371
372 let block_measure = CellOrTrackMeasure {
376 content_sizes: preferred_size.block.into(),
377 percentage: percentage_size.block,
378 };
379
380 self.cell_measures[row_index][column_index] = LogicalVec2 {
381 inline: inline_measure,
382 block: block_measure,
383 };
384 }
385 }
386 }
387
388 fn compute_track_constrainedness_and_has_originating_cells(
394 &mut self,
395 writing_mode: WritingMode,
396 ) {
397 self.rows = vec![RowLayout::default(); self.table.size.height];
398 self.columns = vec![ColumnLayout::default(); self.table.size.width];
399
400 let is_length = |size: &Size<ComputedLengthPercentage>| {
401 size.to_numeric().is_some_and(|size| !size.has_percentage())
402 };
403
404 for column_index in 0..self.table.size.width {
405 if let Some(column) = self.table.columns.get(column_index) {
406 let column = column.borrow();
407 if is_length(&column.base.style.box_size(writing_mode).inline) {
408 self.columns[column_index].constrained = true;
409 continue;
410 }
411 if let Some(column_group_index) = column.group_index {
412 let column_group = self.table.column_groups[column_group_index].borrow();
413 if is_length(&column_group.base.style.box_size(writing_mode).inline) {
414 self.columns[column_index].constrained = true;
415 continue;
416 }
417 }
418 }
419 }
420
421 for row_index in 0..self.table.size.height {
422 if let Some(row) = self.table.rows.get(row_index) {
423 let row = row.borrow();
424 if is_length(&row.base.style.box_size(writing_mode).block) {
425 self.rows[row_index].constrained = true;
426 continue;
427 }
428 if let Some(row_group_index) = row.group_index {
429 let row_group = self.table.row_groups[row_group_index].borrow();
430 if is_length(&row_group.base.style.box_size(writing_mode).block) {
431 self.rows[row_index].constrained = true;
432 continue;
433 }
434 }
435 }
436 }
437
438 for column_index in 0..self.table.size.width {
439 for row_index in 0..self.table.size.height {
440 let coords = TableSlotCoordinates::new(column_index, row_index);
441 let cell_constrained = match self.table.resolve_first_cell(coords) {
442 Some(cell) if cell.colspan == 1 => {
443 cell.base.style.box_size(writing_mode).map(is_length)
444 },
445 _ => LogicalVec2::default(),
446 };
447
448 let rowspan_greater_than_1 = match self.table.slots[row_index][column_index] {
449 TableSlot::Cell(ref cell) => cell.borrow().rowspan > 1,
450 _ => false,
451 };
452
453 self.rows[row_index].has_cell_with_span_greater_than_one |= rowspan_greater_than_1;
454 self.rows[row_index].constrained |= cell_constrained.block;
455
456 let has_originating_cell =
457 matches!(self.table.get_slot(coords), Some(TableSlot::Cell(_)));
458 self.columns[column_index].has_originating_cells |= has_originating_cell;
459 self.columns[column_index].constrained |= cell_constrained.inline;
460 }
461 }
462 }
463
464 fn compute_column_measures(&mut self, writing_mode: WritingMode) {
467 let mut colspan_cell_constraints = Vec::new();
498 for column_index in 0..self.table.size.width {
499 let column = &mut self.columns[column_index];
500
501 let column_measure = self.table.get_column_measure_for_column_at_index(
502 writing_mode,
503 column_index,
504 self.is_in_fixed_mode,
505 );
506 column.content_sizes = column_measure.content_sizes;
507 column.percentage = column_measure.percentage;
508
509 for row_index in 0..self.table.size.height {
510 let coords = TableSlotCoordinates::new(column_index, row_index);
511 let cell_measure = &self.cell_measures[row_index][column_index].inline;
512
513 let cell = match self.table.get_slot(coords) {
514 Some(TableSlot::Cell(cell)) => cell,
515 _ => continue,
516 }
517 .borrow();
518
519 if cell.colspan != 1 {
520 colspan_cell_constraints.push(ColspanToDistribute {
521 starting_column: column_index,
522 span: cell.colspan,
523 content_sizes: cell_measure.content_sizes,
524 percentage: cell_measure.percentage,
525 });
526 continue;
527 }
528
529 column.incorporate_cell_measure(cell_measure);
532 }
533 }
534
535 colspan_cell_constraints.sort_by(ColspanToDistribute::comparison_for_sort);
537
538 self.distribute_colspanned_cells_to_columns(colspan_cell_constraints);
540
541 let mut total_intrinsic_percentage_width = 0.;
548 for column in self.columns.iter_mut() {
549 if let Some(ref mut percentage) = column.percentage {
550 let final_intrinsic_percentage_width =
551 percentage.0.min(1. - total_intrinsic_percentage_width);
552 total_intrinsic_percentage_width += final_intrinsic_percentage_width;
553 *percentage = Percentage(final_intrinsic_percentage_width);
554 }
555 }
556 }
557
558 fn distribute_colspanned_cells_to_columns(
559 &mut self,
560 colspan_cell_constraints: Vec<ColspanToDistribute>,
561 ) {
562 for colspan_cell_constraints in colspan_cell_constraints {
563 self.distribute_colspanned_cell_to_columns(colspan_cell_constraints);
564 }
565 }
566
567 fn distribute_colspanned_cell_to_columns(
572 &mut self,
573 colspan_cell_constraints: ColspanToDistribute,
574 ) {
575 let border_spacing = self.table.border_spacing().inline;
576 let column_range = colspan_cell_constraints.range();
577 let column_count = column_range.len();
578 let total_border_spacing =
579 border_spacing.scale_by((colspan_cell_constraints.span - 1) as f32);
580
581 let mut percent_columns_count = 0;
582 let mut columns_percent_sum = 0.;
583 let mut columns_non_percent_max_inline_size_sum = Au::zero();
584 for column in self.columns[column_range.clone()].iter() {
585 if let Some(percentage) = column.percentage {
586 percent_columns_count += 1;
587 columns_percent_sum += percentage.0;
588 } else {
589 columns_non_percent_max_inline_size_sum += column.content_sizes.max_content;
590 }
591 }
592
593 let colspan_percentage = colspan_cell_constraints.percentage.unwrap_or_default();
594 let surplus_percent = colspan_percentage.0 - columns_percent_sum;
595 if surplus_percent > 0. && column_count > percent_columns_count {
596 for column in self.columns[column_range.clone()].iter_mut() {
597 if column.percentage.is_some() {
598 continue;
599 }
600
601 let ratio = if columns_non_percent_max_inline_size_sum.is_zero() {
602 1. / ((column_count - percent_columns_count) as f32)
603 } else {
604 column.content_sizes.max_content.to_f32_px() /
605 columns_non_percent_max_inline_size_sum.to_f32_px()
606 };
607 column.percentage = Some(Percentage(surplus_percent * ratio));
608 }
609 }
610
611 let colspan_cell_min_size = (colspan_cell_constraints.content_sizes.min_content -
612 total_border_spacing)
613 .max(Au::zero());
614 let distributed_minimum = Self::distribute_width_to_columns(
615 colspan_cell_min_size,
616 &self.columns[column_range.clone()],
617 );
618 {
619 let column_span = &mut self.columns[colspan_cell_constraints.range()];
620 for (column, minimum_size) in column_span.iter_mut().zip(distributed_minimum) {
621 column.content_sizes.min_content.max_assign(minimum_size);
622 }
623 }
624
625 let colspan_cell_max_size = (colspan_cell_constraints.content_sizes.max_content -
626 total_border_spacing)
627 .max(Au::zero());
628 let distributed_maximum = Self::distribute_width_to_columns(
629 colspan_cell_max_size,
630 &self.columns[colspan_cell_constraints.range()],
631 );
632 {
633 let column_span = &mut self.columns[colspan_cell_constraints.range()];
634 for (column, maximum_size) in column_span.iter_mut().zip(distributed_maximum) {
635 column
636 .content_sizes
637 .max_content
638 .max_assign(maximum_size.max(column.content_sizes.min_content));
639 }
640 }
641 }
642
643 fn compute_measures(&mut self, layout_context: &LayoutContext, writing_mode: WritingMode) {
644 self.compute_track_constrainedness_and_has_originating_cells(writing_mode);
645 self.compute_cell_measures(layout_context, writing_mode);
646 self.compute_column_measures(writing_mode);
647 }
648
649 fn compute_grid_min_max(&self) -> ContentSizes {
651 let mut largest_percentage_column_max_size = Au::zero();
664 let mut percent_sum = 0.;
665 let mut non_percent_columns_max_sum = Au::zero();
666 let mut grid_min_max = ContentSizes::zero();
667 for column in self.columns.iter() {
668 match column.percentage {
669 Some(percentage) if !percentage.is_zero() => {
670 largest_percentage_column_max_size.max_assign(
671 column
672 .content_sizes
673 .max_content
674 .scale_by(1.0 / percentage.0),
675 );
676 percent_sum += percentage.0;
677 },
678 _ => {
679 non_percent_columns_max_sum += column.content_sizes.max_content;
680 },
681 }
682
683 grid_min_max += column.content_sizes;
684 }
685
686 grid_min_max
687 .max_content
688 .max_assign(largest_percentage_column_max_size);
689
690 if !percent_sum.is_zero() &&
694 self.table
695 .percentage_columns_allowed_for_inline_content_sizes
696 {
697 let total_inline_size =
698 non_percent_columns_max_sum.scale_by(1.0 / (1.0 - percent_sum.min(1.0)));
699 grid_min_max.max_content.max_assign(total_inline_size);
700 }
701
702 assert!(
703 grid_min_max.min_content <= grid_min_max.max_content,
704 "GRIDMAX should never be smaller than GRIDMIN {:?}",
705 grid_min_max
706 );
707
708 let inline_border_spacing = self.table.total_border_spacing().inline;
709 grid_min_max.min_content += inline_border_spacing;
710 grid_min_max.max_content += inline_border_spacing;
711 grid_min_max
712 }
713
714 fn compute_caption_minimum_inline_size(&self, layout_context: &LayoutContext) -> Au {
716 let containing_block = IndefiniteContainingBlock {
717 size: LogicalVec2::default(),
718 style: &self.table.style,
719 };
720 self.table
721 .captions
722 .iter()
723 .map(|caption| {
724 caption
725 .borrow()
726 .context
727 .outer_inline_content_sizes(
728 layout_context,
729 &containing_block,
730 &LogicalVec2::zero(),
731 false, )
733 .sizes
734 .min_content
735 })
736 .max()
737 .unwrap_or_default()
738 }
739
740 fn compute_table_width(&mut self, containing_block_for_children: &ContainingBlock) {
741 self.table_width = containing_block_for_children.size.inline;
746
747 self.assignable_width = self.table_width - self.table.total_border_spacing().inline;
751
752 self.basis_for_cell_padding_percentage =
755 self.table_width - self.table.border_spacing().inline * 2;
756 }
757
758 fn distribute_width_to_columns(target_inline_size: Au, columns: &[ColumnLayout]) -> Vec<Au> {
761 if columns.is_empty() {
764 return Vec::new();
765 }
766
767 let mut min_content_sizing_guesses = Vec::new();
796 let mut min_content_percentage_sizing_guesses = Vec::new();
797 let mut min_content_specified_sizing_guesses = Vec::new();
798 let mut max_content_sizing_guesses = Vec::new();
799
800 for column in columns {
801 let min_content_width = column.content_sizes.min_content;
802 let max_content_width = column.content_sizes.max_content;
803 let constrained = column.constrained;
804
805 let (
806 min_content_percentage_sizing_guess,
807 min_content_specified_sizing_guess,
808 max_content_sizing_guess,
809 ) = if let Some(percentage) = column.percentage {
810 let resolved = target_inline_size.scale_by(percentage.0);
811 let percent_guess = min_content_width.max(resolved);
812 (percent_guess, percent_guess, percent_guess)
813 } else if constrained {
814 (min_content_width, max_content_width, max_content_width)
815 } else {
816 (min_content_width, min_content_width, max_content_width)
817 };
818
819 min_content_sizing_guesses.push(min_content_width);
820 min_content_percentage_sizing_guesses.push(min_content_percentage_sizing_guess);
821 min_content_specified_sizing_guesses.push(min_content_specified_sizing_guess);
822 max_content_sizing_guesses.push(max_content_sizing_guess);
823 }
824
825 let max_content_sizing_sum = max_content_sizing_guesses.iter().sum();
833 if target_inline_size >= max_content_sizing_sum {
834 Self::distribute_extra_width_to_columns(
835 columns,
836 &mut max_content_sizing_guesses,
837 max_content_sizing_sum,
838 target_inline_size,
839 );
840 return max_content_sizing_guesses;
841 }
842 let min_content_specified_sizing_sum = min_content_specified_sizing_guesses.iter().sum();
843 if target_inline_size == min_content_specified_sizing_sum {
844 return min_content_specified_sizing_guesses;
845 }
846 let min_content_percentage_sizing_sum = min_content_percentage_sizing_guesses.iter().sum();
847 if target_inline_size == min_content_percentage_sizing_sum {
848 return min_content_percentage_sizing_guesses;
849 }
850 let min_content_sizes_sum = min_content_sizing_guesses.iter().sum();
851 if target_inline_size <= min_content_sizes_sum {
852 return min_content_sizing_guesses;
853 }
854
855 let bounds = |sum_a, sum_b| target_inline_size > sum_a && target_inline_size < sum_b;
856
857 let blend = |a: &[Au], sum_a: Au, b: &[Au], sum_b: Au| {
858 let weight_a = (target_inline_size - sum_b).to_f32_px() / (sum_a - sum_b).to_f32_px();
860 let weight_b = 1.0 - weight_a;
861
862 let mut remaining_assignable_width = target_inline_size;
863 let mut widths: Vec<Au> = a
864 .iter()
865 .zip(b.iter())
866 .map(|(guess_a, guess_b)| {
867 let column_width = guess_a.scale_by(weight_a) + guess_b.scale_by(weight_b);
868 let column_width = column_width.min(remaining_assignable_width);
871 remaining_assignable_width -= column_width;
872 column_width
873 })
874 .collect();
875
876 if !remaining_assignable_width.is_zero() {
877 debug_assert!(
881 remaining_assignable_width >= Au::zero(),
882 "Sum of columns shouldn't exceed the assignable table width"
883 );
884 debug_assert!(
885 remaining_assignable_width <= Au::new(widths.len() as i32),
886 "A deviation of more than one Au per column is unlikely to be caused by float imprecision"
887 );
888
889 widths[0] += remaining_assignable_width;
892 }
893
894 debug_assert!(widths.iter().sum::<Au>() == target_inline_size);
895
896 widths
897 };
898
899 if bounds(min_content_sizes_sum, min_content_percentage_sizing_sum) {
900 return blend(
901 &min_content_sizing_guesses,
902 min_content_sizes_sum,
903 &min_content_percentage_sizing_guesses,
904 min_content_percentage_sizing_sum,
905 );
906 }
907
908 if bounds(
909 min_content_percentage_sizing_sum,
910 min_content_specified_sizing_sum,
911 ) {
912 return blend(
913 &min_content_percentage_sizing_guesses,
914 min_content_percentage_sizing_sum,
915 &min_content_specified_sizing_guesses,
916 min_content_specified_sizing_sum,
917 );
918 }
919
920 assert!(bounds(
921 min_content_specified_sizing_sum,
922 max_content_sizing_sum
923 ));
924 blend(
925 &min_content_specified_sizing_guesses,
926 min_content_specified_sizing_sum,
927 &max_content_sizing_guesses,
928 max_content_sizing_sum,
929 )
930 }
931
932 fn distribute_extra_width_to_columns(
935 columns: &[ColumnLayout],
936 column_sizes: &mut [Au],
937 column_sizes_sum: Au,
938 assignable_width: Au,
939 ) {
940 let all_columns = 0..columns.len();
941 let extra_inline_size = assignable_width - column_sizes_sum;
942
943 let has_originating_cells =
944 |column_index: &usize| columns[*column_index].has_originating_cells;
945 let is_constrained = |column_index: &usize| columns[*column_index].constrained;
946 let is_unconstrained = |column_index: &usize| !is_constrained(column_index);
947 let has_percent_greater_than_zero = |column_index: &usize| {
948 columns[*column_index]
949 .percentage
950 .is_some_and(|percentage| percentage.0 > 0.)
951 };
952 let has_percent_zero = |column_index: &usize| !has_percent_greater_than_zero(column_index);
953 let has_max_content =
954 |column_index: &usize| !columns[*column_index].content_sizes.max_content.is_zero();
955
956 let max_content_sum = |column_index: usize| columns[column_index].content_sizes.max_content;
957
958 let unconstrained_max_content_columns = all_columns
964 .clone()
965 .filter(is_unconstrained)
966 .filter(has_originating_cells)
967 .filter(has_percent_zero)
968 .filter(has_max_content);
969 let total_max_content_width: Au = unconstrained_max_content_columns
970 .clone()
971 .map(max_content_sum)
972 .sum();
973 if !total_max_content_width.is_zero() {
974 for column_index in unconstrained_max_content_columns {
975 column_sizes[column_index] += extra_inline_size.scale_by(
976 columns[column_index].content_sizes.max_content.to_f32_px() /
977 total_max_content_width.to_f32_px(),
978 );
979 }
980 return;
981 }
982
983 let unconstrained_no_percent_columns = all_columns
989 .clone()
990 .filter(is_unconstrained)
991 .filter(has_originating_cells)
992 .filter(has_percent_zero);
993 let total_unconstrained_no_percent = unconstrained_no_percent_columns.clone().count();
994 if total_unconstrained_no_percent > 0 {
995 let extra_space_per_column =
996 extra_inline_size.scale_by(1.0 / total_unconstrained_no_percent as f32);
997 for column_index in unconstrained_no_percent_columns {
998 column_sizes[column_index] += extra_space_per_column;
999 }
1000 return;
1001 }
1002
1003 let constrained_max_content_columns = all_columns
1009 .clone()
1010 .filter(is_constrained)
1011 .filter(has_originating_cells)
1012 .filter(has_percent_zero)
1013 .filter(has_max_content);
1014 let total_max_content_width: Au = constrained_max_content_columns
1015 .clone()
1016 .map(max_content_sum)
1017 .sum();
1018 if !total_max_content_width.is_zero() {
1019 for column_index in constrained_max_content_columns {
1020 column_sizes[column_index] += extra_inline_size.scale_by(
1021 columns[column_index].content_sizes.max_content.to_f32_px() /
1022 total_max_content_width.to_f32_px(),
1023 );
1024 }
1025 return;
1026 }
1027
1028 let columns_with_percentage = all_columns.clone().filter(has_percent_greater_than_zero);
1034 let total_percent = columns_with_percentage
1035 .clone()
1036 .map(|column_index| columns[column_index].percentage.unwrap_or_default().0)
1037 .sum::<f32>();
1038 if total_percent > 0. {
1039 for column_index in columns_with_percentage {
1040 let column_percentage = columns[column_index].percentage.unwrap_or_default();
1041 column_sizes[column_index] +=
1042 extra_inline_size.scale_by(column_percentage.0 / total_percent);
1043 }
1044 return;
1045 }
1046
1047 let has_originating_cells_columns = all_columns.clone().filter(has_originating_cells);
1051 let total_has_originating_cells = has_originating_cells_columns.clone().count();
1052 if total_has_originating_cells > 0 {
1053 let extra_space_per_column =
1054 extra_inline_size.scale_by(1.0 / total_has_originating_cells as f32);
1055 for column_index in has_originating_cells_columns {
1056 column_sizes[column_index] += extra_space_per_column;
1057 }
1058 return;
1059 }
1060
1061 let extra_space_for_all_columns = extra_inline_size.scale_by(1.0 / columns.len() as f32);
1064 for guess in column_sizes.iter_mut() {
1065 *guess += extra_space_for_all_columns;
1066 }
1067 }
1068
1069 fn layout_cells_in_row(
1072 &mut self,
1073 layout_context: &LayoutContext,
1074 containing_block_for_table: &ContainingBlock,
1075 ) {
1076 let layout_table_slot = |coordinate: TableSlotCoordinates, slot: &TableSlot| {
1077 let TableSlot::Cell(cell) = slot else {
1078 return None;
1079 };
1080
1081 let cell = cell.borrow();
1082 let area = LogicalSides {
1083 inline_start: coordinate.x,
1084 inline_end: coordinate.x + cell.colspan,
1085 block_start: coordinate.y,
1086 block_end: coordinate.y + cell.rowspan,
1087 };
1088 let layout_style = cell.layout_style();
1089 let border = self
1090 .get_collapsed_border_widths_for_area(area)
1091 .unwrap_or_else(|| {
1092 layout_style.border_width(containing_block_for_table.style.writing_mode)
1093 });
1094 let padding: LogicalSides<Au> = layout_style
1095 .padding(containing_block_for_table.style.writing_mode)
1096 .percentages_relative_to(self.basis_for_cell_padding_percentage);
1097 let inline_border_padding_sum = border.inline_sum() + padding.inline_sum();
1098 let border_spacing_spanned =
1099 self.table.border_spacing().inline * (cell.colspan - 1) as i32;
1100
1101 let mut total_cell_width = (coordinate.x..coordinate.x + cell.colspan)
1102 .map(|column_index| self.distributed_column_widths[column_index])
1103 .sum::<Au>() -
1104 inline_border_padding_sum +
1105 border_spacing_spanned;
1106 total_cell_width = total_cell_width.max(Au::zero());
1107
1108 let containing_block_for_children = ContainingBlock {
1109 size: ContainingBlockSize {
1110 inline: total_cell_width,
1111 block: SizeConstraint::default(),
1112 },
1113 style: &cell.base.style,
1114 };
1115
1116 let mut positioning_context = PositioningContext::default();
1117 let layout = cell.contents.layout(
1118 layout_context,
1119 &mut positioning_context,
1120 &containing_block_for_children,
1121 );
1122
1123 Some(CellLayout {
1124 layout,
1125 padding,
1126 border,
1127 positioning_context,
1128 })
1129 };
1130
1131 self.cells_laid_out = if layout_context.use_rayon {
1132 self.table
1133 .slots
1134 .par_iter()
1135 .enumerate()
1136 .map(|(row_index, row_slots)| {
1137 row_slots
1138 .par_iter()
1139 .enumerate()
1140 .map(|(column_index, slot)| {
1141 layout_table_slot(
1142 TableSlotCoordinates::new(column_index, row_index),
1143 slot,
1144 )
1145 })
1146 .collect()
1147 })
1148 .collect()
1149 } else {
1150 self.table
1151 .slots
1152 .iter()
1153 .enumerate()
1154 .map(|(row_index, row_slots)| {
1155 row_slots
1156 .iter()
1157 .enumerate()
1158 .map(|(column_index, slot)| {
1159 layout_table_slot(
1160 TableSlotCoordinates::new(column_index, row_index),
1161 slot,
1162 )
1163 })
1164 .collect()
1165 })
1166 .collect()
1167 };
1168
1169 for row_index in 0..self.table.size.height {
1172 for column_index in 0..self.table.size.width {
1173 let Some(layout) = &self.cells_laid_out[row_index][column_index] else {
1174 continue;
1175 };
1176
1177 self.cell_measures[row_index][column_index]
1178 .block
1179 .content_sizes
1180 .max_assign(layout.outer_block_size().into());
1181 }
1182 }
1183 }
1184
1185 fn do_first_row_layout(&mut self, writing_mode: WritingMode) -> Vec<Au> {
1188 let mut row_sizes = (0..self.table.size.height)
1189 .map(|row_index| {
1190 let (mut max_ascent, mut max_descent, mut max_row_height) =
1191 (Au::zero(), Au::zero(), Au::zero());
1192
1193 for column_index in 0..self.table.size.width {
1194 let cell = match self.table.slots[row_index][column_index] {
1195 TableSlot::Cell(ref cell) => cell,
1196 _ => continue,
1197 };
1198
1199 let layout = match self.cells_laid_out[row_index][column_index] {
1200 Some(ref layout) => layout,
1201 None => {
1202 warn!(
1203 "Did not find a layout at a slot index with an originating cell."
1204 );
1205 continue;
1206 },
1207 };
1208
1209 let cell = cell.borrow();
1210 let outer_block_size = layout.outer_block_size();
1211 if cell.rowspan == 1 {
1212 max_row_height.max_assign(outer_block_size);
1213 }
1214
1215 if cell.effective_vertical_align() == VerticalAlignKeyword::Baseline {
1216 let ascent = layout.ascent();
1217 let border_padding_start =
1218 layout.border.block_start + layout.padding.block_start;
1219 let border_padding_end = layout.border.block_end + layout.padding.block_end;
1220 max_ascent.max_assign(ascent + border_padding_start);
1221
1222 if cell.rowspan == 1 {
1226 max_descent.max_assign(
1227 layout.layout.content_block_size - ascent + border_padding_end,
1228 );
1229 }
1230 }
1231 }
1232 self.row_baselines.push(max_ascent);
1233 max_row_height.max(max_ascent + max_descent)
1234 })
1235 .collect();
1236 self.calculate_row_sizes_after_first_layout(&mut row_sizes, writing_mode);
1237 row_sizes
1238 }
1239
1240 #[allow(clippy::ptr_arg)] fn calculate_row_sizes_after_first_layout(
1245 &mut self,
1246 row_sizes: &mut Vec<Au>,
1247 writing_mode: WritingMode,
1248 ) {
1249 let mut cells_to_distribute = Vec::new();
1250 let mut total_percentage = 0.;
1251 #[allow(clippy::needless_range_loop)] for row_index in 0..self.table.size.height {
1253 let row_measure = self
1254 .table
1255 .get_row_measure_for_row_at_index(writing_mode, row_index);
1256 row_sizes[row_index].max_assign(row_measure.content_sizes.min_content);
1257
1258 let mut percentage = row_measure.percentage.unwrap_or_default().0;
1259 for column_index in 0..self.table.size.width {
1260 let cell_percentage = self.cell_measures[row_index][column_index]
1261 .block
1262 .percentage
1263 .unwrap_or_default()
1264 .0;
1265 percentage = percentage.max(cell_percentage);
1266
1267 let cell_measure = &self.cell_measures[row_index][column_index].block;
1268 let cell = match self.table.slots[row_index][column_index] {
1269 TableSlot::Cell(ref cell) if cell.borrow().rowspan > 1 => cell,
1270 TableSlot::Cell(_) => {
1271 row_sizes[row_index].max_assign(cell_measure.content_sizes.max_content);
1274 continue;
1275 },
1276 _ => continue,
1277 };
1278
1279 cells_to_distribute.push(RowspanToDistribute {
1280 coordinates: TableSlotCoordinates::new(column_index, row_index),
1281 cell: cell.borrow(),
1282 measure: cell_measure,
1283 });
1284 }
1285
1286 self.rows[row_index].percent = Percentage(percentage.min(1. - total_percentage));
1287 total_percentage += self.rows[row_index].percent.0;
1288 }
1289
1290 cells_to_distribute.sort_by(|a, b| {
1291 if a.range() == b.range() {
1292 return a
1293 .measure
1294 .content_sizes
1295 .min_content
1296 .cmp(&b.measure.content_sizes.min_content);
1297 }
1298 if a.fully_encloses(b) {
1299 return std::cmp::Ordering::Greater;
1300 }
1301 if b.fully_encloses(a) {
1302 return std::cmp::Ordering::Less;
1303 }
1304 a.coordinates.y.cmp(&b.coordinates.y)
1305 });
1306
1307 for rowspan_to_distribute in cells_to_distribute {
1308 let rows_spanned = rowspan_to_distribute.range();
1309 let current_rows_size = rows_spanned.clone().map(|index| row_sizes[index]).sum();
1310 let border_spacing_spanned =
1311 self.table.border_spacing().block * (rows_spanned.len() - 1) as i32;
1312 let excess_size = (rowspan_to_distribute.measure.content_sizes.min_content -
1313 current_rows_size -
1314 border_spacing_spanned)
1315 .max(Au::zero());
1316
1317 self.distribute_extra_size_to_rows(
1318 excess_size,
1319 rows_spanned,
1320 row_sizes,
1321 None,
1322 true, );
1324 }
1325 }
1326
1327 fn distribute_extra_size_to_rows(
1330 &self,
1331 mut excess_size: Au,
1332 track_range: Range<usize>,
1333 track_sizes: &mut [Au],
1334 percentage_resolution_size: Option<Au>,
1335 rowspan_distribution: bool,
1336 ) {
1337 if excess_size.is_zero() {
1338 return;
1339 }
1340
1341 let is_constrained = |track_index: &usize| self.rows[*track_index].constrained;
1342 let is_unconstrained = |track_index: &usize| !is_constrained(track_index);
1343 let is_empty: Vec<bool> = track_sizes.iter().map(|size| size.is_zero()).collect();
1344 let is_not_empty = |track_index: &usize| !is_empty[*track_index];
1345 let other_row_that_starts_a_rowspan = |track_index: &usize| {
1346 *track_index != track_range.start &&
1347 self.rows[*track_index].has_cell_with_span_greater_than_one
1348 };
1349
1350 if let Some(percentage_resolution_size) = percentage_resolution_size {
1354 let get_percent_block_size_deficit = |row_index: usize, track_size: Au| {
1355 let size_needed_for_percent =
1356 percentage_resolution_size.scale_by(self.rows[row_index].percent.0);
1357 (size_needed_for_percent - track_size).max(Au::zero())
1358 };
1359 let percent_block_size_deficit: Au = track_range
1360 .clone()
1361 .map(|index| get_percent_block_size_deficit(index, track_sizes[index]))
1362 .sum();
1363 let percent_distributable_block_size = percent_block_size_deficit.min(excess_size);
1364 if percent_distributable_block_size > Au::zero() {
1365 for track_index in track_range.clone() {
1366 let row_deficit =
1367 get_percent_block_size_deficit(track_index, track_sizes[track_index]);
1368 if row_deficit > Au::zero() {
1369 let ratio =
1370 row_deficit.to_f32_px() / percent_block_size_deficit.to_f32_px();
1371 let size = percent_distributable_block_size.scale_by(ratio);
1372 track_sizes[track_index] += size;
1373 excess_size -= size;
1374 }
1375 }
1376 }
1377 }
1378
1379 if rowspan_distribution {
1382 let rows_that_start_rowspan: Vec<usize> = track_range
1383 .clone()
1384 .filter(other_row_that_starts_a_rowspan)
1385 .collect();
1386 if !rows_that_start_rowspan.is_empty() {
1387 let scale = 1.0 / rows_that_start_rowspan.len() as f32;
1388 for track_index in rows_that_start_rowspan.iter() {
1389 track_sizes[*track_index] += excess_size.scale_by(scale);
1390 }
1391 return;
1392 }
1393 }
1394
1395 let unconstrained_non_empty_rows: Vec<usize> = track_range
1397 .clone()
1398 .filter(is_unconstrained)
1399 .filter(is_not_empty)
1400 .collect();
1401 if !unconstrained_non_empty_rows.is_empty() {
1402 let total_size: Au = unconstrained_non_empty_rows
1403 .iter()
1404 .map(|index| track_sizes[*index])
1405 .sum();
1406 for track_index in unconstrained_non_empty_rows.iter() {
1407 let scale = track_sizes[*track_index].to_f32_px() / total_size.to_f32_px();
1408 track_sizes[*track_index] += excess_size.scale_by(scale);
1409 }
1410 return;
1411 }
1412
1413 let (non_empty_rows, empty_rows): (Vec<usize>, Vec<usize>) =
1414 track_range.clone().partition(is_not_empty);
1415 let only_have_empty_rows = empty_rows.len() == track_range.len();
1416 if !empty_rows.is_empty() {
1417 if rowspan_distribution && only_have_empty_rows {
1420 track_sizes[*empty_rows.last().unwrap()] += excess_size;
1421 return;
1422 }
1423
1424 let non_empty_rows_all_constrained = !non_empty_rows.iter().any(is_unconstrained);
1427 if only_have_empty_rows || non_empty_rows_all_constrained {
1428 let mut rows_to_grow = &empty_rows;
1431 let unconstrained_empty_rows: Vec<usize> = rows_to_grow
1432 .iter()
1433 .copied()
1434 .filter(is_unconstrained)
1435 .collect();
1436 if !unconstrained_empty_rows.is_empty() {
1437 rows_to_grow = &unconstrained_empty_rows;
1438 }
1439
1440 let scale = 1.0 / rows_to_grow.len() as f32;
1442 for track_index in rows_to_grow.iter() {
1443 track_sizes[*track_index] += excess_size.scale_by(scale);
1444 }
1445 return;
1446 }
1447 }
1448
1449 if !non_empty_rows.is_empty() {
1452 let total_size: Au = non_empty_rows.iter().map(|index| track_sizes[*index]).sum();
1453 for track_index in non_empty_rows.iter() {
1454 let scale = track_sizes[*track_index].to_f32_px() / total_size.to_f32_px();
1455 track_sizes[*track_index] += excess_size.scale_by(scale);
1456 }
1457 }
1458 }
1459
1460 fn compute_table_height_and_final_row_heights(
1463 &mut self,
1464 mut row_sizes: Vec<Au>,
1465 containing_block_for_children: &ContainingBlock,
1466 ) {
1467 let table_height_from_style = containing_block_for_children.size.block.definite_or_min();
1474
1475 let block_border_spacing = self.table.total_border_spacing().block;
1476 let table_height_from_rows = row_sizes.iter().sum::<Au>() + block_border_spacing;
1477 self.final_table_height = table_height_from_rows.max(table_height_from_style);
1478
1479 if self.final_table_height == table_height_from_rows {
1482 self.row_sizes = row_sizes;
1483 return;
1484 }
1485
1486 self.distribute_extra_size_to_rows(
1491 self.final_table_height - table_height_from_rows,
1492 0..self.table.size.height,
1493 &mut row_sizes,
1494 Some(self.final_table_height),
1495 false, );
1497 self.row_sizes = row_sizes;
1498 }
1499
1500 fn layout_caption(
1501 &self,
1502 caption: &TableCaption,
1503 layout_context: &LayoutContext,
1504 parent_positioning_context: &mut PositioningContext,
1505 ) -> BoxFragment {
1506 let containing_block = &ContainingBlock {
1507 size: ContainingBlockSize {
1508 inline: self.table_width + self.pbm.padding_border_sums.inline,
1509 block: SizeConstraint::default(),
1510 },
1511 style: &self.table.style,
1512 };
1513
1514 let ignore_block_margins_for_stretch = LogicalSides1D::new(false, false);
1518
1519 let mut positioning_context =
1520 PositioningContext::new_for_layout_box_base(&caption.context.base);
1521 let mut box_fragment = caption.context.layout_in_flow_block_level(
1522 layout_context,
1523 positioning_context
1524 .as_mut()
1525 .unwrap_or(parent_positioning_context),
1526 containing_block,
1527 None, ignore_block_margins_for_stretch,
1529 );
1530
1531 if let Some(mut positioning_context) = positioning_context.take() {
1532 positioning_context.layout_collected_children(layout_context, &mut box_fragment);
1533 parent_positioning_context.append(positioning_context);
1534 }
1535
1536 box_fragment
1537 }
1538
1539 #[servo_tracing::instrument(name = "Table::layout", skip_all)]
1542 fn layout(
1543 mut self,
1544 layout_context: &LayoutContext,
1545 positioning_context: &mut PositioningContext,
1546 containing_block_for_children: &ContainingBlock,
1547 containing_block_for_table: &ContainingBlock,
1548 ) -> CacheableLayoutResult {
1549 let table_writing_mode = containing_block_for_children.style.writing_mode;
1550 self.compute_border_collapse(table_writing_mode);
1551 let layout_style = self.table.layout_style(Some(&self));
1552
1553 self.pbm = layout_style
1554 .padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1555 table_writing_mode,
1556 containing_block_for_table.size.inline,
1557 );
1558 self.compute_measures(layout_context, table_writing_mode);
1559 self.compute_table_width(containing_block_for_children);
1560
1561 let containing_block_for_logical_conversion = ContainingBlock {
1576 size: ContainingBlockSize {
1577 inline: self.table_width,
1578 block: containing_block_for_table.size.block,
1579 },
1580 style: containing_block_for_children.style,
1581 };
1582 let offset_from_wrapper = -self.pbm.padding - self.pbm.border;
1583 let mut current_block_offset = offset_from_wrapper.block_start;
1584
1585 let mut table_layout = CacheableLayoutResult {
1586 fragments: Vec::new(),
1587 content_block_size: Zero::zero(),
1588 content_inline_size_for_table: None,
1589 baselines: Baselines::default(),
1590 depends_on_block_constraints: true,
1591 specific_layout_info: Some(SpecificLayoutInfo::TableWrapper),
1592 collapsible_margins_in_children: CollapsedBlockMargins::zero(),
1593 };
1594
1595 #[derive(EnumIter, PartialEq)]
1596 enum TableWrapperSection {
1597 TopCaptions,
1598 Grid,
1599 BottomCaptions,
1600 }
1601 impl TableWrapperSection {
1602 fn accepts_caption(&self, caption: &TableCaption) -> bool {
1603 match caption.context.style().clone_caption_side() {
1604 CaptionSide::Top => *self == TableWrapperSection::TopCaptions,
1605 CaptionSide::Bottom => *self == TableWrapperSection::BottomCaptions,
1606 }
1607 }
1608 }
1609
1610 for section in TableWrapperSection::iter() {
1611 if section == TableWrapperSection::Grid {
1612 let original_positioning_context_length = positioning_context.len();
1613 let mut grid_fragment = self.layout_grid(
1614 layout_context,
1615 positioning_context,
1616 &containing_block_for_logical_conversion,
1617 containing_block_for_children,
1618 );
1619
1620 let logical_grid_content_rect = grid_fragment
1623 .content_rect()
1624 .to_logical(&containing_block_for_logical_conversion);
1625 let grid_pbm = grid_fragment
1626 .padding_border_margin()
1627 .to_logical(table_writing_mode);
1628 table_layout.baselines = grid_fragment.baselines(table_writing_mode).offset(
1629 current_block_offset +
1630 logical_grid_content_rect.start_corner.block +
1631 grid_pbm.block_start,
1632 );
1633
1634 grid_fragment.base.rect = LogicalRect {
1635 start_corner: LogicalVec2 {
1636 inline: offset_from_wrapper.inline_start + grid_pbm.inline_start,
1637 block: current_block_offset + grid_pbm.block_start,
1638 },
1639 size: grid_fragment.base.rect.size.to_logical(table_writing_mode),
1640 }
1641 .as_physical(Some(&containing_block_for_logical_conversion));
1642
1643 current_block_offset += grid_fragment
1644 .border_rect()
1645 .size
1646 .to_logical(table_writing_mode)
1647 .block;
1648 if logical_grid_content_rect.size.inline < self.table_width {
1649 table_layout.content_inline_size_for_table =
1651 Some(logical_grid_content_rect.size.inline);
1652 }
1653
1654 let grid_fragment = Fragment::Box(ArcRefCell::new(grid_fragment));
1655 positioning_context.adjust_static_position_of_hoisted_fragments(
1656 &grid_fragment,
1657 original_positioning_context_length,
1658 );
1659 table_layout.fragments.push(grid_fragment);
1660 } else {
1661 let caption_fragments = self.table.captions.iter().filter_map(|caption| {
1662 let caption = caption.borrow();
1663 if !section.accepts_caption(&caption) {
1664 return None;
1665 }
1666
1667 let original_positioning_context_length = positioning_context.len();
1668 let mut caption_fragment =
1669 self.layout_caption(&caption, layout_context, positioning_context);
1670
1671 let caption_pbm = caption_fragment
1674 .padding_border_margin()
1675 .to_logical(table_writing_mode);
1676
1677 let caption_style = caption_fragment.style().clone();
1678 let caption_relative_offset = match caption_style.clone_position() {
1679 Position::Relative => {
1680 relative_adjustement(&caption_style, containing_block_for_children)
1681 },
1682 _ => LogicalVec2::zero(),
1683 };
1684
1685 caption_fragment.base.rect = LogicalRect {
1686 start_corner: LogicalVec2 {
1687 inline: offset_from_wrapper.inline_start + caption_pbm.inline_start,
1688 block: current_block_offset + caption_pbm.block_start,
1689 } + caption_relative_offset,
1690 size: caption_fragment
1691 .content_rect()
1692 .size
1693 .to_logical(table_writing_mode),
1694 }
1695 .as_physical(Some(&containing_block_for_logical_conversion));
1696
1697 current_block_offset += caption_fragment
1698 .margin_rect()
1699 .size
1700 .to_logical(table_writing_mode)
1701 .block;
1702
1703 let caption_fragment = Fragment::Box(ArcRefCell::new(caption_fragment));
1704 positioning_context.adjust_static_position_of_hoisted_fragments(
1705 &caption_fragment,
1706 original_positioning_context_length,
1707 );
1708
1709 caption.context.base.set_fragment(caption_fragment.clone());
1710 Some(caption_fragment)
1711 });
1712 table_layout.fragments.extend(caption_fragments);
1713 }
1714 }
1715
1716 table_layout.content_block_size = current_block_offset + offset_from_wrapper.block_end;
1717 table_layout
1718 }
1719
1720 fn layout_grid(
1723 &mut self,
1724 layout_context: &LayoutContext,
1725 positioning_context: &mut PositioningContext,
1726 containing_block_for_logical_conversion: &ContainingBlock,
1727 containing_block_for_children: &ContainingBlock,
1728 ) -> BoxFragment {
1729 self.distributed_column_widths =
1730 Self::distribute_width_to_columns(self.assignable_width, &self.columns);
1731 self.layout_cells_in_row(layout_context, containing_block_for_children);
1732 let table_writing_mode = containing_block_for_children.style.writing_mode;
1733 let first_layout_row_heights = self.do_first_row_layout(table_writing_mode);
1734 self.compute_table_height_and_final_row_heights(
1735 first_layout_row_heights,
1736 containing_block_for_children,
1737 );
1738
1739 assert_eq!(self.table.size.height, self.row_sizes.len());
1740 assert_eq!(self.table.size.width, self.distributed_column_widths.len());
1741
1742 if self.table.size.width == 0 && self.table.size.height == 0 {
1743 let content_rect = LogicalRect {
1744 start_corner: LogicalVec2::zero(),
1745 size: LogicalVec2 {
1746 inline: self.table_width,
1747 block: self.final_table_height,
1748 },
1749 }
1750 .as_physical(Some(containing_block_for_logical_conversion));
1751 return BoxFragment::new(
1752 self.table.grid_base_fragment_info,
1753 self.table.grid_style.clone(),
1754 Vec::new(),
1755 content_rect,
1756 self.pbm.padding.to_physical(table_writing_mode),
1757 self.pbm.border.to_physical(table_writing_mode),
1758 PhysicalSides::zero(),
1759 self.specific_layout_info_for_grid(),
1760 );
1761 }
1762
1763 let mut table_fragments = Vec::new();
1764 let table_and_track_dimensions = TableAndTrackDimensions::new(self);
1765 self.make_fragments_for_columns_and_column_groups(
1766 &table_and_track_dimensions,
1767 &mut table_fragments,
1768 );
1769
1770 let mut baselines = Baselines::default();
1771 let mut row_group_fragment_layout = None;
1772 for row_index in 0..self.table.size.height {
1773 if row_index == 0 {
1783 let row_end = table_and_track_dimensions
1784 .get_row_rect(0)
1785 .max_block_position();
1786 baselines.first = Some(row_end);
1787 baselines.last = Some(row_end);
1788 }
1789
1790 let row_is_collapsed = self.is_row_collapsed(row_index);
1791 let table_row = self.table.rows[row_index].borrow();
1792 let mut row_fragment_layout = RowFragmentLayout::new(
1793 &table_row,
1794 row_index,
1795 &table_and_track_dimensions,
1796 &self.table.style,
1797 );
1798
1799 let old_row_group_index = row_group_fragment_layout
1800 .as_ref()
1801 .map(|layout: &RowGroupFragmentLayout| layout.index);
1802 if table_row.group_index != old_row_group_index {
1803 if let Some(old_row_group_layout) = row_group_fragment_layout.take() {
1805 table_fragments.push(old_row_group_layout.finish(
1806 layout_context,
1807 positioning_context,
1808 containing_block_for_logical_conversion,
1809 containing_block_for_children,
1810 ));
1811 }
1812
1813 if let Some(new_group_index) = table_row.group_index {
1815 row_group_fragment_layout = Some(RowGroupFragmentLayout::new(
1816 self.table.row_groups[new_group_index].clone(),
1817 new_group_index,
1818 &table_and_track_dimensions,
1819 ));
1820 }
1821 }
1822
1823 let column_indices = 0..self.table.size.width;
1824 row_fragment_layout.fragments.reserve(self.table.size.width);
1825 for column_index in column_indices {
1826 self.do_final_cell_layout(
1827 row_index,
1828 column_index,
1829 &table_and_track_dimensions,
1830 &mut baselines,
1831 &mut row_fragment_layout,
1832 row_group_fragment_layout.as_mut(),
1833 positioning_context,
1834 self.is_column_collapsed(column_index) || row_is_collapsed,
1835 );
1836 }
1837
1838 let row_fragment = row_fragment_layout.finish(
1839 layout_context,
1840 positioning_context,
1841 containing_block_for_logical_conversion,
1842 containing_block_for_children,
1843 &mut row_group_fragment_layout,
1844 );
1845
1846 match row_group_fragment_layout.as_mut() {
1847 Some(layout) => layout.fragments.push(row_fragment),
1848 None => table_fragments.push(row_fragment),
1849 }
1850 }
1851
1852 if let Some(row_group_layout) = row_group_fragment_layout.take() {
1853 table_fragments.push(row_group_layout.finish(
1854 layout_context,
1855 positioning_context,
1856 containing_block_for_logical_conversion,
1857 containing_block_for_children,
1858 ));
1859 }
1860
1861 let content_rect = LogicalRect {
1862 start_corner: LogicalVec2::zero(),
1863 size: LogicalVec2 {
1864 inline: table_and_track_dimensions.table_rect.max_inline_position(),
1865 block: table_and_track_dimensions.table_rect.max_block_position(),
1866 },
1867 }
1868 .as_physical(Some(containing_block_for_logical_conversion));
1869 BoxFragment::new(
1870 self.table.grid_base_fragment_info,
1871 self.table.grid_style.clone(),
1872 table_fragments,
1873 content_rect,
1874 self.pbm.padding.to_physical(table_writing_mode),
1875 self.pbm.border.to_physical(table_writing_mode),
1876 PhysicalSides::zero(),
1877 self.specific_layout_info_for_grid(),
1878 )
1879 .with_baselines(baselines)
1880 }
1881
1882 fn specific_layout_info_for_grid(&mut self) -> Option<SpecificLayoutInfo> {
1883 mem::take(&mut self.collapsed_borders).map(|mut collapsed_borders| {
1884 let mut track_sizes = LogicalVec2 {
1887 inline: mem::take(&mut self.distributed_column_widths),
1888 block: mem::take(&mut self.row_sizes),
1889 };
1890 for (column_index, column_size) in track_sizes.inline.iter_mut().enumerate() {
1891 if self.is_column_collapsed(column_index) {
1892 mem::take(column_size);
1893 }
1894 }
1895 for (row_index, row_size) in track_sizes.block.iter_mut().enumerate() {
1896 if self.is_row_collapsed(row_index) {
1897 mem::take(row_size);
1898 }
1899 }
1900 let writing_mode = self.table.style.writing_mode;
1901 if !writing_mode.is_bidi_ltr() {
1902 track_sizes.inline.reverse();
1903 collapsed_borders.inline.reverse();
1904 for border_line in &mut collapsed_borders.block {
1905 border_line.reverse();
1906 }
1907 }
1908 SpecificLayoutInfo::TableGridWithCollapsedBorders(Box::new(SpecificTableGridInfo {
1909 collapsed_borders: if writing_mode.is_horizontal() {
1910 PhysicalVec::new(collapsed_borders.inline, collapsed_borders.block)
1911 } else {
1912 PhysicalVec::new(collapsed_borders.block, collapsed_borders.inline)
1913 },
1914 track_sizes: if writing_mode.is_horizontal() {
1915 PhysicalVec::new(track_sizes.inline, track_sizes.block)
1916 } else {
1917 PhysicalVec::new(track_sizes.block, track_sizes.inline)
1918 },
1919 }))
1920 })
1921 }
1922
1923 fn is_row_collapsed(&self, row_index: usize) -> bool {
1924 let Some(row) = &self.table.rows.get(row_index) else {
1925 return false;
1926 };
1927
1928 let row = row.borrow();
1929 if row.base.style.get_inherited_box().visibility == Visibility::Collapse {
1930 return true;
1931 }
1932 let row_group = match row.group_index {
1933 Some(group_index) => self.table.row_groups[group_index].borrow(),
1934 None => return false,
1935 };
1936 row_group.base.style.get_inherited_box().visibility == Visibility::Collapse
1937 }
1938
1939 fn is_column_collapsed(&self, column_index: usize) -> bool {
1940 let Some(column) = &self.table.columns.get(column_index) else {
1941 return false;
1942 };
1943 let column = column.borrow();
1944 if column.base.style.get_inherited_box().visibility == Visibility::Collapse {
1945 return true;
1946 }
1947 let col_group = match column.group_index {
1948 Some(group_index) => self.table.column_groups[group_index].borrow(),
1949 None => return false,
1950 };
1951 col_group.base.style.get_inherited_box().visibility == Visibility::Collapse
1952 }
1953
1954 #[allow(clippy::too_many_arguments)]
1955 fn do_final_cell_layout(
1956 &mut self,
1957 row_index: usize,
1958 column_index: usize,
1959 dimensions: &TableAndTrackDimensions,
1960 baselines: &mut Baselines,
1961 row_fragment_layout: &mut RowFragmentLayout,
1962 row_group_fragment_layout: Option<&mut RowGroupFragmentLayout>,
1963 positioning_context_for_table: &mut PositioningContext,
1964 is_collapsed: bool,
1965 ) {
1966 let row_group_positioning_context =
1969 row_group_fragment_layout.and_then(|layout| layout.positioning_context.as_mut());
1970 let positioning_context = row_fragment_layout
1971 .positioning_context
1972 .as_mut()
1973 .or(row_group_positioning_context)
1974 .unwrap_or(positioning_context_for_table);
1975
1976 let layout = match self.cells_laid_out[row_index][column_index].take() {
1977 Some(layout) => layout,
1978 None => {
1979 return;
1980 },
1981 };
1982 let cell = match self.table.slots[row_index][column_index] {
1983 TableSlot::Cell(ref cell) => cell,
1984 _ => {
1985 warn!("Did not find a non-spanned cell at index with layout.");
1986 return;
1987 },
1988 }
1989 .borrow();
1990
1991 let row_block_offset = row_fragment_layout.rect.start_corner.block;
1993 let row_baseline = self.row_baselines[row_index];
1994 if cell.effective_vertical_align() == VerticalAlignKeyword::Baseline && !layout.is_empty() {
1995 let baseline = row_block_offset + row_baseline;
1996 if row_index == 0 {
1997 baselines.first = Some(baseline);
1998 }
1999 baselines.last = Some(baseline);
2000 }
2001 let mut row_relative_cell_rect = dimensions.get_cell_rect(
2002 TableSlotCoordinates::new(column_index, row_index),
2003 cell.rowspan,
2004 cell.colspan,
2005 );
2006 row_relative_cell_rect.start_corner -= row_fragment_layout.rect.start_corner;
2007 let mut fragment = cell.create_fragment(
2008 layout,
2009 row_relative_cell_rect,
2010 row_baseline,
2011 positioning_context,
2012 &self.table.style,
2013 &row_fragment_layout.containing_block,
2014 is_collapsed,
2015 );
2016
2017 let make_relative_to_row_start = |mut rect: LogicalRect<Au>| {
2026 rect.start_corner -= row_fragment_layout.rect.start_corner;
2027 let writing_mode = self.table.style.writing_mode;
2028 PhysicalRect::new(
2029 if writing_mode.is_horizontal() {
2030 PhysicalPoint::new(rect.start_corner.inline, rect.start_corner.block)
2031 } else {
2032 PhysicalPoint::new(rect.start_corner.block, rect.start_corner.inline)
2033 },
2034 rect.size.to_physical_size(writing_mode),
2035 )
2036 };
2037
2038 let column = self.table.columns.get(column_index);
2039 let column_group = column
2040 .and_then(|column| column.borrow().group_index)
2041 .and_then(|index| self.table.column_groups.get(index));
2042 if let Some(column_group) = column_group {
2043 let column_group = column_group.borrow();
2044 let rect = make_relative_to_row_start(dimensions.get_column_group_rect(&column_group));
2045 fragment.add_extra_background(ExtraBackground {
2046 style: column_group.shared_background_style.clone(),
2047 rect,
2048 })
2049 }
2050 if let Some(column) = column {
2051 let column = column.borrow();
2052 if !column.is_anonymous {
2053 let rect = make_relative_to_row_start(dimensions.get_column_rect(column_index));
2054 fragment.add_extra_background(ExtraBackground {
2055 style: column.shared_background_style.clone(),
2056 rect,
2057 })
2058 }
2059 }
2060 let row = self.table.rows.get(row_index);
2061 let row_group = row
2062 .and_then(|row| row.borrow().group_index)
2063 .and_then(|index| self.table.row_groups.get(index));
2064 if let Some(row_group) = row_group {
2065 let rect =
2066 make_relative_to_row_start(dimensions.get_row_group_rect(&row_group.borrow()));
2067 fragment.add_extra_background(ExtraBackground {
2068 style: row_group.borrow().shared_background_style.clone(),
2069 rect,
2070 })
2071 }
2072 if let Some(row) = row {
2073 let row = row.borrow();
2074 let rect = make_relative_to_row_start(row_fragment_layout.rect);
2075 fragment.add_extra_background(ExtraBackground {
2076 style: row.shared_background_style.clone(),
2077 rect,
2078 })
2079 }
2080
2081 let fragment = Fragment::Box(ArcRefCell::new(fragment));
2082 cell.base.set_fragment(fragment.clone());
2083 row_fragment_layout.fragments.push(fragment);
2084 }
2085
2086 fn make_fragments_for_columns_and_column_groups(
2087 &self,
2088 dimensions: &TableAndTrackDimensions,
2089 fragments: &mut Vec<Fragment>,
2090 ) {
2091 for column_group in self.table.column_groups.iter() {
2092 let column_group = column_group.borrow();
2093 if !column_group.is_empty() {
2094 let fragment = Fragment::Positioning(PositioningFragment::new_empty(
2095 column_group.base.base_fragment_info,
2096 dimensions
2097 .get_column_group_rect(&column_group)
2098 .as_physical(None),
2099 column_group.base.style.clone(),
2100 ));
2101 column_group.base.set_fragment(fragment.clone());
2102 fragments.push(fragment);
2103 }
2104 }
2105
2106 for (column_index, column) in self.table.columns.iter().enumerate() {
2107 let column = column.borrow();
2108 let fragment = Fragment::Positioning(PositioningFragment::new_empty(
2109 column.base.base_fragment_info,
2110 dimensions.get_column_rect(column_index).as_physical(None),
2111 column.base.style.clone(),
2112 ));
2113 column.base.set_fragment(fragment.clone());
2114 fragments.push(fragment);
2115 }
2116 }
2117
2118 fn compute_border_collapse(&mut self, writing_mode: WritingMode) {
2119 if self.table.style.get_inherited_table().border_collapse != BorderCollapse::Collapse {
2120 self.collapsed_borders = None;
2121 return;
2122 }
2123
2124 let mut collapsed_borders = LogicalVec2 {
2125 block: vec![
2126 vec![Default::default(); self.table.size.width];
2127 self.table.size.height + 1
2128 ],
2129 inline: vec![
2130 vec![Default::default(); self.table.size.height];
2131 self.table.size.width + 1
2132 ],
2133 };
2134
2135 let apply_border = |collapsed_borders: &mut CollapsedBorders,
2136 layout_style: &LayoutStyle,
2137 block: &Range<usize>,
2138 inline: &Range<usize>| {
2139 let border = CollapsedBorder::from_layout_style(layout_style, writing_mode);
2140 border
2141 .block_start
2142 .max_assign_to_slice(&mut collapsed_borders.block[block.start][inline.clone()]);
2143 border
2144 .block_end
2145 .max_assign_to_slice(&mut collapsed_borders.block[block.end][inline.clone()]);
2146 border
2147 .inline_start
2148 .max_assign_to_slice(&mut collapsed_borders.inline[inline.start][block.clone()]);
2149 border
2150 .inline_end
2151 .max_assign_to_slice(&mut collapsed_borders.inline[inline.end][block.clone()]);
2152 };
2153 let hide_inner_borders = |collapsed_borders: &mut CollapsedBorders,
2154 block: &Range<usize>,
2155 inline: &Range<usize>| {
2156 for x in inline.clone() {
2157 for y in block.clone() {
2158 if x != inline.start {
2159 collapsed_borders.inline[x][y].hide();
2160 }
2161 if y != block.start {
2162 collapsed_borders.block[y][x].hide();
2163 }
2164 }
2165 }
2166 };
2167 let all_rows = 0..self.table.size.height;
2168 let all_columns = 0..self.table.size.width;
2169 for row_index in all_rows.clone() {
2170 for column_index in all_columns.clone() {
2171 let cell = match self.table.slots[row_index][column_index] {
2172 TableSlot::Cell(ref cell) => cell,
2173 _ => continue,
2174 }
2175 .borrow();
2176 let block_range = row_index..row_index + cell.rowspan;
2177 let inline_range = column_index..column_index + cell.colspan;
2178 hide_inner_borders(&mut collapsed_borders, &block_range, &inline_range);
2179 apply_border(
2180 &mut collapsed_borders,
2181 &cell.layout_style(),
2182 &block_range,
2183 &inline_range,
2184 );
2185 }
2186 }
2187 for (row_index, row) in self.table.rows.iter().enumerate() {
2188 let row = row.borrow();
2189 apply_border(
2190 &mut collapsed_borders,
2191 &row.layout_style(),
2192 &(row_index..row_index + 1),
2193 &all_columns,
2194 );
2195 }
2196 for row_group in &self.table.row_groups {
2197 let row_group = row_group.borrow();
2198 apply_border(
2199 &mut collapsed_borders,
2200 &row_group.layout_style(),
2201 &row_group.track_range,
2202 &all_columns,
2203 );
2204 }
2205 for (column_index, column) in self.table.columns.iter().enumerate() {
2206 let column = column.borrow();
2207 apply_border(
2208 &mut collapsed_borders,
2209 &column.layout_style(),
2210 &all_rows,
2211 &(column_index..column_index + 1),
2212 );
2213 }
2214 for column_group in &self.table.column_groups {
2215 let column_group = column_group.borrow();
2216 apply_border(
2217 &mut collapsed_borders,
2218 &column_group.layout_style(),
2219 &all_rows,
2220 &column_group.track_range,
2221 );
2222 }
2223 apply_border(
2224 &mut collapsed_borders,
2225 &self.table.layout_style_for_grid(),
2226 &all_rows,
2227 &all_columns,
2228 );
2229
2230 self.collapsed_borders = Some(collapsed_borders);
2231 }
2232
2233 fn get_collapsed_border_widths_for_area(
2234 &self,
2235 area: LogicalSides<usize>,
2236 ) -> Option<LogicalSides<Au>> {
2237 let collapsed_borders = self.collapsed_borders.as_ref()?;
2238 let columns = || area.inline_start..area.inline_end;
2239 let rows = || area.block_start..area.block_end;
2240 let max_width = |slice: &[CollapsedBorder]| {
2241 let slice_widths = slice.iter().map(|collapsed_border| collapsed_border.width);
2242 slice_widths.max().unwrap_or_default()
2243 };
2244 Some(area.map_inline_and_block_axes(
2245 |column| max_width(&collapsed_borders.inline[*column][rows()]) / 2,
2246 |row| max_width(&collapsed_borders.block[*row][columns()]) / 2,
2247 ))
2248 }
2249}
2250
2251struct RowFragmentLayout<'a> {
2252 row: &'a TableTrack,
2253 rect: LogicalRect<Au>,
2254 containing_block: ContainingBlock<'a>,
2255 positioning_context: Option<PositioningContext>,
2256 fragments: Vec<Fragment>,
2257}
2258
2259impl<'a> RowFragmentLayout<'a> {
2260 fn new(
2261 table_row: &'a TableTrack,
2262 index: usize,
2263 dimensions: &TableAndTrackDimensions,
2264 table_style: &'a ComputedValues,
2265 ) -> Self {
2266 let rect = dimensions.get_row_rect(index);
2267 let containing_block = ContainingBlock {
2268 size: ContainingBlockSize {
2269 inline: rect.size.inline,
2270 block: SizeConstraint::Definite(rect.size.block),
2271 },
2272 style: table_style,
2273 };
2274 Self {
2275 row: table_row,
2276 rect,
2277 positioning_context: PositioningContext::new_for_layout_box_base(&table_row.base),
2278 containing_block,
2279 fragments: Vec::new(),
2280 }
2281 }
2282 fn finish(
2283 mut self,
2284 layout_context: &LayoutContext,
2285 table_positioning_context: &mut PositioningContext,
2286 containing_block_for_logical_conversion: &ContainingBlock,
2287 containing_block_for_children: &ContainingBlock,
2288 row_group_fragment_layout: &mut Option<RowGroupFragmentLayout>,
2289 ) -> Fragment {
2290 if self.positioning_context.is_some() {
2291 self.rect.start_corner +=
2292 relative_adjustement(&self.row.base.style, containing_block_for_children);
2293 }
2294
2295 let (inline_size, block_size) = if let Some(row_group_layout) = row_group_fragment_layout {
2296 self.rect.start_corner -= row_group_layout.rect.start_corner;
2297 (
2298 row_group_layout.rect.size.inline,
2299 SizeConstraint::Definite(row_group_layout.rect.size.block),
2300 )
2301 } else {
2302 (
2303 containing_block_for_logical_conversion.size.inline,
2304 containing_block_for_logical_conversion.size.block,
2305 )
2306 };
2307
2308 let row_group_containing_block = ContainingBlock {
2309 size: ContainingBlockSize {
2310 inline: inline_size,
2311 block: block_size,
2312 },
2313 style: containing_block_for_logical_conversion.style,
2314 };
2315
2316 let mut row_fragment = BoxFragment::new(
2317 self.row.base.base_fragment_info,
2318 self.row.base.style.clone(),
2319 self.fragments,
2320 self.rect.as_physical(Some(&row_group_containing_block)),
2321 PhysicalSides::zero(), PhysicalSides::zero(), PhysicalSides::zero(), None, );
2326 row_fragment.set_does_not_paint_background();
2327
2328 if let Some(mut row_positioning_context) = self.positioning_context.take() {
2329 row_positioning_context.layout_collected_children(layout_context, &mut row_fragment);
2330
2331 let parent_positioning_context = row_group_fragment_layout
2332 .as_mut()
2333 .and_then(|layout| layout.positioning_context.as_mut())
2334 .unwrap_or(table_positioning_context);
2335 parent_positioning_context.append(row_positioning_context);
2336 }
2337
2338 let fragment = Fragment::Box(ArcRefCell::new(row_fragment));
2339 self.row.base.set_fragment(fragment.clone());
2340 fragment
2341 }
2342}
2343
2344struct RowGroupFragmentLayout {
2345 row_group: ArcRefCell<TableTrackGroup>,
2346 rect: LogicalRect<Au>,
2347 positioning_context: Option<PositioningContext>,
2348 index: usize,
2349 fragments: Vec<Fragment>,
2350}
2351
2352impl RowGroupFragmentLayout {
2353 fn new(
2354 row_group: ArcRefCell<TableTrackGroup>,
2355 index: usize,
2356 dimensions: &TableAndTrackDimensions,
2357 ) -> Self {
2358 let (rect, positioning_context) = {
2359 let row_group = row_group.borrow();
2360 (
2361 dimensions.get_row_group_rect(&row_group),
2362 PositioningContext::new_for_layout_box_base(&row_group.base),
2363 )
2364 };
2365 Self {
2366 row_group,
2367 rect,
2368 positioning_context,
2369 index,
2370 fragments: Vec::new(),
2371 }
2372 }
2373
2374 fn finish(
2375 mut self,
2376 layout_context: &LayoutContext,
2377 table_positioning_context: &mut PositioningContext,
2378 containing_block_for_logical_conversion: &ContainingBlock,
2379 containing_block_for_children: &ContainingBlock,
2380 ) -> Fragment {
2381 let row_group = self.row_group.borrow();
2382 if self.positioning_context.is_some() {
2383 self.rect.start_corner +=
2384 relative_adjustement(&row_group.base.style, containing_block_for_children);
2385 }
2386
2387 let mut row_group_fragment = BoxFragment::new(
2388 row_group.base.base_fragment_info,
2389 row_group.base.style.clone(),
2390 self.fragments,
2391 self.rect
2392 .as_physical(Some(containing_block_for_logical_conversion)),
2393 PhysicalSides::zero(), PhysicalSides::zero(), PhysicalSides::zero(), None, );
2398 row_group_fragment.set_does_not_paint_background();
2399
2400 if let Some(mut row_positioning_context) = self.positioning_context.take() {
2401 row_positioning_context
2402 .layout_collected_children(layout_context, &mut row_group_fragment);
2403 table_positioning_context.append(row_positioning_context);
2404 }
2405
2406 let fragment = Fragment::Box(ArcRefCell::new(row_group_fragment));
2407 row_group.base.set_fragment(fragment.clone());
2408 fragment
2409 }
2410}
2411
2412struct TableAndTrackDimensions {
2413 table_rect: LogicalRect<Au>,
2415 table_cells_rect: LogicalRect<Au>,
2418 row_dimensions: Vec<(Au, Au)>,
2420 column_dimensions: Vec<(Au, Au)>,
2422}
2423
2424impl TableAndTrackDimensions {
2425 fn new(table_layout: &TableLayout) -> Self {
2426 let border_spacing = table_layout.table.border_spacing();
2427
2428 let fallback_inline_size = table_layout.assignable_width;
2430 let fallback_block_size = table_layout.final_table_height;
2431
2432 let mut column_dimensions = Vec::new();
2433 let mut column_offset = Au::zero();
2434 for column_index in 0..table_layout.table.size.width {
2435 if table_layout.is_column_collapsed(column_index) {
2436 column_dimensions.push((column_offset, column_offset));
2437 continue;
2438 }
2439 let start_offset = column_offset + border_spacing.inline;
2440 let end_offset = start_offset + table_layout.distributed_column_widths[column_index];
2441 column_dimensions.push((start_offset, end_offset));
2442 column_offset = end_offset;
2443 }
2444 column_offset += if table_layout.table.size.width == 0 {
2445 fallback_inline_size
2446 } else {
2447 border_spacing.inline
2448 };
2449
2450 let mut row_dimensions = Vec::new();
2451 let mut row_offset = Au::zero();
2452 for row_index in 0..table_layout.table.size.height {
2453 if table_layout.is_row_collapsed(row_index) {
2454 row_dimensions.push((row_offset, row_offset));
2455 continue;
2456 }
2457 let start_offset = row_offset + border_spacing.block;
2458 let end_offset = start_offset + table_layout.row_sizes[row_index];
2459 row_dimensions.push((start_offset, end_offset));
2460 row_offset = end_offset;
2461 }
2462 row_offset += if table_layout.table.size.height == 0 {
2463 fallback_block_size
2464 } else {
2465 border_spacing.block
2466 };
2467
2468 let table_start_corner = LogicalVec2 {
2469 inline: column_dimensions.first().map_or_else(Au::zero, |v| v.0),
2470 block: row_dimensions.first().map_or_else(Au::zero, |v| v.0),
2471 };
2472 let table_size = LogicalVec2 {
2473 inline: column_dimensions
2474 .last()
2475 .map_or(fallback_inline_size, |v| v.1),
2476 block: row_dimensions.last().map_or(fallback_block_size, |v| v.1),
2477 } - table_start_corner;
2478 let table_cells_rect = LogicalRect {
2479 start_corner: table_start_corner,
2480 size: table_size,
2481 };
2482
2483 let table_rect = LogicalRect {
2484 start_corner: LogicalVec2::zero(),
2485 size: LogicalVec2 {
2486 inline: column_offset,
2487 block: row_offset,
2488 },
2489 };
2490
2491 Self {
2492 table_rect,
2493 table_cells_rect,
2494 row_dimensions,
2495 column_dimensions,
2496 }
2497 }
2498
2499 fn get_row_rect(&self, row_index: usize) -> LogicalRect<Au> {
2500 let mut row_rect = self.table_cells_rect;
2501 let row_dimensions = self.row_dimensions[row_index];
2502 row_rect.start_corner.block = row_dimensions.0;
2503 row_rect.size.block = row_dimensions.1 - row_dimensions.0;
2504 row_rect
2505 }
2506
2507 fn get_column_rect(&self, column_index: usize) -> LogicalRect<Au> {
2508 let mut row_rect = self.table_cells_rect;
2509 let column_dimensions = self.column_dimensions[column_index];
2510 row_rect.start_corner.inline = column_dimensions.0;
2511 row_rect.size.inline = column_dimensions.1 - column_dimensions.0;
2512 row_rect
2513 }
2514
2515 fn get_row_group_rect(&self, row_group: &TableTrackGroup) -> LogicalRect<Au> {
2516 if row_group.is_empty() {
2517 return LogicalRect::zero();
2518 }
2519
2520 let mut row_group_rect = self.table_cells_rect;
2521 let block_start = self.row_dimensions[row_group.track_range.start].0;
2522 let block_end = self.row_dimensions[row_group.track_range.end - 1].1;
2523 row_group_rect.start_corner.block = block_start;
2524 row_group_rect.size.block = block_end - block_start;
2525 row_group_rect
2526 }
2527
2528 fn get_column_group_rect(&self, column_group: &TableTrackGroup) -> LogicalRect<Au> {
2529 if column_group.is_empty() {
2530 return LogicalRect::zero();
2531 }
2532
2533 let mut column_group_rect = self.table_cells_rect;
2534 let inline_start = self.column_dimensions[column_group.track_range.start].0;
2535 let inline_end = self.column_dimensions[column_group.track_range.end - 1].1;
2536 column_group_rect.start_corner.inline = inline_start;
2537 column_group_rect.size.inline = inline_end - inline_start;
2538 column_group_rect
2539 }
2540
2541 fn get_cell_rect(
2542 &self,
2543 coordinates: TableSlotCoordinates,
2544 rowspan: usize,
2545 colspan: usize,
2546 ) -> LogicalRect<Au> {
2547 let start_corner = LogicalVec2 {
2548 inline: self.column_dimensions[coordinates.x].0,
2549 block: self.row_dimensions[coordinates.y].0,
2550 };
2551 let size = LogicalVec2 {
2552 inline: self.column_dimensions[coordinates.x + colspan - 1].1,
2553 block: self.row_dimensions[coordinates.y + rowspan - 1].1,
2554 } - start_corner;
2555 LogicalRect { start_corner, size }
2556 }
2557}
2558
2559impl Table {
2560 fn border_spacing(&self) -> LogicalVec2<Au> {
2561 if self.style.clone_border_collapse() == BorderCollapse::Collapse {
2562 LogicalVec2::zero()
2563 } else {
2564 let border_spacing = self.style.clone_border_spacing();
2565 LogicalVec2 {
2566 inline: border_spacing.horizontal(),
2567 block: border_spacing.vertical(),
2568 }
2569 }
2570 }
2571
2572 fn total_border_spacing(&self) -> LogicalVec2<Au> {
2573 let border_spacing = self.border_spacing();
2574 LogicalVec2 {
2575 inline: if self.size.width > 0 {
2576 border_spacing.inline * (self.size.width as i32 + 1)
2577 } else {
2578 Au::zero()
2579 },
2580 block: if self.size.height > 0 {
2581 border_spacing.block * (self.size.height as i32 + 1)
2582 } else {
2583 Au::zero()
2584 },
2585 }
2586 }
2587
2588 fn get_column_measure_for_column_at_index(
2589 &self,
2590 writing_mode: WritingMode,
2591 column_index: usize,
2592 is_in_fixed_mode: bool,
2593 ) -> CellOrTrackMeasure {
2594 let column = match self.columns.get(column_index) {
2595 Some(column) => column,
2596 None => return CellOrTrackMeasure::zero(),
2597 }
2598 .borrow();
2599
2600 let CellOrColumnOuterSizes {
2601 preferred: preferred_size,
2602 min: min_size,
2603 max: max_size,
2604 percentage: percentage_size,
2605 } = CellOrColumnOuterSizes::new(
2606 &column.base.style,
2607 writing_mode,
2608 &Default::default(),
2609 is_in_fixed_mode,
2610 );
2611
2612 CellOrTrackMeasure {
2613 content_sizes: ContentSizes {
2614 min_content: min_size.inline,
2619 max_content: preferred_size
2623 .inline
2624 .clamp_between_extremums(min_size.inline, max_size.inline),
2625 },
2626 percentage: percentage_size.inline,
2627 }
2628 }
2629
2630 fn get_row_measure_for_row_at_index(
2631 &self,
2632 writing_mode: WritingMode,
2633 row_index: usize,
2634 ) -> CellOrTrackMeasure {
2635 let row = match self.rows.get(row_index) {
2636 Some(row) => row,
2637 None => return CellOrTrackMeasure::zero(),
2638 };
2639
2640 let row = row.borrow();
2644 let size = row.base.style.box_size(writing_mode);
2645 let max_size = row.base.style.max_box_size(writing_mode);
2646 let percentage_contribution = get_size_percentage_contribution(&size, &max_size);
2647
2648 CellOrTrackMeasure {
2649 content_sizes: size
2650 .block
2651 .to_numeric()
2652 .and_then(|size| size.to_length())
2653 .map_or_else(Au::zero, Au::from)
2654 .into(),
2655 percentage: percentage_contribution.block,
2656 }
2657 }
2658
2659 pub(crate) fn layout(
2660 &self,
2661 layout_context: &LayoutContext,
2662 positioning_context: &mut PositioningContext,
2663 containing_block_for_children: &ContainingBlock,
2664 containing_block_for_table: &ContainingBlock,
2665 ) -> CacheableLayoutResult {
2666 TableLayout::new(self).layout(
2667 layout_context,
2668 positioning_context,
2669 containing_block_for_children,
2670 containing_block_for_table,
2671 )
2672 }
2673
2674 pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
2675 for caption in &self.captions {
2676 caption
2677 .borrow_mut()
2678 .context
2679 .base
2680 .parent_box
2681 .replace(layout_box.clone());
2682 }
2683 for row_group in &self.row_groups {
2684 row_group
2685 .borrow_mut()
2686 .base
2687 .parent_box
2688 .replace(layout_box.clone());
2689 }
2690 for column_group in &self.column_groups {
2691 column_group
2692 .borrow_mut()
2693 .base
2694 .parent_box
2695 .replace(layout_box.clone());
2696 }
2697 for row in &self.rows {
2698 let row = &mut *row.borrow_mut();
2699 if let Some(group_index) = row.group_index {
2700 row.base.parent_box.replace(WeakLayoutBox::TableLevelBox(
2701 WeakTableLevelBox::TrackGroup(self.row_groups[group_index].downgrade()),
2702 ));
2703 } else {
2704 row.base.parent_box.replace(layout_box.clone());
2705 }
2706 }
2707 for column in &self.columns {
2708 let column = &mut *column.borrow_mut();
2709 if let Some(group_index) = column.group_index {
2710 column.base.parent_box.replace(WeakLayoutBox::TableLevelBox(
2711 WeakTableLevelBox::TrackGroup(self.column_groups[group_index].downgrade()),
2712 ));
2713 } else {
2714 column.base.parent_box.replace(layout_box.clone());
2715 }
2716 }
2717 for row_index in 0..self.size.height {
2718 let row = WeakLayoutBox::TableLevelBox(WeakTableLevelBox::Track(
2719 self.rows[row_index].downgrade(),
2720 ));
2721 for column_index in 0..self.size.width {
2722 if let TableSlot::Cell(ref cell) = self.slots[row_index][column_index] {
2723 cell.borrow_mut().base.parent_box.replace(row.clone());
2724 }
2725 }
2726 }
2727 }
2728}
2729
2730impl ComputeInlineContentSizes for Table {
2731 #[servo_tracing::instrument(name = "Table::compute_inline_content_sizes", skip_all)]
2732 fn compute_inline_content_sizes(
2733 &self,
2734 layout_context: &LayoutContext,
2735 constraint_space: &ConstraintSpace,
2736 ) -> InlineContentSizesResult {
2737 let writing_mode = constraint_space.style.writing_mode;
2738 let mut layout = TableLayout::new(self);
2739 layout.compute_border_collapse(writing_mode);
2740 layout.pbm = self
2741 .layout_style(Some(&layout))
2742 .padding_border_margin_with_writing_mode_and_containing_block_inline_size(
2743 writing_mode,
2744 Au::zero(),
2745 );
2746 layout.compute_measures(layout_context, writing_mode);
2747
2748 let grid_content_sizes = layout.compute_grid_min_max();
2749
2750 let caption_content_sizes = ContentSizes::from(
2754 layout.compute_caption_minimum_inline_size(layout_context) -
2755 layout.pbm.padding_border_sums.inline,
2756 );
2757
2758 InlineContentSizesResult {
2759 sizes: grid_content_sizes.max(caption_content_sizes),
2760 depends_on_block_constraints: false,
2761 }
2762 }
2763}
2764
2765impl Table {
2766 #[inline]
2767 pub(crate) fn layout_style<'a>(
2768 &'a self,
2769 layout: Option<&'a TableLayout<'a>>,
2770 ) -> LayoutStyle<'a> {
2771 LayoutStyle::Table(TableLayoutStyle {
2772 table: self,
2773 layout,
2774 })
2775 }
2776
2777 #[inline]
2778 pub(crate) fn layout_style_for_grid(&self) -> LayoutStyle<'_> {
2779 LayoutStyle::Default(&self.grid_style)
2780 }
2781}
2782
2783impl TableTrack {
2784 #[inline]
2785 pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
2786 LayoutStyle::Default(&self.base.style)
2787 }
2788}
2789
2790impl TableTrackGroup {
2791 #[inline]
2792 pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
2793 LayoutStyle::Default(&self.base.style)
2794 }
2795}
2796
2797impl TableLayoutStyle<'_> {
2798 #[inline]
2799 pub(crate) fn style(&self) -> &ComputedValues {
2800 &self.table.style
2801 }
2802
2803 #[inline]
2804 pub(crate) fn collapses_borders(&self) -> bool {
2805 self.style().get_inherited_table().border_collapse == BorderCollapse::Collapse
2806 }
2807
2808 pub(crate) fn halved_collapsed_border_widths(&self) -> LogicalSides<Au> {
2809 debug_assert!(self.collapses_borders());
2810 let area = LogicalSides {
2811 inline_start: 0,
2812 inline_end: self.table.size.width,
2813 block_start: 0,
2814 block_end: self.table.size.height,
2815 };
2816 if let Some(layout) = self.layout {
2817 layout.get_collapsed_border_widths_for_area(area)
2818 } else {
2819 let mut layout = TableLayout::new(self.table);
2821 layout.compute_border_collapse(self.style().writing_mode);
2822 layout.get_collapsed_border_widths_for_area(area)
2823 }
2824 .expect("Collapsed borders should be computed")
2825 }
2826}
2827
2828impl TableSlotCell {
2829 #[inline]
2830 fn layout_style(&self) -> LayoutStyle<'_> {
2831 self.contents.layout_style(&self.base)
2832 }
2833
2834 fn effective_vertical_align(&self) -> VerticalAlignKeyword {
2835 match self.base.style.clone_vertical_align() {
2836 VerticalAlign::Keyword(VerticalAlignKeyword::Top) => VerticalAlignKeyword::Top,
2837 VerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => VerticalAlignKeyword::Bottom,
2838 VerticalAlign::Keyword(VerticalAlignKeyword::Middle) => VerticalAlignKeyword::Middle,
2839 _ => VerticalAlignKeyword::Baseline,
2840 }
2841 }
2842
2843 fn inline_content_sizes(&self, layout_context: &LayoutContext) -> ContentSizes {
2844 let constraint_space = ConstraintSpace::new_for_style_and_ratio(
2845 &self.base.style,
2846 None, );
2848 self.base
2849 .inline_content_sizes(layout_context, &constraint_space, &self.contents.contents)
2850 .sizes
2851 }
2852
2853 #[allow(clippy::too_many_arguments)]
2854 fn create_fragment(
2855 &self,
2856 mut layout: CellLayout,
2857 cell_rect: LogicalRect<Au>,
2858 cell_baseline: Au,
2859 positioning_context: &mut PositioningContext,
2860 table_style: &ComputedValues,
2861 containing_block: &ContainingBlock,
2862 is_collapsed: bool,
2863 ) -> BoxFragment {
2864 use style::Zero as StyleZero;
2866
2867 let cell_content_rect = cell_rect.deflate(&(layout.padding + layout.border));
2868 let content_block_size = layout.layout.content_block_size;
2869 let free_space = || Au::zero().max(cell_content_rect.size.block - content_block_size);
2870 let vertical_align_offset = match self.effective_vertical_align() {
2871 VerticalAlignKeyword::Top => Au::zero(),
2872 VerticalAlignKeyword::Bottom => free_space(),
2873 VerticalAlignKeyword::Middle => free_space().scale_by(0.5),
2874 _ => {
2875 cell_baseline -
2876 (layout.padding.block_start + layout.border.block_start) -
2877 layout.ascent()
2878 },
2879 };
2880
2881 let mut base_fragment_info = self.base.base_fragment_info;
2882 if self.base.style.get_inherited_table().empty_cells == EmptyCells::Hide &&
2883 table_style.get_inherited_table().border_collapse != BorderCollapse::Collapse &&
2884 layout.is_empty_for_empty_cells()
2885 {
2886 base_fragment_info.flags.insert(FragmentFlags::DO_NOT_PAINT);
2887 }
2888
2889 if is_collapsed {
2890 base_fragment_info.flags.insert(FragmentFlags::IS_COLLAPSED);
2891 }
2892
2893 let mut vertical_align_fragment_rect = cell_content_rect;
2895 vertical_align_fragment_rect.start_corner = LogicalVec2 {
2896 inline: Au::zero(),
2897 block: vertical_align_offset,
2898 };
2899 let vertical_align_fragment = PositioningFragment::new_anonymous(
2900 self.base.style.clone(),
2901 vertical_align_fragment_rect.as_physical(None),
2902 layout.layout.fragments,
2903 );
2904
2905 let physical_cell_rect = cell_content_rect.as_physical(Some(containing_block));
2913 layout
2914 .positioning_context
2915 .adjust_static_position_of_hoisted_fragments_with_offset(
2916 &physical_cell_rect.origin.to_vector(),
2917 PositioningContextLength::zero(),
2918 );
2919 positioning_context.append(layout.positioning_context);
2920
2921 let specific_layout_info = (table_style.get_inherited_table().border_collapse ==
2922 BorderCollapse::Collapse)
2923 .then_some(SpecificLayoutInfo::TableCellWithCollapsedBorders);
2924
2925 BoxFragment::new(
2926 base_fragment_info,
2927 self.base.style.clone(),
2928 vec![Fragment::Positioning(vertical_align_fragment)],
2929 physical_cell_rect,
2930 layout.padding.to_physical(table_style.writing_mode),
2931 layout.border.to_physical(table_style.writing_mode),
2932 PhysicalSides::zero(), specific_layout_info,
2934 )
2935 .with_baselines(layout.layout.baselines)
2936 }
2937}
2938
2939fn get_size_percentage_contribution(
2940 size: &LogicalVec2<Size<ComputedLengthPercentage>>,
2941 max_size: &LogicalVec2<Size<ComputedLengthPercentage>>,
2942) -> LogicalVec2<Option<Percentage>> {
2943 LogicalVec2 {
2951 inline: max_two_optional_percentages(
2952 size.inline.to_percentage(),
2953 max_size.inline.to_percentage(),
2954 ),
2955 block: max_two_optional_percentages(
2956 size.block.to_percentage(),
2957 max_size.block.to_percentage(),
2958 ),
2959 }
2960}
2961
2962struct CellOrColumnOuterSizes {
2963 min: LogicalVec2<Au>,
2964 preferred: LogicalVec2<Au>,
2965 max: LogicalVec2<Option<Au>>,
2966 percentage: LogicalVec2<Option<Percentage>>,
2967}
2968
2969impl CellOrColumnOuterSizes {
2970 fn new(
2971 style: &Arc<ComputedValues>,
2972 writing_mode: WritingMode,
2973 padding_border_sums: &LogicalVec2<Au>,
2974 is_in_fixed_mode: bool,
2975 ) -> Self {
2976 let box_sizing = style.get_position().box_sizing;
2977 let outer_size = |size: LogicalVec2<Au>| match box_sizing {
2978 BoxSizing::ContentBox => size + *padding_border_sums,
2979 BoxSizing::BorderBox => LogicalVec2 {
2980 inline: size.inline.max(padding_border_sums.inline),
2981 block: size.block.max(padding_border_sums.block),
2982 },
2983 };
2984
2985 let outer_option_size = |size: LogicalVec2<Option<Au>>| match box_sizing {
2986 BoxSizing::ContentBox => size.map_inline_and_block_axes(
2987 |inline| inline.map(|inline| inline + padding_border_sums.inline),
2988 |block| block.map(|block| block + padding_border_sums.block),
2989 ),
2990 BoxSizing::BorderBox => size.map_inline_and_block_axes(
2991 |inline| inline.map(|inline| inline.max(padding_border_sums.inline)),
2992 |block| block.map(|block| block.max(padding_border_sums.block)),
2993 ),
2994 };
2995
2996 let get_size_for_axis = |size: &Size<ComputedLengthPercentage>| {
2997 size.to_numeric()
3000 .and_then(|length_percentage| length_percentage.to_length())
3001 .map(Au::from)
3002 };
3003
3004 let size = style.box_size(writing_mode);
3005 if is_in_fixed_mode {
3006 return Self {
3007 percentage: size.map(|v| v.to_percentage()),
3008 preferred: outer_option_size(size.map(get_size_for_axis))
3009 .map(|v| v.unwrap_or_default()),
3010 min: LogicalVec2::default(),
3011 max: LogicalVec2::default(),
3012 };
3013 }
3014
3015 let min_size = style.min_box_size(writing_mode);
3016 let max_size = style.max_box_size(writing_mode);
3017
3018 Self {
3019 min: outer_size(min_size.map(|v| get_size_for_axis(v).unwrap_or_default())),
3020 preferred: outer_size(size.map(|v| get_size_for_axis(v).unwrap_or_default())),
3021 max: outer_option_size(max_size.map(get_size_for_axis)),
3022 percentage: get_size_percentage_contribution(&size, &max_size),
3023 }
3024 }
3025}
3026
3027struct RowspanToDistribute<'a> {
3028 coordinates: TableSlotCoordinates,
3029 cell: AtomicRef<'a, TableSlotCell>,
3030 measure: &'a CellOrTrackMeasure,
3031}
3032
3033impl RowspanToDistribute<'_> {
3034 fn range(&self) -> Range<usize> {
3035 self.coordinates.y..self.coordinates.y + self.cell.rowspan
3036 }
3037
3038 fn fully_encloses(&self, other: &RowspanToDistribute) -> bool {
3039 other.coordinates.y > self.coordinates.y && other.range().end < self.range().end
3040 }
3041}
3042
3043#[derive(Debug)]
3046struct ColspanToDistribute {
3047 starting_column: usize,
3048 span: usize,
3049 content_sizes: ContentSizes,
3050 percentage: Option<Percentage>,
3051}
3052
3053impl ColspanToDistribute {
3054 fn comparison_for_sort(a: &Self, b: &Self) -> Ordering {
3058 a.span
3059 .cmp(&b.span)
3060 .then_with(|| a.starting_column.cmp(&b.starting_column))
3061 }
3062
3063 fn range(&self) -> Range<usize> {
3064 self.starting_column..self.starting_column + self.span
3065 }
3066}
3067
3068#[cfg(test)]
3069mod test {
3070 use app_units::MIN_AU;
3071
3072 use super::*;
3073 use crate::sizing::ContentSizes;
3074
3075 #[test]
3076 fn test_colspan_to_distribute_first_sort_by_span() {
3077 let a = ColspanToDistribute {
3078 starting_column: 0,
3079 span: 0,
3080 content_sizes: ContentSizes {
3081 min_content: MIN_AU,
3082 max_content: MIN_AU,
3083 },
3084 percentage: None,
3085 };
3086
3087 let b = ColspanToDistribute {
3088 starting_column: 0,
3089 span: 1,
3090 content_sizes: ContentSizes {
3091 min_content: MIN_AU,
3092 max_content: MIN_AU,
3093 },
3094 percentage: None,
3095 };
3096
3097 let ordering = ColspanToDistribute::comparison_for_sort(&a, &b);
3098 assert_eq!(ordering, Ordering::Less);
3099 }
3100
3101 #[test]
3102 fn test_colspan_to_distribute_if_spans_are_equal_sort_by_starting_column() {
3103 let a = ColspanToDistribute {
3104 starting_column: 0,
3105 span: 0,
3106 content_sizes: ContentSizes {
3107 min_content: MIN_AU,
3108 max_content: MIN_AU,
3109 },
3110 percentage: None,
3111 };
3112
3113 let b = ColspanToDistribute {
3114 starting_column: 1,
3115 span: 0,
3116 content_sizes: ContentSizes {
3117 min_content: MIN_AU,
3118 max_content: MIN_AU,
3119 },
3120 percentage: None,
3121 };
3122
3123 let ordering = ColspanToDistribute::comparison_for_sort(&a, &b);
3124 assert_eq!(ordering, Ordering::Less);
3125 }
3126}