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