use core::cmp::Ordering;
use std::mem;
use std::ops::Range;
use app_units::Au;
use log::warn;
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
use servo_arc::Arc;
use style::Zero;
use style::computed_values::border_collapse::T as BorderCollapse;
use style::computed_values::box_sizing::T as BoxSizing;
use style::computed_values::caption_side::T as CaptionSide;
use style::computed_values::empty_cells::T as EmptyCells;
use style::computed_values::position::T as Position;
use style::computed_values::table_layout::T as TableLayoutMode;
use style::computed_values::visibility::T as Visibility;
use style::properties::ComputedValues;
use style::values::computed::{
BorderStyle, LengthPercentage as ComputedLengthPercentage, Percentage,
};
use style::values::generics::box_::{GenericVerticalAlign as VerticalAlign, VerticalAlignKeyword};
use super::{
ArcRefCell, CollapsedBorder, CollapsedBorderLine, SpecificTableGridInfo, Table, TableCaption,
TableLayoutStyle, TableSlot, TableSlotCell, TableSlotCoordinates, TableTrack, TableTrackGroup,
};
use crate::context::LayoutContext;
use crate::formatting_contexts::{Baselines, IndependentLayout};
use crate::fragment_tree::{
BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, ExtraBackground, Fragment, FragmentFlags,
PositioningFragment, SpecificLayoutInfo,
};
use crate::geom::{
LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, PhysicalRect,
PhysicalSides, PhysicalVec, Size, SizeConstraint, ToLogical, ToLogicalWithContainingBlock,
};
use crate::positioned::{PositioningContext, PositioningContextLength, relative_adjustement};
use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult};
use crate::style_ext::{
BorderStyleColor, Clamp, ComputedValuesExt, LayoutStyle, PaddingBorderMargin,
};
use crate::{
ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock, WritingMode,
};
struct CellLayout {
layout: IndependentLayout,
padding: LogicalSides<Au>,
border: LogicalSides<Au>,
positioning_context: PositioningContext,
}
impl CellLayout {
fn ascent(&self) -> Au {
self.layout
.baselines
.first
.unwrap_or(self.layout.content_block_size)
}
fn outer_block_size(&self) -> Au {
self.layout.content_block_size + self.border.block_sum() + self.padding.block_sum()
}
fn is_empty(&self) -> bool {
self.layout.fragments.is_empty()
}
fn is_empty_for_empty_cells(&self) -> bool {
self.layout
.fragments
.iter()
.all(|fragment| matches!(fragment, Fragment::AbsoluteOrFixedPositioned(_)))
}
}
#[derive(Clone, Debug, Default)]
struct RowLayout {
constrained: bool,
has_cell_with_span_greater_than_one: bool,
percent: Percentage,
}
#[derive(Clone, Debug, Default)]
struct ColumnLayout {
constrained: bool,
has_originating_cells: bool,
content_sizes: ContentSizes,
percentage: Option<Percentage>,
}
fn max_two_optional_percentages(
a: Option<Percentage>,
b: Option<Percentage>,
) -> Option<Percentage> {
match (a, b) {
(Some(a), Some(b)) => Some(Percentage(a.0.max(b.0))),
_ => a.or(b),
}
}
impl ColumnLayout {
fn incorporate_cell_measure(&mut self, cell_measure: &CellOrTrackMeasure) {
self.content_sizes.max_assign(cell_measure.content_sizes);
self.percentage = max_two_optional_percentages(self.percentage, cell_measure.percentage);
}
}
impl CollapsedBorder {
fn new(style_color: BorderStyleColor, width: Au) -> Self {
Self { style_color, width }
}
fn from_layout_style(
layout_style: &LayoutStyle,
writing_mode: WritingMode,
) -> LogicalSides<Self> {
let border_style_color = layout_style.style().border_style_color(writing_mode);
let border_width = layout_style.border_width(writing_mode);
LogicalSides {
inline_start: Self::new(border_style_color.inline_start, border_width.inline_start),
inline_end: Self::new(border_style_color.inline_end, border_width.inline_end),
block_start: Self::new(border_style_color.block_start, border_width.block_start),
block_end: Self::new(border_style_color.block_end, border_width.block_end),
}
}
fn max_assign(&mut self, other: &Self) {
if *self < *other {
*self = other.clone();
}
}
fn max_assign_to_slice(&self, slice: &mut [CollapsedBorder]) {
for collapsed_border in slice {
collapsed_border.max_assign(self)
}
}
fn hide(&mut self) {
self.style_color = BorderStyleColor::hidden();
self.width = Au::zero();
}
}
impl PartialOrd for CollapsedBorder {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let is_hidden = |border: &Self| border.style_color.style == BorderStyle::Hidden;
let style_specificity = |border: &Self| match border.style_color.style {
BorderStyle::None => 0,
BorderStyle::Inset => 1,
BorderStyle::Groove => 2,
BorderStyle::Outset => 3,
BorderStyle::Ridge => 4,
BorderStyle::Dotted => 5,
BorderStyle::Dashed => 6,
BorderStyle::Solid => 7,
BorderStyle::Double => 8,
BorderStyle::Hidden => 9,
};
let candidate = (is_hidden(self).cmp(&is_hidden(other)))
.then_with(|| self.width.cmp(&other.width))
.then_with(|| style_specificity(self).cmp(&style_specificity(other)));
if !candidate.is_eq() || self.style_color.color == other.style_color.color {
Some(candidate)
} else {
None
}
}
}
impl Eq for CollapsedBorder {}
type CollapsedBorders = LogicalVec2<Vec<CollapsedBorderLine>>;
pub(crate) struct TableLayout<'a> {
table: &'a Table,
pbm: PaddingBorderMargin,
rows: Vec<RowLayout>,
columns: Vec<ColumnLayout>,
cell_measures: Vec<Vec<LogicalVec2<CellOrTrackMeasure>>>,
table_width: Au,
assignable_width: Au,
final_table_height: Au,
distributed_column_widths: Vec<Au>,
row_sizes: Vec<Au>,
row_baselines: Vec<Au>,
cells_laid_out: Vec<Vec<Option<CellLayout>>>,
basis_for_cell_padding_percentage: Au,
collapsed_borders: Option<CollapsedBorders>,
is_in_fixed_mode: bool,
}
#[derive(Clone, Debug)]
struct CellOrTrackMeasure {
content_sizes: ContentSizes,
percentage: Option<Percentage>,
}
impl Zero for CellOrTrackMeasure {
fn zero() -> Self {
Self {
content_sizes: ContentSizes::zero(),
percentage: None,
}
}
fn is_zero(&self) -> bool {
self.content_sizes.is_zero() && self.percentage.is_none()
}
}
impl<'a> TableLayout<'a> {
fn new(table: &'a Table) -> TableLayout<'a> {
let style = &table.style;
let is_in_fixed_mode = style.get_table().table_layout == TableLayoutMode::Fixed &&
!style.box_size(style.writing_mode).inline.is_initial();
Self {
table,
pbm: PaddingBorderMargin::zero(),
rows: Vec::new(),
columns: Vec::new(),
cell_measures: Vec::new(),
table_width: Au::zero(),
assignable_width: Au::zero(),
final_table_height: Au::zero(),
distributed_column_widths: Vec::new(),
row_sizes: Vec::new(),
row_baselines: Vec::new(),
cells_laid_out: Vec::new(),
basis_for_cell_padding_percentage: Au::zero(),
collapsed_borders: None,
is_in_fixed_mode,
}
}
pub(crate) fn compute_cell_measures(
&mut self,
layout_context: &LayoutContext,
writing_mode: WritingMode,
) {
let row_measures = vec![LogicalVec2::zero(); self.table.size.width];
self.cell_measures = vec![row_measures; self.table.size.height];
for row_index in 0..self.table.size.height {
for column_index in 0..self.table.size.width {
let cell = match self.table.slots[row_index][column_index] {
TableSlot::Cell(ref cell) => cell,
_ => continue,
};
let layout_style = cell.layout_style();
let padding = layout_style
.padding(writing_mode)
.percentages_relative_to(Au::zero());
let border = self
.get_collapsed_border_widths_for_area(LogicalSides {
inline_start: column_index,
inline_end: column_index + cell.colspan,
block_start: row_index,
block_end: row_index + cell.rowspan,
})
.unwrap_or_else(|| layout_style.border_width(writing_mode));
let padding_border_sums = LogicalVec2 {
inline: padding.inline_sum() + border.inline_sum(),
block: padding.block_sum() + border.block_sum(),
};
let CellOrColumnOuterSizes {
preferred: preferred_size,
min: min_size,
max: max_size,
percentage: percentage_size,
} = CellOrColumnOuterSizes::new(
&cell.base.style,
writing_mode,
&padding_border_sums,
self.is_in_fixed_mode,
);
let inline_measure = if self.is_in_fixed_mode {
if row_index > 0 {
CellOrTrackMeasure::zero()
} else {
CellOrTrackMeasure {
content_sizes: preferred_size.inline.into(),
percentage: percentage_size.inline,
}
}
} else {
let inline_content_sizes = cell.inline_content_sizes(layout_context) +
padding_border_sums.inline.into();
assert!(
inline_content_sizes.max_content >= inline_content_sizes.min_content,
"the max-content size should never be smaller than the min-content size"
);
let outer_min_content_width = inline_content_sizes
.min_content
.clamp_between_extremums(min_size.inline, max_size.inline);
let outer_max_content_width = if self.columns[column_index].constrained {
inline_content_sizes
.min_content
.max(preferred_size.inline)
.clamp_between_extremums(min_size.inline, max_size.inline)
} else {
inline_content_sizes
.max_content
.max(preferred_size.inline)
.clamp_between_extremums(min_size.inline, max_size.inline)
};
assert!(outer_min_content_width <= outer_max_content_width);
CellOrTrackMeasure {
content_sizes: ContentSizes {
min_content: outer_min_content_width,
max_content: outer_max_content_width,
},
percentage: percentage_size.inline,
}
};
let block_measure = CellOrTrackMeasure {
content_sizes: preferred_size.block.into(),
percentage: percentage_size.block,
};
self.cell_measures[row_index][column_index] = LogicalVec2 {
inline: inline_measure,
block: block_measure,
};
}
}
}
fn compute_track_constrainedness_and_has_originating_cells(
&mut self,
writing_mode: WritingMode,
) {
self.rows = vec![RowLayout::default(); self.table.size.height];
self.columns = vec![ColumnLayout::default(); self.table.size.width];
let is_length = |size: &Size<ComputedLengthPercentage>| {
size.to_numeric().is_some_and(|size| !size.has_percentage())
};
for column_index in 0..self.table.size.width {
if let Some(column) = self.table.columns.get(column_index) {
if is_length(&column.style.box_size(writing_mode).inline) {
self.columns[column_index].constrained = true;
continue;
}
if let Some(column_group_index) = column.group_index {
let column_group = &self.table.column_groups[column_group_index];
if is_length(&column_group.style.box_size(writing_mode).inline) {
self.columns[column_index].constrained = true;
continue;
}
}
}
}
for row_index in 0..self.table.size.height {
if let Some(row) = self.table.rows.get(row_index) {
if is_length(&row.style.box_size(writing_mode).block) {
self.rows[row_index].constrained = true;
continue;
}
if let Some(row_group_index) = row.group_index {
let row_group = &self.table.row_groups[row_group_index];
if is_length(&row_group.style.box_size(writing_mode).block) {
self.rows[row_index].constrained = true;
continue;
}
}
}
}
for column_index in 0..self.table.size.width {
for row_index in 0..self.table.size.height {
let coords = TableSlotCoordinates::new(column_index, row_index);
let cell_constrained = match self.table.resolve_first_cell(coords) {
Some(cell) if cell.colspan == 1 => {
cell.base.style.box_size(writing_mode).map(is_length)
},
_ => LogicalVec2::default(),
};
let rowspan_greater_than_1 = match self.table.slots[row_index][column_index] {
TableSlot::Cell(ref cell) => cell.rowspan > 1,
_ => false,
};
self.rows[row_index].has_cell_with_span_greater_than_one |= rowspan_greater_than_1;
self.rows[row_index].constrained |= cell_constrained.block;
let has_originating_cell =
matches!(self.table.get_slot(coords), Some(TableSlot::Cell(_)));
self.columns[column_index].has_originating_cells |= has_originating_cell;
self.columns[column_index].constrained |= cell_constrained.inline;
}
}
}
fn compute_column_measures(&mut self, writing_mode: WritingMode) {
let mut colspan_cell_constraints = Vec::new();
for column_index in 0..self.table.size.width {
let column = &mut self.columns[column_index];
let column_measure = self.table.get_column_measure_for_column_at_index(
writing_mode,
column_index,
self.is_in_fixed_mode,
);
column.content_sizes = column_measure.content_sizes;
column.percentage = column_measure.percentage;
for row_index in 0..self.table.size.height {
let coords = TableSlotCoordinates::new(column_index, row_index);
let cell_measure = &self.cell_measures[row_index][column_index].inline;
let cell = match self.table.get_slot(coords) {
Some(TableSlot::Cell(cell)) => cell,
_ => continue,
};
if cell.colspan != 1 {
colspan_cell_constraints.push(ColspanToDistribute {
starting_column: column_index,
span: cell.colspan,
content_sizes: cell_measure.content_sizes,
percentage: cell_measure.percentage,
});
continue;
}
column.incorporate_cell_measure(cell_measure);
}
}
colspan_cell_constraints.sort_by(ColspanToDistribute::comparison_for_sort);
self.distribute_colspanned_cells_to_columns(colspan_cell_constraints);
let mut total_intrinsic_percentage_width = 0.;
for column in self.columns.iter_mut() {
if let Some(ref mut percentage) = column.percentage {
let final_intrinsic_percentage_width =
percentage.0.min(1. - total_intrinsic_percentage_width);
total_intrinsic_percentage_width += final_intrinsic_percentage_width;
*percentage = Percentage(final_intrinsic_percentage_width);
}
}
}
fn distribute_colspanned_cells_to_columns(
&mut self,
colspan_cell_constraints: Vec<ColspanToDistribute>,
) {
for colspan_cell_constraints in colspan_cell_constraints {
self.distribute_colspanned_cell_to_columns(colspan_cell_constraints);
}
}
fn distribute_colspanned_cell_to_columns(
&mut self,
colspan_cell_constraints: ColspanToDistribute,
) {
let border_spacing = self.table.border_spacing().inline;
let column_range = colspan_cell_constraints.range();
let column_count = column_range.len();
let total_border_spacing =
border_spacing.scale_by((colspan_cell_constraints.span - 1) as f32);
let mut percent_columns_count = 0;
let mut columns_percent_sum = 0.;
let mut columns_non_percent_max_inline_size_sum = Au::zero();
for column in self.columns[column_range.clone()].iter() {
if let Some(percentage) = column.percentage {
percent_columns_count += 1;
columns_percent_sum += percentage.0;
} else {
columns_non_percent_max_inline_size_sum += column.content_sizes.max_content;
}
}
let colspan_percentage = colspan_cell_constraints.percentage.unwrap_or_default();
let surplus_percent = colspan_percentage.0 - columns_percent_sum;
if surplus_percent > 0. && column_count > percent_columns_count {
for column in self.columns[column_range.clone()].iter_mut() {
if column.percentage.is_some() {
continue;
}
let ratio = if columns_non_percent_max_inline_size_sum.is_zero() {
1. / ((column_count - percent_columns_count) as f32)
} else {
column.content_sizes.max_content.to_f32_px() /
columns_non_percent_max_inline_size_sum.to_f32_px()
};
column.percentage = Some(Percentage(surplus_percent * ratio));
}
}
let colspan_cell_min_size = (colspan_cell_constraints.content_sizes.min_content -
total_border_spacing)
.max(Au::zero());
let distributed_minimum = Self::distribute_width_to_columns(
colspan_cell_min_size,
&self.columns[column_range.clone()],
);
{
let column_span = &mut self.columns[colspan_cell_constraints.range()];
for (column, minimum_size) in column_span.iter_mut().zip(distributed_minimum) {
column.content_sizes.min_content.max_assign(minimum_size);
}
}
let colspan_cell_max_size = (colspan_cell_constraints.content_sizes.max_content -
total_border_spacing)
.max(Au::zero());
let distributed_maximum = Self::distribute_width_to_columns(
colspan_cell_max_size,
&self.columns[colspan_cell_constraints.range()],
);
{
let column_span = &mut self.columns[colspan_cell_constraints.range()];
for (column, maximum_size) in column_span.iter_mut().zip(distributed_maximum) {
column
.content_sizes
.max_content
.max_assign(maximum_size.max(column.content_sizes.min_content));
}
}
}
fn compute_measures(&mut self, layout_context: &LayoutContext, writing_mode: WritingMode) {
self.compute_track_constrainedness_and_has_originating_cells(writing_mode);
self.compute_cell_measures(layout_context, writing_mode);
self.compute_column_measures(writing_mode);
}
fn compute_grid_min_max(&self) -> ContentSizes {
let mut largest_percentage_column_max_size = Au::zero();
let mut percent_sum = 0.;
let mut non_percent_columns_max_sum = Au::zero();
let mut grid_min_max = ContentSizes::zero();
for column in self.columns.iter() {
match column.percentage {
Some(percentage) if !percentage.is_zero() => {
largest_percentage_column_max_size.max_assign(
column
.content_sizes
.max_content
.scale_by(1.0 / percentage.0),
);
percent_sum += percentage.0;
},
_ => {
non_percent_columns_max_sum += column.content_sizes.max_content;
},
}
grid_min_max += column.content_sizes;
}
grid_min_max
.max_content
.max_assign(largest_percentage_column_max_size);
if !percent_sum.is_zero() &&
self.table
.percentage_columns_allowed_for_inline_content_sizes
{
let total_inline_size =
non_percent_columns_max_sum.scale_by(1.0 / (1.0 - percent_sum.min(1.0)));
grid_min_max.max_content.max_assign(total_inline_size);
}
assert!(
grid_min_max.min_content <= grid_min_max.max_content,
"GRIDMAX should never be smaller than GRIDMIN {:?}",
grid_min_max
);
let inline_border_spacing = self.table.total_border_spacing().inline;
grid_min_max.min_content += inline_border_spacing;
grid_min_max.max_content += inline_border_spacing;
grid_min_max
}
fn compute_caption_minimum_inline_size(&self, layout_context: &LayoutContext) -> Au {
let containing_block = IndefiniteContainingBlock {
size: LogicalVec2::default(),
writing_mode: self.table.style.writing_mode,
};
self.table
.captions
.iter()
.map(|caption| {
let context = caption.context.borrow();
context
.outer_inline_content_sizes(
layout_context,
&containing_block,
&LogicalVec2::zero(),
false, )
.sizes
.min_content
})
.max()
.unwrap_or_default()
}
fn compute_table_width(&mut self, containing_block_for_children: &ContainingBlock) {
self.table_width = containing_block_for_children.size.inline;
self.assignable_width = self.table_width - self.table.total_border_spacing().inline;
self.basis_for_cell_padding_percentage =
self.table_width - self.table.border_spacing().inline * 2;
}
fn distribute_width_to_columns(target_inline_size: Au, columns: &[ColumnLayout]) -> Vec<Au> {
if columns.is_empty() {
return Vec::new();
}
let mut min_content_sizing_guesses = Vec::new();
let mut min_content_percentage_sizing_guesses = Vec::new();
let mut min_content_specified_sizing_guesses = Vec::new();
let mut max_content_sizing_guesses = Vec::new();
for column in columns {
let min_content_width = column.content_sizes.min_content;
let max_content_width = column.content_sizes.max_content;
let constrained = column.constrained;
let (
min_content_percentage_sizing_guess,
min_content_specified_sizing_guess,
max_content_sizing_guess,
) = if let Some(percentage) = column.percentage {
let resolved = target_inline_size.scale_by(percentage.0);
let percent_guess = min_content_width.max(resolved);
(percent_guess, percent_guess, percent_guess)
} else if constrained {
(min_content_width, max_content_width, max_content_width)
} else {
(min_content_width, min_content_width, max_content_width)
};
min_content_sizing_guesses.push(min_content_width);
min_content_percentage_sizing_guesses.push(min_content_percentage_sizing_guess);
min_content_specified_sizing_guesses.push(min_content_specified_sizing_guess);
max_content_sizing_guesses.push(max_content_sizing_guess);
}
fn sum(guesses: &[Au]) -> Au {
guesses.iter().fold(Au::zero(), |sum, guess| sum + *guess)
}
let max_content_sizing_sum = sum(&max_content_sizing_guesses);
if target_inline_size >= max_content_sizing_sum {
Self::distribute_extra_width_to_columns(
columns,
&mut max_content_sizing_guesses,
max_content_sizing_sum,
target_inline_size,
);
return max_content_sizing_guesses;
}
let min_content_specified_sizing_sum = sum(&min_content_specified_sizing_guesses);
if target_inline_size == min_content_specified_sizing_sum {
return min_content_specified_sizing_guesses;
}
let min_content_percentage_sizing_sum = sum(&min_content_percentage_sizing_guesses);
if target_inline_size == min_content_percentage_sizing_sum {
return min_content_percentage_sizing_guesses;
}
let min_content_sizes_sum = sum(&min_content_sizing_guesses);
if target_inline_size <= min_content_sizes_sum {
return min_content_sizing_guesses;
}
let bounds = |sum_a, sum_b| target_inline_size > sum_a && target_inline_size < sum_b;
let blend = |a: &[Au], sum_a: Au, b: &[Au], sum_b: Au| {
let weight_a = (target_inline_size - sum_b).to_f32_px() / (sum_a - sum_b).to_f32_px();
let weight_b = 1.0 - weight_a;
let mut remaining_assignable_width = target_inline_size;
let mut widths: Vec<Au> = a
.iter()
.zip(b.iter())
.map(|(guess_a, guess_b)| {
let column_width = guess_a.scale_by(weight_a) + guess_b.scale_by(weight_b);
let column_width = column_width.min(remaining_assignable_width);
remaining_assignable_width -= column_width;
column_width
})
.collect();
if !remaining_assignable_width.is_zero() {
debug_assert!(
remaining_assignable_width >= Au::zero(),
"Sum of columns shouldn't exceed the assignable table width"
);
debug_assert!(
remaining_assignable_width <= Au::new(widths.len() as i32),
"A deviation of more than one Au per column is unlikely to be caused by float imprecision"
);
widths[0] += remaining_assignable_width;
}
debug_assert!(widths.iter().sum::<Au>() == target_inline_size);
widths
};
if bounds(min_content_sizes_sum, min_content_percentage_sizing_sum) {
return blend(
&min_content_sizing_guesses,
min_content_sizes_sum,
&min_content_percentage_sizing_guesses,
min_content_percentage_sizing_sum,
);
}
if bounds(
min_content_percentage_sizing_sum,
min_content_specified_sizing_sum,
) {
return blend(
&min_content_percentage_sizing_guesses,
min_content_percentage_sizing_sum,
&min_content_specified_sizing_guesses,
min_content_specified_sizing_sum,
);
}
assert!(bounds(
min_content_specified_sizing_sum,
max_content_sizing_sum
));
blend(
&min_content_specified_sizing_guesses,
min_content_specified_sizing_sum,
&max_content_sizing_guesses,
max_content_sizing_sum,
)
}
fn distribute_extra_width_to_columns(
columns: &[ColumnLayout],
column_sizes: &mut [Au],
column_sizes_sum: Au,
assignable_width: Au,
) {
let all_columns = 0..columns.len();
let extra_inline_size = assignable_width - column_sizes_sum;
let has_originating_cells =
|column_index: &usize| columns[*column_index].has_originating_cells;
let is_constrained = |column_index: &usize| columns[*column_index].constrained;
let is_unconstrained = |column_index: &usize| !is_constrained(column_index);
let has_percent_greater_than_zero = |column_index: &usize| {
columns[*column_index]
.percentage
.is_some_and(|percentage| percentage.0 > 0.)
};
let has_percent_zero = |column_index: &usize| !has_percent_greater_than_zero(column_index);
let has_max_content =
|column_index: &usize| !columns[*column_index].content_sizes.max_content.is_zero();
let max_content_sum = |column_index: usize| columns[column_index].content_sizes.max_content;
let unconstrained_max_content_columns = all_columns
.clone()
.filter(is_unconstrained)
.filter(has_originating_cells)
.filter(has_percent_zero)
.filter(has_max_content);
let total_max_content_width = unconstrained_max_content_columns
.clone()
.map(max_content_sum)
.fold(Au::zero(), |a, b| a + b);
if total_max_content_width != Au::zero() {
for column_index in unconstrained_max_content_columns {
column_sizes[column_index] += extra_inline_size.scale_by(
columns[column_index].content_sizes.max_content.to_f32_px() /
total_max_content_width.to_f32_px(),
);
}
return;
}
let unconstrained_no_percent_columns = all_columns
.clone()
.filter(is_unconstrained)
.filter(has_originating_cells)
.filter(has_percent_zero);
let total_unconstrained_no_percent = unconstrained_no_percent_columns.clone().count();
if total_unconstrained_no_percent > 0 {
let extra_space_per_column =
extra_inline_size.scale_by(1.0 / total_unconstrained_no_percent as f32);
for column_index in unconstrained_no_percent_columns {
column_sizes[column_index] += extra_space_per_column;
}
return;
}
let constrained_max_content_columns = all_columns
.clone()
.filter(is_constrained)
.filter(has_originating_cells)
.filter(has_percent_zero)
.filter(has_max_content);
let total_max_content_width = constrained_max_content_columns
.clone()
.map(max_content_sum)
.fold(Au::zero(), |a, b| a + b);
if total_max_content_width != Au::zero() {
for column_index in constrained_max_content_columns {
column_sizes[column_index] += extra_inline_size.scale_by(
columns[column_index].content_sizes.max_content.to_f32_px() /
total_max_content_width.to_f32_px(),
);
}
return;
}
let columns_with_percentage = all_columns.clone().filter(has_percent_greater_than_zero);
let total_percent = columns_with_percentage
.clone()
.map(|column_index| columns[column_index].percentage.unwrap_or_default().0)
.sum::<f32>();
if total_percent > 0. {
for column_index in columns_with_percentage {
let column_percentage = columns[column_index].percentage.unwrap_or_default();
column_sizes[column_index] +=
extra_inline_size.scale_by(column_percentage.0 / total_percent);
}
return;
}
let has_originating_cells_columns = all_columns.clone().filter(has_originating_cells);
let total_has_originating_cells = has_originating_cells_columns.clone().count();
if total_has_originating_cells > 0 {
let extra_space_per_column =
extra_inline_size.scale_by(1.0 / total_has_originating_cells as f32);
for column_index in has_originating_cells_columns {
column_sizes[column_index] += extra_space_per_column;
}
return;
}
let extra_space_for_all_columns = extra_inline_size.scale_by(1.0 / columns.len() as f32);
for guess in column_sizes.iter_mut() {
*guess += extra_space_for_all_columns;
}
}
fn layout_cells_in_row(
&mut self,
layout_context: &LayoutContext,
containing_block_for_table: &ContainingBlock,
parent_positioning_context: &mut PositioningContext,
) {
self.cells_laid_out = self
.table
.slots
.par_iter()
.enumerate()
.map(|(row_index, row_slots)| {
let collect_for_nearest_positioned_ancestor = parent_positioning_context
.collects_for_nearest_positioned_ancestor() ||
self.table.rows.get(row_index).is_some_and(|row| {
let row_group_collects_for_nearest_positioned_ancestor =
row.group_index.is_some_and(|group_index| {
self.table.row_groups[group_index]
.style
.establishes_containing_block_for_absolute_descendants(
FragmentFlags::empty(),
)
});
row_group_collects_for_nearest_positioned_ancestor ||
row.style
.establishes_containing_block_for_absolute_descendants(
FragmentFlags::empty(),
)
});
row_slots
.par_iter()
.enumerate()
.map(|(column_index, slot)| {
let TableSlot::Cell(cell) = slot else {
return None;
};
let area = LogicalSides {
inline_start: column_index,
inline_end: column_index + cell.colspan,
block_start: row_index,
block_end: row_index + cell.rowspan,
};
let layout_style = cell.layout_style();
let border = self
.get_collapsed_border_widths_for_area(area)
.unwrap_or_else(|| {
layout_style
.border_width(containing_block_for_table.style.writing_mode)
});
let padding: LogicalSides<Au> = layout_style
.padding(containing_block_for_table.style.writing_mode)
.percentages_relative_to(self.basis_for_cell_padding_percentage);
let inline_border_padding_sum = border.inline_sum() + padding.inline_sum();
let mut total_cell_width: Au = (column_index..column_index + cell.colspan)
.map(|column_index| self.distributed_column_widths[column_index])
.sum::<Au>() -
inline_border_padding_sum;
total_cell_width = total_cell_width.max(Au::zero());
let containing_block_for_children = ContainingBlock {
size: ContainingBlockSize {
inline: total_cell_width,
block: SizeConstraint::default(),
},
style: &cell.base.style,
};
let mut positioning_context = PositioningContext::new_for_subtree(
collect_for_nearest_positioned_ancestor,
);
let layout = cell.contents.layout(
layout_context,
&mut positioning_context,
&containing_block_for_children,
);
Some(CellLayout {
layout,
padding,
border,
positioning_context,
})
})
.collect()
})
.collect();
for row_index in 0..self.table.size.height {
for column_index in 0..self.table.size.width {
let Some(layout) = &self.cells_laid_out[row_index][column_index] else {
continue;
};
self.cell_measures[row_index][column_index]
.block
.content_sizes
.max_assign(layout.outer_block_size().into());
}
}
}
fn do_first_row_layout(&mut self, writing_mode: WritingMode) -> Vec<Au> {
let mut row_sizes = (0..self.table.size.height)
.map(|row_index| {
let (mut max_ascent, mut max_descent, mut max_row_height) =
(Au::zero(), Au::zero(), Au::zero());
for column_index in 0..self.table.size.width {
let cell = match self.table.slots[row_index][column_index] {
TableSlot::Cell(ref cell) => cell,
_ => continue,
};
let layout = match self.cells_laid_out[row_index][column_index] {
Some(ref layout) => layout,
None => {
warn!(
"Did not find a layout at a slot index with an originating cell."
);
continue;
},
};
let outer_block_size = layout.outer_block_size();
if cell.rowspan == 1 {
max_row_height.max_assign(outer_block_size);
}
if cell.effective_vertical_align() == VerticalAlignKeyword::Baseline {
let ascent = layout.ascent();
let border_padding_start =
layout.border.block_start + layout.padding.block_start;
let border_padding_end = layout.border.block_end + layout.padding.block_end;
max_ascent.max_assign(ascent + border_padding_start);
if cell.rowspan == 1 {
max_descent.max_assign(
layout.layout.content_block_size - ascent + border_padding_end,
);
}
}
}
self.row_baselines.push(max_ascent);
max_row_height.max(max_ascent + max_descent)
})
.collect();
self.calculate_row_sizes_after_first_layout(&mut row_sizes, writing_mode);
row_sizes
}
#[allow(clippy::ptr_arg)] fn calculate_row_sizes_after_first_layout(
&mut self,
row_sizes: &mut Vec<Au>,
writing_mode: WritingMode,
) {
let mut cells_to_distribute = Vec::new();
let mut total_percentage = 0.;
#[allow(clippy::needless_range_loop)] for row_index in 0..self.table.size.height {
let row_measure = self
.table
.get_row_measure_for_row_at_index(writing_mode, row_index);
row_sizes[row_index].max_assign(row_measure.content_sizes.min_content);
let mut percentage = row_measure.percentage.unwrap_or_default().0;
for column_index in 0..self.table.size.width {
let cell_percentage = self.cell_measures[row_index][column_index]
.block
.percentage
.unwrap_or_default()
.0;
percentage = percentage.max(cell_percentage);
let cell_measure = &self.cell_measures[row_index][column_index].block;
let cell = match self.table.slots[row_index][column_index] {
TableSlot::Cell(ref cell) if cell.rowspan > 1 => cell,
TableSlot::Cell(_) => {
row_sizes[row_index].max_assign(cell_measure.content_sizes.max_content);
continue;
},
_ => continue,
};
cells_to_distribute.push(RowspanToDistribute {
coordinates: TableSlotCoordinates::new(column_index, row_index),
cell,
measure: cell_measure,
});
}
self.rows[row_index].percent = Percentage(percentage.min(1. - total_percentage));
total_percentage += self.rows[row_index].percent.0;
}
cells_to_distribute.sort_by(|a, b| {
if a.range() == b.range() {
return a
.measure
.content_sizes
.min_content
.cmp(&b.measure.content_sizes.min_content);
}
if a.fully_encloses(b) {
return std::cmp::Ordering::Greater;
}
if b.fully_encloses(a) {
return std::cmp::Ordering::Less;
}
a.coordinates.y.cmp(&b.coordinates.y)
});
for rowspan_to_distribute in cells_to_distribute {
let rows_spanned = rowspan_to_distribute.range();
let current_rows_size: Au = rows_spanned.clone().map(|index| row_sizes[index]).sum();
let border_spacing_spanned =
self.table.border_spacing().block * (rows_spanned.len() - 1) as i32;
let excess_size = (rowspan_to_distribute.measure.content_sizes.min_content -
current_rows_size -
border_spacing_spanned)
.max(Au::zero());
self.distribute_extra_size_to_rows(
excess_size,
rows_spanned,
row_sizes,
None,
true, );
}
}
fn distribute_extra_size_to_rows(
&self,
mut excess_size: Au,
track_range: Range<usize>,
track_sizes: &mut [Au],
percentage_resolution_size: Option<Au>,
rowspan_distribution: bool,
) {
if excess_size.is_zero() {
return;
}
let is_constrained = |track_index: &usize| self.rows[*track_index].constrained;
let is_unconstrained = |track_index: &usize| !is_constrained(track_index);
let is_empty: Vec<bool> = track_sizes.iter().map(|size| size.is_zero()).collect();
let is_not_empty = |track_index: &usize| !is_empty[*track_index];
let other_row_that_starts_a_rowspan = |track_index: &usize| {
*track_index != track_range.start &&
self.rows[*track_index].has_cell_with_span_greater_than_one
};
if let Some(percentage_resolution_size) = percentage_resolution_size {
let get_percent_block_size_deficit = |row_index: usize, track_size: Au| {
let size_needed_for_percent =
percentage_resolution_size.scale_by(self.rows[row_index].percent.0);
(size_needed_for_percent - track_size).max(Au::zero())
};
let percent_block_size_deficit: Au = track_range
.clone()
.map(|index| get_percent_block_size_deficit(index, track_sizes[index]))
.sum();
let percent_distributable_block_size = percent_block_size_deficit.min(excess_size);
if percent_distributable_block_size > Au::zero() {
for track_index in track_range.clone() {
let row_deficit =
get_percent_block_size_deficit(track_index, track_sizes[track_index]);
if row_deficit > Au::zero() {
let ratio =
row_deficit.to_f32_px() / percent_block_size_deficit.to_f32_px();
let size = percent_distributable_block_size.scale_by(ratio);
track_sizes[track_index] += size;
excess_size -= size;
}
}
}
}
if rowspan_distribution {
let rows_that_start_rowspan: Vec<usize> = track_range
.clone()
.filter(other_row_that_starts_a_rowspan)
.collect();
if !rows_that_start_rowspan.is_empty() {
let scale = 1.0 / rows_that_start_rowspan.len() as f32;
for track_index in rows_that_start_rowspan.iter() {
track_sizes[*track_index] += excess_size.scale_by(scale);
}
return;
}
}
let unconstrained_non_empty_rows: Vec<usize> = track_range
.clone()
.filter(is_unconstrained)
.filter(is_not_empty)
.collect();
if !unconstrained_non_empty_rows.is_empty() {
let total_size: Au = unconstrained_non_empty_rows
.iter()
.map(|index| track_sizes[*index])
.sum();
for track_index in unconstrained_non_empty_rows.iter() {
let scale = track_sizes[*track_index].to_f32_px() / total_size.to_f32_px();
track_sizes[*track_index] += excess_size.scale_by(scale);
}
return;
}
let (non_empty_rows, empty_rows): (Vec<usize>, Vec<usize>) =
track_range.clone().partition(is_not_empty);
let only_have_empty_rows = empty_rows.len() == track_range.len();
if !empty_rows.is_empty() {
if rowspan_distribution && only_have_empty_rows {
track_sizes[*empty_rows.last().unwrap()] += excess_size;
return;
}
let non_empty_rows_all_constrained = !non_empty_rows.iter().any(is_unconstrained);
if only_have_empty_rows || non_empty_rows_all_constrained {
let mut rows_to_grow = &empty_rows;
let unconstrained_empty_rows: Vec<usize> = rows_to_grow
.iter()
.copied()
.filter(is_unconstrained)
.collect();
if !unconstrained_empty_rows.is_empty() {
rows_to_grow = &unconstrained_empty_rows;
}
let scale = 1.0 / rows_to_grow.len() as f32;
for track_index in rows_to_grow.iter() {
track_sizes[*track_index] += excess_size.scale_by(scale);
}
return;
}
}
if !non_empty_rows.is_empty() {
let total_size: Au = non_empty_rows.iter().map(|index| track_sizes[*index]).sum();
for track_index in non_empty_rows.iter() {
let scale = track_sizes[*track_index].to_f32_px() / total_size.to_f32_px();
track_sizes[*track_index] += excess_size.scale_by(scale);
}
}
}
fn compute_table_height_and_final_row_heights(
&mut self,
mut row_sizes: Vec<Au>,
containing_block_for_children: &ContainingBlock,
) {
let table_height_from_style = match containing_block_for_children.size.block {
SizeConstraint::Definite(size) => size,
SizeConstraint::MinMax(min, _) => min,
};
let block_border_spacing = self.table.total_border_spacing().block;
let table_height_from_rows = row_sizes.iter().sum::<Au>() + block_border_spacing;
self.final_table_height = table_height_from_rows.max(table_height_from_style);
if self.final_table_height == table_height_from_rows {
self.row_sizes = row_sizes;
return;
}
self.distribute_extra_size_to_rows(
self.final_table_height - table_height_from_rows,
0..self.table.size.height,
&mut row_sizes,
Some(self.final_table_height),
false, );
self.row_sizes = row_sizes;
}
fn layout_caption(
&self,
caption: &TableCaption,
layout_context: &LayoutContext,
parent_positioning_context: &mut PositioningContext,
) -> BoxFragment {
let context = caption.context.borrow();
let mut positioning_context = PositioningContext::new_for_style(context.style());
let containing_block = &ContainingBlock {
size: ContainingBlockSize {
inline: self.table_width + self.pbm.padding_border_sums.inline,
block: SizeConstraint::default(),
},
style: &self.table.style,
};
let ignore_block_margins_for_stretch = LogicalSides1D::new(false, false);
let mut box_fragment = context.layout_in_flow_block_level(
layout_context,
positioning_context
.as_mut()
.unwrap_or(parent_positioning_context),
containing_block,
None, ignore_block_margins_for_stretch,
);
if let Some(mut positioning_context) = positioning_context.take() {
positioning_context.layout_collected_children(layout_context, &mut box_fragment);
parent_positioning_context.append(positioning_context);
}
box_fragment
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(
name = "Table::layout",
skip_all,
fields(servo_profiling = true),
level = "trace",
)
)]
fn layout(
mut self,
layout_context: &LayoutContext,
positioning_context: &mut PositioningContext,
containing_block_for_children: &ContainingBlock,
containing_block_for_table: &ContainingBlock,
) -> IndependentLayout {
let table_writing_mode = containing_block_for_children.style.writing_mode;
self.compute_border_collapse(table_writing_mode);
let layout_style = self.table.layout_style(Some(&self));
let depends_on_block_constraints = layout_style
.content_box_sizes_and_padding_border_margin(&containing_block_for_table.into())
.depends_on_block_constraints;
self.pbm = layout_style
.padding_border_margin_with_writing_mode_and_containing_block_inline_size(
table_writing_mode,
containing_block_for_table.size.inline,
);
self.compute_measures(layout_context, table_writing_mode);
self.compute_table_width(containing_block_for_children);
let containing_block_for_logical_conversion = ContainingBlock {
size: ContainingBlockSize {
inline: self.table_width,
block: containing_block_for_table.size.block,
},
style: containing_block_for_children.style,
};
let offset_from_wrapper = -self.pbm.padding - self.pbm.border;
let mut current_block_offset = offset_from_wrapper.block_start;
let mut table_layout = IndependentLayout {
fragments: Vec::new(),
content_block_size: Zero::zero(),
content_inline_size_for_table: None,
baselines: Baselines::default(),
depends_on_block_constraints,
specific_layout_info: Some(SpecificLayoutInfo::TableWrapper),
};
table_layout
.fragments
.extend(self.table.captions.iter().filter_map(|caption| {
if caption.context.borrow().style().clone_caption_side() != CaptionSide::Top {
return None;
}
let original_positioning_context_length = positioning_context.len();
let mut caption_fragment =
self.layout_caption(caption, layout_context, positioning_context);
let caption_pbm = caption_fragment
.padding_border_margin()
.to_logical(table_writing_mode);
let caption_relative_offset = match caption_fragment.style.clone_position() {
Position::Relative => {
relative_adjustement(&caption_fragment.style, containing_block_for_children)
},
_ => LogicalVec2::zero(),
};
caption_fragment.content_rect = LogicalRect {
start_corner: LogicalVec2 {
inline: offset_from_wrapper.inline_start + caption_pbm.inline_start,
block: current_block_offset + caption_pbm.block_start,
} + caption_relative_offset,
size: caption_fragment
.content_rect
.size
.to_logical(table_writing_mode),
}
.as_physical(Some(&containing_block_for_logical_conversion));
current_block_offset += caption_fragment
.margin_rect()
.size
.to_logical(table_writing_mode)
.block;
let caption_fragment = Fragment::Box(ArcRefCell::new(caption_fragment));
positioning_context.adjust_static_position_of_hoisted_fragments(
&caption_fragment,
original_positioning_context_length,
);
Some(caption_fragment)
}));
let original_positioning_context_length = positioning_context.len();
let mut grid_fragment = self.layout_grid(
layout_context,
positioning_context,
&containing_block_for_logical_conversion,
containing_block_for_children,
);
let logical_grid_content_rect = grid_fragment
.content_rect
.to_logical(&containing_block_for_logical_conversion);
let grid_pbm = grid_fragment
.padding_border_margin()
.to_logical(table_writing_mode);
table_layout.baselines = grid_fragment.baselines(table_writing_mode).offset(
current_block_offset +
logical_grid_content_rect.start_corner.block +
grid_pbm.block_start,
);
grid_fragment.content_rect = LogicalRect {
start_corner: LogicalVec2 {
inline: offset_from_wrapper.inline_start + grid_pbm.inline_start,
block: current_block_offset + grid_pbm.block_start,
},
size: grid_fragment
.content_rect
.size
.to_logical(table_writing_mode),
}
.as_physical(Some(&containing_block_for_logical_conversion));
current_block_offset += grid_fragment
.border_rect()
.size
.to_logical(table_writing_mode)
.block;
if logical_grid_content_rect.size.inline < self.table_width {
table_layout.content_inline_size_for_table =
Some(logical_grid_content_rect.size.inline);
}
let grid_fragment = Fragment::Box(ArcRefCell::new(grid_fragment));
positioning_context.adjust_static_position_of_hoisted_fragments(
&grid_fragment,
original_positioning_context_length,
);
table_layout.fragments.push(grid_fragment);
table_layout
.fragments
.extend(self.table.captions.iter().filter_map(|caption| {
if caption.context.borrow().style().clone_caption_side() != CaptionSide::Bottom {
return None;
}
let original_positioning_context_length = positioning_context.len();
let mut caption_fragment =
self.layout_caption(caption, layout_context, positioning_context);
let caption_pbm = caption_fragment
.padding_border_margin()
.to_logical(table_writing_mode);
caption_fragment.content_rect = LogicalRect {
start_corner: LogicalVec2 {
inline: offset_from_wrapper.inline_start + caption_pbm.inline_start,
block: current_block_offset + caption_pbm.block_start,
},
size: caption_fragment
.content_rect
.size
.to_logical(table_writing_mode),
}
.as_physical(Some(&containing_block_for_logical_conversion));
current_block_offset += caption_fragment
.margin_rect()
.size
.to_logical(table_writing_mode)
.block;
let caption_fragment = Fragment::Box(ArcRefCell::new(caption_fragment));
positioning_context.adjust_static_position_of_hoisted_fragments(
&caption_fragment,
original_positioning_context_length,
);
Some(caption_fragment)
}));
table_layout.content_block_size = current_block_offset + offset_from_wrapper.block_end;
table_layout
}
fn layout_grid(
&mut self,
layout_context: &LayoutContext,
positioning_context: &mut PositioningContext,
containing_block_for_logical_conversion: &ContainingBlock,
containing_block_for_children: &ContainingBlock,
) -> BoxFragment {
self.distributed_column_widths =
Self::distribute_width_to_columns(self.assignable_width, &self.columns);
self.layout_cells_in_row(
layout_context,
containing_block_for_children,
positioning_context,
);
let table_writing_mode = containing_block_for_children.style.writing_mode;
let first_layout_row_heights = self.do_first_row_layout(table_writing_mode);
self.compute_table_height_and_final_row_heights(
first_layout_row_heights,
containing_block_for_children,
);
assert_eq!(self.table.size.height, self.row_sizes.len());
assert_eq!(self.table.size.width, self.distributed_column_widths.len());
if self.table.size.width == 0 && self.table.size.height == 0 {
let content_rect = LogicalRect {
start_corner: LogicalVec2::zero(),
size: LogicalVec2 {
inline: self.table_width,
block: self.final_table_height,
},
}
.as_physical(Some(containing_block_for_logical_conversion));
return BoxFragment::new(
self.table.grid_base_fragment_info,
self.table.grid_style.clone(),
Vec::new(),
content_rect,
self.pbm.padding.to_physical(table_writing_mode),
self.pbm.border.to_physical(table_writing_mode),
PhysicalSides::zero(),
None, CollapsedBlockMargins::zero(),
)
.with_specific_layout_info(self.specific_layout_info_for_grid());
}
let mut table_fragments = Vec::new();
let table_and_track_dimensions = TableAndTrackDimensions::new(self);
self.make_fragments_for_columns_and_column_groups(
&table_and_track_dimensions,
&mut table_fragments,
);
let mut baselines = Baselines::default();
let mut row_group_fragment_layout = None;
for row_index in 0..self.table.size.height {
if row_index == 0 {
let row_end = table_and_track_dimensions
.get_row_rect(0)
.max_block_position();
baselines.first = Some(row_end);
baselines.last = Some(row_end);
}
if self.is_row_collapsed(row_index) {
continue;
}
let table_row = &self.table.rows[row_index];
let mut row_fragment_layout = RowFragmentLayout::new(
table_row,
row_index,
&table_and_track_dimensions,
&self.table.style,
);
let old_row_group_index = row_group_fragment_layout
.as_ref()
.map(|layout: &RowGroupFragmentLayout| layout.index);
if table_row.group_index != old_row_group_index {
if let Some(old_row_group_layout) = row_group_fragment_layout.take() {
table_fragments.push(Fragment::Box(old_row_group_layout.finish(
layout_context,
positioning_context,
containing_block_for_logical_conversion,
containing_block_for_children,
)));
}
if let Some(new_group_index) = table_row.group_index {
row_group_fragment_layout = Some(RowGroupFragmentLayout::new(
&self.table.row_groups[new_group_index],
new_group_index,
&table_and_track_dimensions,
));
}
}
let column_indices = 0..self.table.size.width;
row_fragment_layout.fragments.reserve(self.table.size.width);
for column_index in column_indices {
if self.is_column_collapsed(column_index) {
continue;
}
self.do_final_cell_layout(
row_index,
column_index,
&table_and_track_dimensions,
&mut baselines,
&mut row_fragment_layout,
row_group_fragment_layout.as_mut(),
positioning_context,
);
}
let row_fragment = Fragment::Box(row_fragment_layout.finish(
layout_context,
positioning_context,
containing_block_for_logical_conversion,
containing_block_for_children,
&mut row_group_fragment_layout,
));
match row_group_fragment_layout.as_mut() {
Some(layout) => layout.fragments.push(row_fragment),
None => table_fragments.push(row_fragment),
}
}
if let Some(row_group_layout) = row_group_fragment_layout.take() {
table_fragments.push(Fragment::Box(row_group_layout.finish(
layout_context,
positioning_context,
containing_block_for_logical_conversion,
containing_block_for_children,
)));
}
let content_rect = LogicalRect {
start_corner: LogicalVec2::zero(),
size: LogicalVec2 {
inline: table_and_track_dimensions.table_rect.max_inline_position(),
block: table_and_track_dimensions.table_rect.max_block_position(),
},
}
.as_physical(Some(containing_block_for_logical_conversion));
BoxFragment::new(
self.table.grid_base_fragment_info,
self.table.grid_style.clone(),
table_fragments,
content_rect,
self.pbm.padding.to_physical(table_writing_mode),
self.pbm.border.to_physical(table_writing_mode),
PhysicalSides::zero(),
None, CollapsedBlockMargins::zero(),
)
.with_baselines(baselines)
.with_specific_layout_info(self.specific_layout_info_for_grid())
}
fn specific_layout_info_for_grid(&mut self) -> Option<SpecificLayoutInfo> {
mem::take(&mut self.collapsed_borders).map(|mut collapsed_borders| {
let mut track_sizes = LogicalVec2 {
inline: mem::take(&mut self.distributed_column_widths),
block: mem::take(&mut self.row_sizes),
};
for (column_index, column_size) in track_sizes.inline.iter_mut().enumerate() {
if self.is_column_collapsed(column_index) {
mem::take(column_size);
}
}
for (row_index, row_size) in track_sizes.block.iter_mut().enumerate() {
if self.is_row_collapsed(row_index) {
mem::take(row_size);
}
}
let writing_mode = self.table.style.writing_mode;
if !writing_mode.is_bidi_ltr() {
track_sizes.inline.reverse();
collapsed_borders.inline.reverse();
for border_line in &mut collapsed_borders.block {
border_line.reverse();
}
}
SpecificLayoutInfo::TableGridWithCollapsedBorders(Box::new(SpecificTableGridInfo {
collapsed_borders: if writing_mode.is_horizontal() {
PhysicalVec::new(collapsed_borders.inline, collapsed_borders.block)
} else {
PhysicalVec::new(collapsed_borders.block, collapsed_borders.inline)
},
track_sizes: if writing_mode.is_horizontal() {
PhysicalVec::new(track_sizes.inline, track_sizes.block)
} else {
PhysicalVec::new(track_sizes.block, track_sizes.inline)
},
}))
})
}
fn is_row_collapsed(&self, row_index: usize) -> bool {
let Some(row) = &self.table.rows.get(row_index) else {
return false;
};
if row.style.get_inherited_box().visibility == Visibility::Collapse {
return true;
}
let row_group = match row.group_index {
Some(group_index) => &self.table.row_groups[group_index],
None => return false,
};
row_group.style.get_inherited_box().visibility == Visibility::Collapse
}
fn is_column_collapsed(&self, column_index: usize) -> bool {
let Some(col) = &self.table.columns.get(column_index) else {
return false;
};
if col.style.get_inherited_box().visibility == Visibility::Collapse {
return true;
}
let col_group = match col.group_index {
Some(group_index) => &self.table.column_groups[group_index],
None => return false,
};
col_group.style.get_inherited_box().visibility == Visibility::Collapse
}
#[allow(clippy::too_many_arguments)]
fn do_final_cell_layout(
&mut self,
row_index: usize,
column_index: usize,
dimensions: &TableAndTrackDimensions,
baselines: &mut Baselines,
row_fragment_layout: &mut RowFragmentLayout,
row_group_fragment_layout: Option<&mut RowGroupFragmentLayout>,
positioning_context_for_table: &mut PositioningContext,
) {
let row_group_positioning_context =
row_group_fragment_layout.and_then(|layout| layout.positioning_context.as_mut());
let positioning_context = row_fragment_layout
.positioning_context
.as_mut()
.or(row_group_positioning_context)
.unwrap_or(positioning_context_for_table);
let layout = match self.cells_laid_out[row_index][column_index].take() {
Some(layout) => layout,
None => {
return;
},
};
let cell = match self.table.slots[row_index][column_index] {
TableSlot::Cell(ref cell) => cell,
_ => {
warn!("Did not find a non-spanned cell at index with layout.");
return;
},
};
let row_block_offset = row_fragment_layout.rect.start_corner.block;
let row_baseline = self.row_baselines[row_index];
if cell.effective_vertical_align() == VerticalAlignKeyword::Baseline && !layout.is_empty() {
let baseline = row_block_offset + row_baseline;
if row_index == 0 {
baselines.first = Some(baseline);
}
baselines.last = Some(baseline);
}
let mut row_relative_cell_rect = dimensions.get_cell_rect(
TableSlotCoordinates::new(column_index, row_index),
cell.rowspan,
cell.colspan,
);
row_relative_cell_rect.start_corner -= row_fragment_layout.rect.start_corner;
let mut fragment = cell.create_fragment(
layout,
row_relative_cell_rect,
row_baseline,
positioning_context,
&self.table.style,
&row_fragment_layout.containing_block,
);
let make_relative_to_row_start = |mut rect: LogicalRect<Au>| {
rect.start_corner -= row_fragment_layout.rect.start_corner;
let writing_mode = self.table.style.writing_mode;
PhysicalRect::new(
if writing_mode.is_horizontal() {
PhysicalPoint::new(rect.start_corner.inline, rect.start_corner.block)
} else {
PhysicalPoint::new(rect.start_corner.block, rect.start_corner.inline)
},
rect.size.to_physical_size(writing_mode),
)
};
let column = self.table.columns.get(column_index);
let column_group = column
.and_then(|column| column.group_index)
.and_then(|index| self.table.column_groups.get(index));
if let Some(column_group) = column_group {
let rect = make_relative_to_row_start(dimensions.get_column_group_rect(column_group));
fragment.add_extra_background(ExtraBackground {
style: column_group.style.clone(),
rect,
})
}
if let Some(column) = column {
if !column.is_anonymous {
let rect = make_relative_to_row_start(dimensions.get_column_rect(column_index));
fragment.add_extra_background(ExtraBackground {
style: column.style.clone(),
rect,
})
}
}
let row = self.table.rows.get(row_index);
let row_group = row
.and_then(|row| row.group_index)
.and_then(|index| self.table.row_groups.get(index));
if let Some(row_group) = row_group {
let rect = make_relative_to_row_start(dimensions.get_row_group_rect(row_group));
fragment.add_extra_background(ExtraBackground {
style: row_group.style.clone(),
rect,
})
}
if let Some(row) = row {
let rect = make_relative_to_row_start(row_fragment_layout.rect);
fragment.add_extra_background(ExtraBackground {
style: row.style.clone(),
rect,
})
}
row_fragment_layout
.fragments
.push(Fragment::Box(ArcRefCell::new(fragment)));
}
fn make_fragments_for_columns_and_column_groups(
&self,
dimensions: &TableAndTrackDimensions,
fragments: &mut Vec<Fragment>,
) {
for column_group in self.table.column_groups.iter() {
if !column_group.is_empty() {
fragments.push(Fragment::Positioning(PositioningFragment::new_empty(
column_group.base_fragment_info,
dimensions
.get_column_group_rect(column_group)
.as_physical(None),
column_group.style.clone(),
)));
}
}
for (column_index, column) in self.table.columns.iter().enumerate() {
fragments.push(Fragment::Positioning(PositioningFragment::new_empty(
column.base_fragment_info,
dimensions.get_column_rect(column_index).as_physical(None),
column.style.clone(),
)));
}
}
fn compute_border_collapse(&mut self, writing_mode: WritingMode) {
if self.table.style.get_inherited_table().border_collapse != BorderCollapse::Collapse {
self.collapsed_borders = None;
return;
}
let mut collapsed_borders = LogicalVec2 {
block: vec![
vec![Default::default(); self.table.size.width];
self.table.size.height + 1
],
inline: vec![
vec![Default::default(); self.table.size.height];
self.table.size.width + 1
],
};
let apply_border = |collapsed_borders: &mut CollapsedBorders,
layout_style: &LayoutStyle,
block: &Range<usize>,
inline: &Range<usize>| {
let border = CollapsedBorder::from_layout_style(layout_style, writing_mode);
border
.block_start
.max_assign_to_slice(&mut collapsed_borders.block[block.start][inline.clone()]);
border
.block_end
.max_assign_to_slice(&mut collapsed_borders.block[block.end][inline.clone()]);
border
.inline_start
.max_assign_to_slice(&mut collapsed_borders.inline[inline.start][block.clone()]);
border
.inline_end
.max_assign_to_slice(&mut collapsed_borders.inline[inline.end][block.clone()]);
};
let hide_inner_borders = |collapsed_borders: &mut CollapsedBorders,
block: &Range<usize>,
inline: &Range<usize>| {
for x in inline.clone() {
for y in block.clone() {
if x != inline.start {
collapsed_borders.inline[x][y].hide();
}
if y != block.start {
collapsed_borders.block[y][x].hide();
}
}
}
};
let all_rows = 0..self.table.size.height;
let all_columns = 0..self.table.size.width;
for row_index in all_rows.clone() {
for column_index in all_columns.clone() {
let cell = match self.table.slots[row_index][column_index] {
TableSlot::Cell(ref cell) => cell,
_ => continue,
};
let block_range = row_index..row_index + cell.rowspan;
let inline_range = column_index..column_index + cell.colspan;
hide_inner_borders(&mut collapsed_borders, &block_range, &inline_range);
apply_border(
&mut collapsed_borders,
&cell.layout_style(),
&block_range,
&inline_range,
);
}
}
for (row_index, row) in self.table.rows.iter().enumerate() {
apply_border(
&mut collapsed_borders,
&row.layout_style(),
&(row_index..row_index + 1),
&all_columns,
);
}
for row_group in &self.table.row_groups {
apply_border(
&mut collapsed_borders,
&row_group.layout_style(),
&row_group.track_range,
&all_columns,
);
}
for (column_index, column) in self.table.columns.iter().enumerate() {
apply_border(
&mut collapsed_borders,
&column.layout_style(),
&all_rows,
&(column_index..column_index + 1),
);
}
for column_group in &self.table.column_groups {
apply_border(
&mut collapsed_borders,
&column_group.layout_style(),
&all_rows,
&column_group.track_range,
);
}
apply_border(
&mut collapsed_borders,
&self.table.layout_style_for_grid(),
&all_rows,
&all_columns,
);
self.collapsed_borders = Some(collapsed_borders);
}
fn get_collapsed_border_widths_for_area(
&self,
area: LogicalSides<usize>,
) -> Option<LogicalSides<Au>> {
let collapsed_borders = self.collapsed_borders.as_ref()?;
let columns = || area.inline_start..area.inline_end;
let rows = || area.block_start..area.block_end;
let max_width = |slice: &[CollapsedBorder]| {
let slice_widths = slice.iter().map(|collapsed_border| collapsed_border.width);
slice_widths.max().unwrap_or_default()
};
Some(area.map_inline_and_block_axes(
|column| max_width(&collapsed_borders.inline[*column][rows()]) / 2,
|row| max_width(&collapsed_borders.block[*row][columns()]) / 2,
))
}
}
struct RowFragmentLayout<'a> {
row: &'a TableTrack,
rect: LogicalRect<Au>,
containing_block: ContainingBlock<'a>,
positioning_context: Option<PositioningContext>,
fragments: Vec<Fragment>,
}
impl<'a> RowFragmentLayout<'a> {
fn new(
table_row: &'a TableTrack,
index: usize,
dimensions: &TableAndTrackDimensions,
table_style: &'a ComputedValues,
) -> Self {
let rect = dimensions.get_row_rect(index);
let containing_block = ContainingBlock {
size: ContainingBlockSize {
inline: rect.size.inline,
block: SizeConstraint::Definite(rect.size.block),
},
style: table_style,
};
Self {
row: table_row,
rect,
positioning_context: PositioningContext::new_for_style(&table_row.style),
containing_block,
fragments: Vec::new(),
}
}
fn finish(
mut self,
layout_context: &LayoutContext,
table_positioning_context: &mut PositioningContext,
containing_block_for_logical_conversion: &ContainingBlock,
containing_block_for_children: &ContainingBlock,
row_group_fragment_layout: &mut Option<RowGroupFragmentLayout>,
) -> ArcRefCell<BoxFragment> {
if self.positioning_context.is_some() {
self.rect.start_corner +=
relative_adjustement(&self.row.style, containing_block_for_children);
}
let (inline_size, block_size) = if let Some(row_group_layout) = row_group_fragment_layout {
self.rect.start_corner -= row_group_layout.rect.start_corner;
(
row_group_layout.rect.size.inline,
SizeConstraint::Definite(row_group_layout.rect.size.block),
)
} else {
(
containing_block_for_logical_conversion.size.inline,
containing_block_for_logical_conversion.size.block,
)
};
let row_group_containing_block = ContainingBlock {
size: ContainingBlockSize {
inline: inline_size,
block: block_size,
},
style: containing_block_for_logical_conversion.style,
};
let mut row_fragment = BoxFragment::new(
self.row.base_fragment_info,
self.row.style.clone(),
self.fragments,
self.rect.as_physical(Some(&row_group_containing_block)),
PhysicalSides::zero(), PhysicalSides::zero(), PhysicalSides::zero(), None, CollapsedBlockMargins::zero(),
);
row_fragment.set_does_not_paint_background();
if let Some(mut row_positioning_context) = self.positioning_context.take() {
row_positioning_context.layout_collected_children(layout_context, &mut row_fragment);
let positioning_context = row_group_fragment_layout
.as_mut()
.and_then(|layout| layout.positioning_context.as_mut())
.unwrap_or(table_positioning_context);
positioning_context.append(row_positioning_context);
}
ArcRefCell::new(row_fragment)
}
}
struct RowGroupFragmentLayout {
base_fragment_info: BaseFragmentInfo,
style: Arc<ComputedValues>,
rect: LogicalRect<Au>,
positioning_context: Option<PositioningContext>,
index: usize,
fragments: Vec<Fragment>,
}
impl RowGroupFragmentLayout {
fn new(
row_group: &TableTrackGroup,
index: usize,
dimensions: &TableAndTrackDimensions,
) -> Self {
let rect = dimensions.get_row_group_rect(row_group);
Self {
base_fragment_info: row_group.base_fragment_info,
style: row_group.style.clone(),
rect,
positioning_context: PositioningContext::new_for_style(&row_group.style),
index,
fragments: Vec::new(),
}
}
fn finish(
mut self,
layout_context: &LayoutContext,
table_positioning_context: &mut PositioningContext,
containing_block_for_logical_conversion: &ContainingBlock,
containing_block_for_children: &ContainingBlock,
) -> ArcRefCell<BoxFragment> {
if self.positioning_context.is_some() {
self.rect.start_corner +=
relative_adjustement(&self.style, containing_block_for_children);
}
let mut row_group_fragment = BoxFragment::new(
self.base_fragment_info,
self.style,
self.fragments,
self.rect
.as_physical(Some(containing_block_for_logical_conversion)),
PhysicalSides::zero(), PhysicalSides::zero(), PhysicalSides::zero(), None, CollapsedBlockMargins::zero(),
);
row_group_fragment.set_does_not_paint_background();
if let Some(mut row_positioning_context) = self.positioning_context.take() {
row_positioning_context
.layout_collected_children(layout_context, &mut row_group_fragment);
table_positioning_context.append(row_positioning_context);
}
ArcRefCell::new(row_group_fragment)
}
}
struct TableAndTrackDimensions {
table_rect: LogicalRect<Au>,
table_cells_rect: LogicalRect<Au>,
row_dimensions: Vec<(Au, Au)>,
column_dimensions: Vec<(Au, Au)>,
}
impl TableAndTrackDimensions {
fn new(table_layout: &TableLayout) -> Self {
let border_spacing = table_layout.table.border_spacing();
let fallback_inline_size = table_layout.assignable_width;
let fallback_block_size = table_layout.final_table_height;
let mut column_dimensions = Vec::new();
let mut column_offset = Au::zero();
for column_index in 0..table_layout.table.size.width {
if table_layout.is_column_collapsed(column_index) {
column_dimensions.push((column_offset, column_offset));
continue;
}
let start_offset = column_offset + border_spacing.inline;
let end_offset = start_offset + table_layout.distributed_column_widths[column_index];
column_dimensions.push((start_offset, end_offset));
column_offset = end_offset;
}
column_offset += if table_layout.table.size.width == 0 {
fallback_inline_size
} else {
border_spacing.inline
};
let mut row_dimensions = Vec::new();
let mut row_offset = Au::zero();
for row_index in 0..table_layout.table.size.height {
if table_layout.is_row_collapsed(row_index) {
row_dimensions.push((row_offset, row_offset));
continue;
}
let start_offset = row_offset + border_spacing.block;
let end_offset = start_offset + table_layout.row_sizes[row_index];
row_dimensions.push((start_offset, end_offset));
row_offset = end_offset;
}
row_offset += if table_layout.table.size.height == 0 {
fallback_block_size
} else {
border_spacing.block
};
let table_start_corner = LogicalVec2 {
inline: column_dimensions.first().map_or_else(Au::zero, |v| v.0),
block: row_dimensions.first().map_or_else(Au::zero, |v| v.0),
};
let table_size = LogicalVec2 {
inline: column_dimensions
.last()
.map_or(fallback_inline_size, |v| v.1),
block: row_dimensions.last().map_or(fallback_block_size, |v| v.1),
} - table_start_corner;
let table_cells_rect = LogicalRect {
start_corner: table_start_corner,
size: table_size,
};
let table_rect = LogicalRect {
start_corner: LogicalVec2::zero(),
size: LogicalVec2 {
inline: column_offset,
block: row_offset,
},
};
Self {
table_rect,
table_cells_rect,
row_dimensions,
column_dimensions,
}
}
fn get_row_rect(&self, row_index: usize) -> LogicalRect<Au> {
let mut row_rect = self.table_cells_rect;
let row_dimensions = self.row_dimensions[row_index];
row_rect.start_corner.block = row_dimensions.0;
row_rect.size.block = row_dimensions.1 - row_dimensions.0;
row_rect
}
fn get_column_rect(&self, column_index: usize) -> LogicalRect<Au> {
let mut row_rect = self.table_cells_rect;
let column_dimensions = self.column_dimensions[column_index];
row_rect.start_corner.inline = column_dimensions.0;
row_rect.size.inline = column_dimensions.1 - column_dimensions.0;
row_rect
}
fn get_row_group_rect(&self, row_group: &TableTrackGroup) -> LogicalRect<Au> {
if row_group.is_empty() {
return LogicalRect::zero();
}
let mut row_group_rect = self.table_cells_rect;
let block_start = self.row_dimensions[row_group.track_range.start].0;
let block_end = self.row_dimensions[row_group.track_range.end - 1].1;
row_group_rect.start_corner.block = block_start;
row_group_rect.size.block = block_end - block_start;
row_group_rect
}
fn get_column_group_rect(&self, column_group: &TableTrackGroup) -> LogicalRect<Au> {
if column_group.is_empty() {
return LogicalRect::zero();
}
let mut column_group_rect = self.table_cells_rect;
let inline_start = self.column_dimensions[column_group.track_range.start].0;
let inline_end = self.column_dimensions[column_group.track_range.end - 1].1;
column_group_rect.start_corner.inline = inline_start;
column_group_rect.size.inline = inline_end - inline_start;
column_group_rect
}
fn get_cell_rect(
&self,
coordinates: TableSlotCoordinates,
rowspan: usize,
colspan: usize,
) -> LogicalRect<Au> {
let start_corner = LogicalVec2 {
inline: self.column_dimensions[coordinates.x].0,
block: self.row_dimensions[coordinates.y].0,
};
let size = LogicalVec2 {
inline: self.column_dimensions[coordinates.x + colspan - 1].1,
block: self.row_dimensions[coordinates.y + rowspan - 1].1,
} - start_corner;
LogicalRect { start_corner, size }
}
}
impl Table {
fn border_spacing(&self) -> LogicalVec2<Au> {
if self.style.clone_border_collapse() == BorderCollapse::Collapse {
LogicalVec2::zero()
} else {
let border_spacing = self.style.clone_border_spacing();
LogicalVec2 {
inline: border_spacing.horizontal(),
block: border_spacing.vertical(),
}
}
}
fn total_border_spacing(&self) -> LogicalVec2<Au> {
let border_spacing = self.border_spacing();
LogicalVec2 {
inline: if self.size.width > 0 {
border_spacing.inline * (self.size.width as i32 + 1)
} else {
Au::zero()
},
block: if self.size.height > 0 {
border_spacing.block * (self.size.height as i32 + 1)
} else {
Au::zero()
},
}
}
fn get_column_measure_for_column_at_index(
&self,
writing_mode: WritingMode,
column_index: usize,
is_in_fixed_mode: bool,
) -> CellOrTrackMeasure {
let column = match self.columns.get(column_index) {
Some(column) => column,
None => return CellOrTrackMeasure::zero(),
};
let CellOrColumnOuterSizes {
preferred: preferred_size,
min: min_size,
max: max_size,
percentage: percentage_size,
} = CellOrColumnOuterSizes::new(
&column.style,
writing_mode,
&Default::default(),
is_in_fixed_mode,
);
CellOrTrackMeasure {
content_sizes: ContentSizes {
min_content: min_size.inline,
max_content: preferred_size
.inline
.clamp_between_extremums(min_size.inline, max_size.inline),
},
percentage: percentage_size.inline,
}
}
fn get_row_measure_for_row_at_index(
&self,
writing_mode: WritingMode,
row_index: usize,
) -> CellOrTrackMeasure {
let row = match self.rows.get(row_index) {
Some(row) => row,
None => return CellOrTrackMeasure::zero(),
};
let size = row.style.box_size(writing_mode);
let max_size = row.style.max_box_size(writing_mode);
let percentage_contribution = get_size_percentage_contribution(&size, &max_size);
CellOrTrackMeasure {
content_sizes: size
.block
.to_numeric()
.and_then(|size| size.to_length())
.map_or_else(Au::zero, Au::from)
.into(),
percentage: percentage_contribution.block,
}
}
pub(crate) fn layout(
&self,
layout_context: &LayoutContext,
positioning_context: &mut PositioningContext,
containing_block_for_children: &ContainingBlock,
containing_block_for_table: &ContainingBlock,
) -> IndependentLayout {
TableLayout::new(self).layout(
layout_context,
positioning_context,
containing_block_for_children,
containing_block_for_table,
)
}
}
impl ComputeInlineContentSizes for Table {
#[cfg_attr(
feature = "tracing",
tracing::instrument(
name = "Table::compute_inline_content_sizes",
skip_all,
fields(servo_profiling = true),
level = "trace",
)
)]
fn compute_inline_content_sizes(
&self,
layout_context: &LayoutContext,
constraint_space: &ConstraintSpace,
) -> InlineContentSizesResult {
let writing_mode = constraint_space.writing_mode;
let mut layout = TableLayout::new(self);
layout.compute_border_collapse(writing_mode);
layout.pbm = self
.layout_style(Some(&layout))
.padding_border_margin_with_writing_mode_and_containing_block_inline_size(
writing_mode,
Au::zero(),
);
layout.compute_measures(layout_context, writing_mode);
let grid_content_sizes = layout.compute_grid_min_max();
let caption_content_sizes = ContentSizes::from(
layout.compute_caption_minimum_inline_size(layout_context) -
layout.pbm.padding_border_sums.inline,
);
InlineContentSizesResult {
sizes: grid_content_sizes.max(caption_content_sizes),
depends_on_block_constraints: false,
}
}
}
impl Table {
#[inline]
pub(crate) fn layout_style<'a>(
&'a self,
layout: Option<&'a TableLayout<'a>>,
) -> LayoutStyle<'a> {
LayoutStyle::Table(TableLayoutStyle {
table: self,
layout,
})
}
#[inline]
pub(crate) fn layout_style_for_grid(&self) -> LayoutStyle {
LayoutStyle::Default(&self.grid_style)
}
}
impl TableTrack {
#[inline]
pub(crate) fn layout_style(&self) -> LayoutStyle {
LayoutStyle::Default(&self.style)
}
}
impl TableTrackGroup {
#[inline]
pub(crate) fn layout_style(&self) -> LayoutStyle {
LayoutStyle::Default(&self.style)
}
}
impl TableLayoutStyle<'_> {
#[inline]
pub(crate) fn style(&self) -> &ComputedValues {
&self.table.style
}
#[inline]
pub(crate) fn collapses_borders(&self) -> bool {
self.style().get_inherited_table().border_collapse == BorderCollapse::Collapse
}
pub(crate) fn halved_collapsed_border_widths(&self) -> LogicalSides<Au> {
debug_assert!(self.collapses_borders());
let area = LogicalSides {
inline_start: 0,
inline_end: self.table.size.width,
block_start: 0,
block_end: self.table.size.height,
};
if let Some(layout) = self.layout {
layout.get_collapsed_border_widths_for_area(area)
} else {
let mut layout = TableLayout::new(self.table);
layout.compute_border_collapse(self.style().writing_mode);
layout.get_collapsed_border_widths_for_area(area)
}
.expect("Collapsed borders should be computed")
}
}
impl TableSlotCell {
#[inline]
fn layout_style(&self) -> LayoutStyle {
self.contents.layout_style(&self.base)
}
fn effective_vertical_align(&self) -> VerticalAlignKeyword {
match self.base.style.clone_vertical_align() {
VerticalAlign::Keyword(VerticalAlignKeyword::Top) => VerticalAlignKeyword::Top,
VerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => VerticalAlignKeyword::Bottom,
VerticalAlign::Keyword(VerticalAlignKeyword::Middle) => VerticalAlignKeyword::Middle,
_ => VerticalAlignKeyword::Baseline,
}
}
fn inline_content_sizes(&self, layout_context: &LayoutContext) -> ContentSizes {
let constraint_space = ConstraintSpace::new_for_style_and_ratio(
&self.base.style,
None, );
self.base
.inline_content_sizes(layout_context, &constraint_space, &self.contents.contents)
.sizes
}
fn create_fragment(
&self,
mut layout: CellLayout,
cell_rect: LogicalRect<Au>,
cell_baseline: Au,
positioning_context: &mut PositioningContext,
table_style: &ComputedValues,
containing_block: &ContainingBlock,
) -> BoxFragment {
use style::Zero as StyleZero;
let cell_content_rect = cell_rect.deflate(&(layout.padding + layout.border));
let content_block_size = layout.layout.content_block_size;
let vertical_align_offset = match self.effective_vertical_align() {
VerticalAlignKeyword::Top => Au::zero(),
VerticalAlignKeyword::Bottom => cell_content_rect.size.block - content_block_size,
VerticalAlignKeyword::Middle => {
(cell_content_rect.size.block - content_block_size).scale_by(0.5)
},
_ => {
cell_baseline -
(layout.padding.block_start + layout.border.block_start) -
layout.ascent()
},
};
let mut base_fragment_info = self.base.base_fragment_info;
if self.base.style.get_inherited_table().empty_cells == EmptyCells::Hide &&
table_style.get_inherited_table().border_collapse != BorderCollapse::Collapse &&
layout.is_empty_for_empty_cells()
{
base_fragment_info.flags.insert(FragmentFlags::DO_NOT_PAINT);
}
let mut vertical_align_fragment_rect = cell_content_rect;
vertical_align_fragment_rect.start_corner = LogicalVec2 {
inline: Au::zero(),
block: vertical_align_offset,
};
let vertical_align_fragment = PositioningFragment::new_anonymous(
vertical_align_fragment_rect.as_physical(None),
layout.layout.fragments,
);
let physical_cell_rect = cell_content_rect.as_physical(Some(containing_block));
layout
.positioning_context
.adjust_static_position_of_hoisted_fragments_with_offset(
&physical_cell_rect.origin.to_vector(),
PositioningContextLength::zero(),
);
positioning_context.append(layout.positioning_context);
let specific_layout_info = (table_style.get_inherited_table().border_collapse ==
BorderCollapse::Collapse)
.then_some(SpecificLayoutInfo::TableCellWithCollapsedBorders);
BoxFragment::new(
base_fragment_info,
self.base.style.clone(),
vec![Fragment::Positioning(vertical_align_fragment)],
physical_cell_rect,
layout.padding.to_physical(table_style.writing_mode),
layout.border.to_physical(table_style.writing_mode),
PhysicalSides::zero(), None, CollapsedBlockMargins::zero(),
)
.with_baselines(layout.layout.baselines)
.with_specific_layout_info(specific_layout_info)
}
}
fn get_size_percentage_contribution(
size: &LogicalVec2<Size<ComputedLengthPercentage>>,
max_size: &LogicalVec2<Size<ComputedLengthPercentage>>,
) -> LogicalVec2<Option<Percentage>> {
LogicalVec2 {
inline: max_two_optional_percentages(
size.inline.to_percentage(),
max_size.inline.to_percentage(),
),
block: max_two_optional_percentages(
size.block.to_percentage(),
max_size.block.to_percentage(),
),
}
}
struct CellOrColumnOuterSizes {
min: LogicalVec2<Au>,
preferred: LogicalVec2<Au>,
max: LogicalVec2<Option<Au>>,
percentage: LogicalVec2<Option<Percentage>>,
}
impl CellOrColumnOuterSizes {
fn new(
style: &Arc<ComputedValues>,
writing_mode: WritingMode,
padding_border_sums: &LogicalVec2<Au>,
is_in_fixed_mode: bool,
) -> Self {
let box_sizing = style.get_position().box_sizing;
let outer_size = |size: LogicalVec2<Au>| match box_sizing {
BoxSizing::ContentBox => size + *padding_border_sums,
BoxSizing::BorderBox => LogicalVec2 {
inline: size.inline.max(padding_border_sums.inline),
block: size.block.max(padding_border_sums.block),
},
};
let outer_option_size = |size: LogicalVec2<Option<Au>>| match box_sizing {
BoxSizing::ContentBox => size.map_inline_and_block_axes(
|inline| inline.map(|inline| inline + padding_border_sums.inline),
|block| block.map(|block| block + padding_border_sums.block),
),
BoxSizing::BorderBox => size.map_inline_and_block_axes(
|inline| inline.map(|inline| inline.max(padding_border_sums.inline)),
|block| block.map(|block| block.max(padding_border_sums.block)),
),
};
let get_size_for_axis = |size: &Size<ComputedLengthPercentage>| {
size.to_numeric()
.and_then(|length_percentage| length_percentage.to_length())
.map(Au::from)
};
let size = style.box_size(writing_mode);
if is_in_fixed_mode {
return Self {
percentage: size.map(|v| v.to_percentage()),
preferred: outer_option_size(size.map(get_size_for_axis))
.map(|v| v.unwrap_or_default()),
min: LogicalVec2::default(),
max: LogicalVec2::default(),
};
}
let min_size = style.min_box_size(writing_mode);
let max_size = style.max_box_size(writing_mode);
Self {
min: outer_size(min_size.map(|v| get_size_for_axis(v).unwrap_or_default())),
preferred: outer_size(size.map(|v| get_size_for_axis(v).unwrap_or_default())),
max: outer_option_size(max_size.map(get_size_for_axis)),
percentage: get_size_percentage_contribution(&size, &max_size),
}
}
}
struct RowspanToDistribute<'a> {
coordinates: TableSlotCoordinates,
cell: &'a TableSlotCell,
measure: &'a CellOrTrackMeasure,
}
impl RowspanToDistribute<'_> {
fn range(&self) -> Range<usize> {
self.coordinates.y..self.coordinates.y + self.cell.rowspan
}
fn fully_encloses(&self, other: &RowspanToDistribute) -> bool {
other.coordinates.y > self.coordinates.y && other.range().end < self.range().end
}
}
#[derive(Debug)]
struct ColspanToDistribute {
starting_column: usize,
span: usize,
content_sizes: ContentSizes,
percentage: Option<Percentage>,
}
impl ColspanToDistribute {
fn comparison_for_sort(a: &Self, b: &Self) -> Ordering {
a.span
.cmp(&b.span)
.then_with(|| b.starting_column.cmp(&b.starting_column))
}
fn range(&self) -> Range<usize> {
self.starting_column..self.starting_column + self.span
}
}