diff options
Diffstat (limited to 'components/layout_2020/table.rs')
-rw-r--r-- | components/layout_2020/table.rs | 1378 |
1 files changed, 1378 insertions, 0 deletions
diff --git a/components/layout_2020/table.rs b/components/layout_2020/table.rs new file mode 100644 index 00000000000..9f6219f0980 --- /dev/null +++ b/components/layout_2020/table.rs @@ -0,0 +1,1378 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! CSS table formatting contexts. + +use crate::block::{BlockFlow, CandidateBSizeIterator, ISizeAndMarginsComputer}; +use crate::block::{ISizeConstraintInput, ISizeConstraintSolution}; +use crate::context::LayoutContext; +use crate::display_list::{ + BorderPaintingMode, DisplayListBuildState, StackingContextCollectionFlags, + StackingContextCollectionState, +}; +use crate::flow::{ + BaseFlow, EarlyAbsolutePositionInfo, Flow, FlowClass, GetBaseFlow, ImmutableFlowUtils, + OpaqueFlow, +}; +use crate::flow_list::{FlowListIterator, MutFlowListIterator}; +use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow}; +use crate::layout_debug; +use crate::model::{IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto}; +use crate::table_cell::TableCellFlow; +use crate::table_row::{self, CellIntrinsicInlineSize, CollapsedBorder, CollapsedBorderProvenance}; +use crate::table_row::{TableRowFlow, TableRowSizeData}; +use crate::table_wrapper::TableLayout; +use app_units::Au; +use euclid::Point2D; +use gfx_traits::print_tree::PrintTree; +use std::{cmp, fmt}; +use style::computed_values::{border_collapse, border_spacing, table_layout}; +use style::context::SharedStyleContext; +use style::logical_geometry::LogicalSize; +use style::properties::style_structs::Background; +use style::properties::ComputedValues; +use style::servo::restyle_damage::ServoRestyleDamage; +use style::values::computed::Size; +use style::values::CSSFloat; + +#[allow(unsafe_code)] +unsafe impl crate::flow::HasBaseFlow for TableFlow {} + +/// A table flow corresponded to the table's internal table fragment under a table wrapper flow. +/// The properties `position`, `float`, and `margin-*` are used on the table wrapper fragment, +/// not table fragment per CSS 2.1 § 10.5. +#[derive(Serialize)] +#[repr(C)] +pub struct TableFlow { + pub block_flow: BlockFlow, + + /// Information about the intrinsic inline-sizes of each column, computed bottom-up during + /// intrinsic inline-size bubbling. + pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>, + + /// Information about the actual inline sizes of each column, computed top-down during actual + /// inline-size bubbling. + pub column_computed_inline_sizes: Vec<ColumnComputedInlineSize>, + + /// The final width of the borders in the inline direction for each cell, computed by the + /// entire table and pushed down into each row during inline size computation. + pub collapsed_inline_direction_border_widths_for_table: Vec<Au>, + + /// The final width of the borders in the block direction for each cell, computed by the + /// entire table and pushed down into each row during inline size computation. + pub collapsed_block_direction_border_widths_for_table: Vec<Au>, + + /// Table-layout property + pub table_layout: TableLayout, +} + +impl TableFlow { + pub fn from_fragment(fragment: Fragment) -> TableFlow { + let mut block_flow = BlockFlow::from_fragment(fragment); + let table_layout = + if block_flow.fragment().style().get_table().table_layout == table_layout::T::Fixed { + TableLayout::Fixed + } else { + TableLayout::Auto + }; + TableFlow { + block_flow: block_flow, + column_intrinsic_inline_sizes: Vec::new(), + column_computed_inline_sizes: Vec::new(), + collapsed_inline_direction_border_widths_for_table: Vec::new(), + collapsed_block_direction_border_widths_for_table: Vec::new(), + table_layout: table_layout, + } + } + + /// Update the corresponding value of `self_inline_sizes` if a value of `kid_inline_sizes` has + /// a larger value than one of `self_inline_sizes`. Returns the minimum and preferred inline + /// sizes. + fn update_automatic_column_inline_sizes( + parent_inline_sizes: &mut Vec<ColumnIntrinsicInlineSize>, + child_cell_inline_sizes: &[CellIntrinsicInlineSize], + surrounding_size: Au, + ) -> IntrinsicISizes { + let mut total_inline_sizes = IntrinsicISizes { + minimum_inline_size: surrounding_size, + preferred_inline_size: surrounding_size, + }; + let mut column_index = 0; + let mut incoming_rowspan = vec![]; + + for child_cell_inline_size in child_cell_inline_sizes { + // Skip any column occupied by a cell from a previous row. + while column_index < incoming_rowspan.len() && incoming_rowspan[column_index] != 1 { + if incoming_rowspan[column_index] > 1 { + incoming_rowspan[column_index] -= 1; + } + column_index += 1; + } + for _ in 0..child_cell_inline_size.column_span { + if column_index < parent_inline_sizes.len() { + // We already have some intrinsic size information for this column. Merge it in + // according to the rules specified in INTRINSIC § 4. + let parent_sizes = &mut parent_inline_sizes[column_index]; + if child_cell_inline_size.column_span > 1 { + // TODO(pcwalton): Perform the recursive algorithm specified in INTRINSIC § + // 4. For now we make this column contribute no width. + } else { + let column_size = &child_cell_inline_size.column_size; + *parent_sizes = ColumnIntrinsicInlineSize { + minimum_length: cmp::max( + parent_sizes.minimum_length, + column_size.minimum_length, + ), + percentage: parent_sizes.greatest_percentage(column_size), + preferred: cmp::max(parent_sizes.preferred, column_size.preferred), + constrained: parent_sizes.constrained || column_size.constrained, + } + } + } else { + // We discovered a new column. Initialize its data. + debug_assert_eq!(column_index, parent_inline_sizes.len()); + if child_cell_inline_size.column_span > 1 { + // TODO(pcwalton): Perform the recursive algorithm specified in INTRINSIC § + // 4. For now we make this column contribute no width. + parent_inline_sizes.push(ColumnIntrinsicInlineSize::new()) + } else { + parent_inline_sizes.push(child_cell_inline_size.column_size) + } + } + + total_inline_sizes.minimum_inline_size += + parent_inline_sizes[column_index].minimum_length; + total_inline_sizes.preferred_inline_size += + parent_inline_sizes[column_index].preferred; + + // If this cell spans later rows, record its rowspan. + if child_cell_inline_size.row_span > 1 { + if incoming_rowspan.len() < column_index + 1 { + incoming_rowspan.resize(column_index + 1, 0); + } + incoming_rowspan[column_index] = child_cell_inline_size.row_span; + } + + column_index += 1 + } + } + + total_inline_sizes + } + + /// Updates the minimum and preferred inline-size calculation for a single row. This is + /// factored out into a separate function because we process children of rowgroups too. + fn update_column_inline_sizes_for_row( + row: &TableRowFlow, + column_inline_sizes: &mut Vec<ColumnIntrinsicInlineSize>, + computation: &mut IntrinsicISizesContribution, + first_row: bool, + table_layout: TableLayout, + surrounding_inline_size: Au, + ) { + // Read column inline-sizes from the table-row, and assign inline-size=0 for the columns + // not defined in the column group. + // + // FIXME: Need to read inline-sizes from either table-header-group OR the first table-row. + match table_layout { + TableLayout::Fixed => { + // Fixed table layout only looks at the first row. + // + // FIXME(pcwalton): This is really inefficient. We should stop after the first row! + if first_row { + for cell_inline_size in &row.cell_intrinsic_inline_sizes { + column_inline_sizes.push(cell_inline_size.column_size); + } + } + }, + TableLayout::Auto => { + computation.union_block(&TableFlow::update_automatic_column_inline_sizes( + column_inline_sizes, + &row.cell_intrinsic_inline_sizes, + surrounding_inline_size, + )) + }, + } + } + + /// Returns the effective spacing per cell, taking the value of `border-collapse` into account. + pub fn spacing(&self) -> border_spacing::T { + let style = self.block_flow.fragment.style(); + match style.get_inherited_table().border_collapse { + border_collapse::T::Separate => style.get_inherited_table().border_spacing, + border_collapse::T::Collapse => border_spacing::T::zero(), + } + } + + pub fn total_horizontal_spacing(&self) -> Au { + let num_columns = self.column_intrinsic_inline_sizes.len(); + if num_columns == 0 { + return Au(0); + } + self.spacing().horizontal() * (num_columns as i32 + 1) + } + + fn column_styles(&self) -> Vec<ColumnStyle> { + let mut styles = vec![]; + for group in self + .block_flow + .base + .child_iter() + .filter(|kid| kid.is_table_colgroup()) + { + // XXXManishearth these as_foo methods should return options + // so that we can filter_map + let group = group.as_table_colgroup(); + let colgroup_style = group.fragment.as_ref().map(|f| f.style()); + + // The colgroup's span attribute is only relevant when + // it has no children + // https://html.spec.whatwg.org/multipage/#forming-a-table + if group.cols.is_empty() { + let span = group + .fragment + .as_ref() + .map(|f| f.column_span()) + .unwrap_or(1); + styles.push(ColumnStyle { + span, + colgroup_style, + col_style: None, + }); + } else { + for col in &group.cols { + // XXXManishearth Arc-cloning colgroup_style is suboptimal + styles.push(ColumnStyle { + span: col.column_span(), + colgroup_style: colgroup_style, + col_style: Some(col.style()), + }) + } + } + } + styles + } +} + +impl Flow for TableFlow { + fn class(&self) -> FlowClass { + FlowClass::Table + } + + fn as_mut_table(&mut self) -> &mut TableFlow { + self + } + + fn as_table(&self) -> &TableFlow { + self + } + + fn as_mut_block(&mut self) -> &mut BlockFlow { + &mut self.block_flow + } + + fn as_block(&self) -> &BlockFlow { + &self.block_flow + } + + fn mark_as_root(&mut self) { + self.block_flow.mark_as_root(); + } + + /// The specified column inline-sizes are set from column group and the first row for the fixed + /// table layout calculation. + /// The maximum min/pref inline-sizes of each column are set from the rows for the automatic + /// table layout calculation. + fn bubble_inline_sizes(&mut self) { + let _scope = layout_debug_scope!( + "table::bubble_inline_sizes {:x}", + self.block_flow.base.debug_id() + ); + + // Get column inline sizes from colgroups + for kid in self + .block_flow + .base + .child_iter_mut() + .filter(|kid| kid.is_table_colgroup()) + { + for specified_inline_size in &kid.as_mut_table_colgroup().inline_sizes { + self.column_intrinsic_inline_sizes + .push(ColumnIntrinsicInlineSize { + minimum_length: match *specified_inline_size { + Size::Auto => Au(0), + Size::LengthPercentage(ref lp) => { + lp.maybe_to_used_value(None).unwrap_or(Au(0)) + }, + }, + percentage: match *specified_inline_size { + Size::Auto => 0.0, + Size::LengthPercentage(ref lp) => { + lp.0.as_percentage().map_or(0.0, |p| p.0) + }, + }, + preferred: Au(0), + constrained: false, + }) + } + } + + self.collapsed_inline_direction_border_widths_for_table = Vec::new(); + self.collapsed_block_direction_border_widths_for_table = vec![Au(0)]; + + let collapsing_borders = self + .block_flow + .fragment + .style + .get_inherited_table() + .border_collapse == + border_collapse::T::Collapse; + let table_inline_collapsed_borders = if collapsing_borders { + Some(TableInlineCollapsedBorders { + start: CollapsedBorder::inline_start( + &*self.block_flow.fragment.style, + CollapsedBorderProvenance::FromTable, + ), + end: CollapsedBorder::inline_end( + &*self.block_flow.fragment.style, + CollapsedBorderProvenance::FromTable, + ), + }) + } else { + None + }; + + let mut computation = IntrinsicISizesContribution::new(); + let mut previous_collapsed_block_end_borders = + PreviousBlockCollapsedBorders::FromTable(CollapsedBorder::block_start( + &*self.block_flow.fragment.style, + CollapsedBorderProvenance::FromTable, + )); + let mut first_row = true; + let (border_padding, _) = self.block_flow.fragment.surrounding_intrinsic_inline_size(); + + { + let mut iterator = TableRowIterator::new(&mut self.block_flow.base).peekable(); + while let Some(row) = iterator.next() { + TableFlow::update_column_inline_sizes_for_row( + row, + &mut self.column_intrinsic_inline_sizes, + &mut computation, + first_row, + self.table_layout, + border_padding, + ); + if collapsing_borders { + let next_index_and_sibling = iterator.peek(); + let next_collapsed_borders_in_block_direction = match next_index_and_sibling { + Some(next_sibling) => NextBlockCollapsedBorders::FromNextRow( + &next_sibling + .as_table_row() + .preliminary_collapsed_borders + .block_start, + ), + None => NextBlockCollapsedBorders::FromTable(CollapsedBorder::block_end( + &*self.block_flow.fragment.style, + CollapsedBorderProvenance::FromTable, + )), + }; + perform_border_collapse_for_row( + row, + table_inline_collapsed_borders.as_ref().unwrap(), + previous_collapsed_block_end_borders, + next_collapsed_borders_in_block_direction, + &mut self.collapsed_inline_direction_border_widths_for_table, + &mut self.collapsed_block_direction_border_widths_for_table, + ); + previous_collapsed_block_end_borders = + PreviousBlockCollapsedBorders::FromPreviousRow( + row.final_collapsed_borders.block_end.clone(), + ); + } + first_row = false + } + } + + let total_horizontal_spacing = self.total_horizontal_spacing(); + let mut style_specified_intrinsic_inline_size = self + .block_flow + .fragment + .style_specified_intrinsic_inline_size() + .finish(); + style_specified_intrinsic_inline_size.minimum_inline_size -= total_horizontal_spacing; + style_specified_intrinsic_inline_size.preferred_inline_size -= total_horizontal_spacing; + computation.union_block(&style_specified_intrinsic_inline_size); + computation.surrounding_size += total_horizontal_spacing; + + self.block_flow.base.intrinsic_inline_sizes = computation.finish() + } + + /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. + /// When called on this context, the context has had its inline-size set by the parent context. + fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { + let _scope = layout_debug_scope!( + "table::assign_inline_sizes {:x}", + self.block_flow.base.debug_id() + ); + debug!( + "assign_inline_sizes({}): assigning inline_size for flow", + "table" + ); + + let shared_context = layout_context.shared_context(); + // The position was set to the containing block by the flow's parent. + // FIXME: The code for distributing column widths should really be placed under table_wrapper.rs. + let containing_block_inline_size = self.block_flow.base.block_container_inline_size; + + let mut constrained_column_inline_sizes_indices = vec![]; + let mut unspecified_inline_sizes_indices = vec![]; + for (idx, column_inline_size) in self.column_intrinsic_inline_sizes.iter().enumerate() { + if column_inline_size.constrained { + constrained_column_inline_sizes_indices.push(idx); + } else if column_inline_size.percentage == 0.0 { + unspecified_inline_sizes_indices.push(idx); + } + } + + let inline_size_computer = InternalTable; + inline_size_computer.compute_used_inline_size( + &mut self.block_flow, + shared_context, + containing_block_inline_size, + ); + + let inline_start_content_edge = self.block_flow.fragment.border_padding.inline_start; + let inline_end_content_edge = self.block_flow.fragment.border_padding.inline_end; + let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end(); + let spacing_per_cell = self.spacing(); + let total_horizontal_spacing = self.total_horizontal_spacing(); + let content_inline_size = self.block_flow.fragment.border_box.size.inline - + padding_and_borders - + total_horizontal_spacing; + let mut remaining_inline_size = content_inline_size; + + match self.table_layout { + TableLayout::Fixed => { + self.column_computed_inline_sizes.clear(); + + // https://drafts.csswg.org/css2/tables.html#fixed-table-layout + for column_inline_size in &self.column_intrinsic_inline_sizes { + if column_inline_size.constrained { + self.column_computed_inline_sizes + .push(ColumnComputedInlineSize { + size: column_inline_size.minimum_length, + }); + remaining_inline_size -= column_inline_size.minimum_length; + } else if column_inline_size.percentage != 0.0 { + let size = remaining_inline_size.scale_by(column_inline_size.percentage); + self.column_computed_inline_sizes + .push(ColumnComputedInlineSize { size: size }); + remaining_inline_size -= size; + } else { + // Set the size to 0 now, distribute the remaining widths later + self.column_computed_inline_sizes + .push(ColumnComputedInlineSize { size: Au(0) }); + } + } + + // Distribute remaining content inline size + if unspecified_inline_sizes_indices.len() > 0 { + for &index in &unspecified_inline_sizes_indices { + self.column_computed_inline_sizes[index].size = remaining_inline_size + .scale_by(1.0 / unspecified_inline_sizes_indices.len() as f32); + } + } else { + let total_minimum_size = self + .column_intrinsic_inline_sizes + .iter() + .filter(|size| size.constrained) + .map(|size| size.minimum_length.0 as f32) + .sum::<f32>(); + + for &index in &constrained_column_inline_sizes_indices { + let inline_size = self.column_computed_inline_sizes[index].size.0; + self.column_computed_inline_sizes[index].size += + remaining_inline_size.scale_by(inline_size as f32 / total_minimum_size); + } + } + }, + _ => { + // The table wrapper already computed the inline-sizes and propagated them down + // to us. + }, + } + + let column_computed_inline_sizes = &self.column_computed_inline_sizes; + let collapsed_inline_direction_border_widths_for_table = + &self.collapsed_inline_direction_border_widths_for_table; + let mut collapsed_block_direction_border_widths_for_table = self + .collapsed_block_direction_border_widths_for_table + .iter() + .peekable(); + let mut incoming_rowspan = vec![]; + self.block_flow.propagate_assigned_inline_size_to_children( + shared_context, + inline_start_content_edge, + inline_end_content_edge, + content_inline_size, + |child_flow, + _child_index, + _content_inline_size, + writing_mode, + _inline_start_margin_edge, + _inline_end_margin_edge| { + table_row::propagate_column_inline_sizes_to_child( + child_flow, + writing_mode, + column_computed_inline_sizes, + &spacing_per_cell, + &mut incoming_rowspan, + ); + if child_flow.is_table_row() { + let child_table_row = child_flow.as_mut_table_row(); + child_table_row.populate_collapsed_border_spacing( + collapsed_inline_direction_border_widths_for_table, + &mut collapsed_block_direction_border_widths_for_table, + ); + } else if child_flow.is_table_rowgroup() { + let child_table_rowgroup = child_flow.as_mut_table_rowgroup(); + child_table_rowgroup.populate_collapsed_border_spacing( + collapsed_inline_direction_border_widths_for_table, + &mut collapsed_block_direction_border_widths_for_table, + ); + } + }, + ); + } + + fn assign_block_size(&mut self, lc: &LayoutContext) { + debug!("assign_block_size: assigning block_size for table"); + let vertical_spacing = self.spacing().vertical(); + self.block_flow + .assign_block_size_for_table_like_flow(vertical_spacing, lc) + } + + fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) { + self.block_flow + .compute_stacking_relative_position(layout_context) + } + + fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> { + self.block_flow.generated_containing_block_size(flow) + } + + fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { + self.block_flow + .update_late_computed_inline_position_if_necessary(inline_position) + } + + fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) { + self.block_flow + .update_late_computed_block_position_if_necessary(block_position) + } + + fn build_display_list(&mut self, state: &mut DisplayListBuildState) { + let border_painting_mode = match self + .block_flow + .fragment + .style + .get_inherited_table() + .border_collapse + { + border_collapse::T::Separate => BorderPaintingMode::Separate, + border_collapse::T::Collapse => BorderPaintingMode::Hidden, + }; + + self.block_flow + .build_display_list_for_block(state, border_painting_mode); + + let iter = TableCellStyleIterator::new(&self); + for style in iter { + style.build_display_list(state) + } + } + + fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) { + // Stacking contexts are collected by the table wrapper. + self.block_flow.collect_stacking_contexts_for_block( + state, + StackingContextCollectionFlags::NEVER_CREATES_STACKING_CONTEXT, + ); + } + + fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) { + self.block_flow.repair_style(new_style) + } + + fn compute_overflow(&self) -> Overflow { + self.block_flow.compute_overflow() + } + + fn iterate_through_fragment_border_boxes( + &self, + iterator: &mut dyn FragmentBorderBoxIterator, + level: i32, + stacking_context_position: &Point2D<Au>, + ) { + self.block_flow.iterate_through_fragment_border_boxes( + iterator, + level, + stacking_context_position, + ) + } + + fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) { + self.block_flow.mutate_fragments(mutator) + } + + fn print_extra_flow_children(&self, print_tree: &mut PrintTree) { + self.block_flow.print_extra_flow_children(print_tree); + } +} + +#[derive(Debug)] +struct ColumnStyle<'table> { + span: u32, + colgroup_style: Option<&'table ComputedValues>, + col_style: Option<&'table ComputedValues>, +} + +impl fmt::Debug for TableFlow { + /// Outputs a debugging string describing this table flow. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TableFlow: {:?}", self.block_flow) + } +} + +/// Table, TableRowGroup, TableRow, TableCell types. +/// Their inline-sizes are calculated in the same way and do not have margins. +pub struct InternalTable; + +impl ISizeAndMarginsComputer for InternalTable { + /// Compute the used value of inline-size, taking care of min-inline-size and max-inline-size. + /// + /// CSS Section 10.4: Minimum and Maximum inline-sizes + fn compute_used_inline_size( + &self, + block: &mut BlockFlow, + shared_context: &SharedStyleContext, + parent_flow_inline_size: Au, + ) { + let mut input = self.compute_inline_size_constraint_inputs( + block, + parent_flow_inline_size, + shared_context, + ); + + // Tables are always at least as wide as their minimum inline size. + let minimum_inline_size = block.base.intrinsic_inline_sizes.minimum_inline_size - + block.fragment.border_padding.inline_start_end(); + input.available_inline_size = cmp::max(input.available_inline_size, minimum_inline_size); + + let solution = self.solve_inline_size_constraints(block, &input); + self.set_inline_size_constraint_solutions(block, solution); + } + + /// Solve the inline-size and margins constraints for this block flow. + fn solve_inline_size_constraints( + &self, + _: &mut BlockFlow, + input: &ISizeConstraintInput, + ) -> ISizeConstraintSolution { + ISizeConstraintSolution::new(input.available_inline_size, Au(0), Au(0)) + } +} + +/// Information about the intrinsic inline sizes of columns within a table. +/// +/// During table inline-size bubbling, we might need to store both a percentage constraint and a +/// specific width constraint. For instance, one cell might say that it wants to be 100 pixels wide +/// in the inline direction and another cell might say that it wants to take up 20% of the inline- +/// size of the table. Now because we bubble up these constraints during the bubble-inline-sizes +/// phase of layout, we don't know yet how wide the table is ultimately going to be in the inline +/// direction. As we need to pick the maximum width of all cells for a column (in this case, the +/// maximum of 100 pixels and 20% of the table), the preceding constraint means that we must +/// potentially store both a specified width *and* a specified percentage, so that the inline-size +/// assignment phase of layout will know which one to pick. +#[derive(Clone, Copy, Debug, Serialize)] +pub struct ColumnIntrinsicInlineSize { + /// The preferred intrinsic inline size. + pub preferred: Au, + /// The largest specified size of this column as a length. + pub minimum_length: Au, + /// The largest specified size of this column as a percentage (`width` property). + pub percentage: CSSFloat, + /// Whether the column inline size is *constrained* per INTRINSIC § 4.1. + pub constrained: bool, +} + +impl ColumnIntrinsicInlineSize { + /// Returns a newly-initialized `ColumnIntrinsicInlineSize` with all fields blank. + pub fn new() -> ColumnIntrinsicInlineSize { + ColumnIntrinsicInlineSize { + preferred: Au(0), + minimum_length: Au(0), + percentage: 0.0, + constrained: false, + } + } + + /// Returns the higher of the two percentages specified in `self` and `other`. + pub fn greatest_percentage(&self, other: &ColumnIntrinsicInlineSize) -> CSSFloat { + if self.percentage > other.percentage { + self.percentage + } else { + other.percentage + } + } +} + +/// The actual inline size for each column. +/// +/// TODO(pcwalton): There will probably be some `border-collapse`-related info in here too +/// eventually. +#[derive(Clone, Copy, Debug, Serialize)] +pub struct ColumnComputedInlineSize { + /// The computed size of this inline column. + pub size: Au, +} + +pub trait VecExt<T> { + fn push_or_set(&mut self, index: usize, value: T) -> &mut T; + fn get_mut_or_push(&mut self, index: usize, zero: T) -> &mut T; +} + +impl<T> VecExt<T> for Vec<T> { + fn push_or_set(&mut self, index: usize, value: T) -> &mut T { + if index < self.len() { + self[index] = value + } else { + debug_assert_eq!(index, self.len()); + self.push(value) + } + &mut self[index] + } + + fn get_mut_or_push(&mut self, index: usize, zero: T) -> &mut T { + if index >= self.len() { + debug_assert_eq!(index, self.len()); + self.push(zero) + } + &mut self[index] + } +} + +/// Updates the border styles in the block direction for a single row. This function should +/// only be called if border collapsing is on. It is factored out into a separate function +/// because we process children of rowgroups too. +fn perform_border_collapse_for_row( + child_table_row: &mut TableRowFlow, + table_inline_borders: &TableInlineCollapsedBorders, + previous_block_borders: PreviousBlockCollapsedBorders, + next_block_borders: NextBlockCollapsedBorders, + inline_spacing: &mut Vec<Au>, + block_spacing: &mut Vec<Au>, +) { + // TODO mbrubeck: Take rowspan and colspan into account. + let number_of_borders_inline_direction = + child_table_row.preliminary_collapsed_borders.inline.len(); + // Compute interior inline borders. + for (i, this_inline_border) in child_table_row + .preliminary_collapsed_borders + .inline + .iter_mut() + .enumerate() + { + child_table_row + .final_collapsed_borders + .inline + .push_or_set(i, *this_inline_border); + if i == 0 { + child_table_row.final_collapsed_borders.inline[i].combine(&table_inline_borders.start); + } else if i + 1 == number_of_borders_inline_direction { + child_table_row.final_collapsed_borders.inline[i].combine(&table_inline_borders.end); + } + + let inline_spacing = inline_spacing.get_mut_or_push(i, Au(0)); + *inline_spacing = cmp::max( + *inline_spacing, + child_table_row.final_collapsed_borders.inline[i].width, + ) + } + + // Compute block-start borders. + let block_start_borders = &mut child_table_row.final_collapsed_borders.block_start; + *block_start_borders = child_table_row + .preliminary_collapsed_borders + .block_start + .clone(); + for (i, this_border) in block_start_borders.iter_mut().enumerate() { + match previous_block_borders { + PreviousBlockCollapsedBorders::FromPreviousRow(ref previous_block_borders) => { + if previous_block_borders.len() > i { + this_border.combine(&previous_block_borders[i]); + } + }, + PreviousBlockCollapsedBorders::FromTable(table_border) => { + this_border.combine(&table_border); + }, + } + } + + // Compute block-end borders. + let next_block = &mut child_table_row.final_collapsed_borders.block_end; + block_spacing.push(Au(0)); + let block_spacing = block_spacing.last_mut().unwrap(); + for (i, this_block_border) in child_table_row + .preliminary_collapsed_borders + .block_end + .iter() + .enumerate() + { + let next_block = next_block.push_or_set(i, *this_block_border); + match next_block_borders { + NextBlockCollapsedBorders::FromNextRow(next_block_borders) => { + if next_block_borders.len() > i { + next_block.combine(&next_block_borders[i]) + } + }, + NextBlockCollapsedBorders::FromTable(ref next_block_borders) => { + next_block.combine(next_block_borders); + }, + } + *block_spacing = cmp::max(*block_spacing, next_block.width) + } +} + +/// Encapsulates functionality shared among all table-like flows: for now, tables and table +/// rowgroups. +pub trait TableLikeFlow { + /// Lays out the rows of a table. + fn assign_block_size_for_table_like_flow( + &mut self, + block_direction_spacing: Au, + layout_context: &LayoutContext, + ); +} + +impl TableLikeFlow for BlockFlow { + fn assign_block_size_for_table_like_flow( + &mut self, + block_direction_spacing: Au, + layout_context: &LayoutContext, + ) { + debug_assert!( + self.fragment.style.get_inherited_table().border_collapse == + border_collapse::T::Separate || + block_direction_spacing == Au(0) + ); + + fn border_spacing_for_row( + fragment: &Fragment, + row: &TableRowFlow, + block_direction_spacing: Au, + ) -> Au { + match fragment.style.get_inherited_table().border_collapse { + border_collapse::T::Separate => block_direction_spacing, + border_collapse::T::Collapse => row.collapsed_border_spacing.block_start, + } + } + + if self + .base + .restyle_damage + .contains(ServoRestyleDamage::REFLOW) + { + let mut sizes = vec![Default::default()]; + // The amount of border spacing up to and including this row, + // but not including the spacing beneath it + let mut cumulative_border_spacing = Au(0); + let mut incoming_rowspan_data = vec![]; + let mut rowgroup_id = 0; + let mut first = true; + + // First pass: Compute block-direction border spacings + // XXXManishearth this can be done in tandem with the second pass, + // provided we never hit any rowspan cases + for kid in self.base.child_iter_mut() { + if kid.is_table_row() { + // skip the first row, it is accounted for + if first { + first = false; + continue; + } + cumulative_border_spacing += border_spacing_for_row( + &self.fragment, + kid.as_table_row(), + block_direction_spacing, + ); + sizes.push(TableRowSizeData { + // we haven't calculated sizes yet + size: Au(0), + cumulative_border_spacing, + rowgroup_id, + }); + } else if kid.is_table_rowgroup() && !first { + rowgroup_id += 1; + } + } + + // Second pass: Compute row block sizes + // [expensive: iterates over cells] + let mut i = 0; + for kid in self.base.child_iter_mut() { + if kid.is_table_row() { + let size = kid.as_mut_table_row().compute_block_size_table_row_base( + layout_context, + &mut incoming_rowspan_data, + &sizes, + i, + ); + sizes[i].size = size; + i += 1; + } + } + + // Our current border-box position. + let block_start_border_padding = self.fragment.border_padding.block_start; + let mut current_block_offset = block_start_border_padding; + let mut has_rows = false; + + // Third pass: Assign block sizes and positions to rows, cells, and other children + // [expensive: iterates over cells] + // At this point, `current_block_offset` is at the content edge of our box. Now iterate + // over children. + let mut i = 0; + for kid in self.base.child_iter_mut() { + if kid.is_table_row() { + has_rows = true; + let row = kid.as_mut_table_row(); + row.assign_block_size_to_self_and_children(&sizes, i); + row.mut_base().restyle_damage.remove( + ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW, + ); + current_block_offset = current_block_offset + + border_spacing_for_row(&self.fragment, row, block_direction_spacing); + i += 1; + } + + // At this point, `current_block_offset` is at the border edge of the child. + kid.mut_base().position.start.b = current_block_offset; + + // Move past the child's border box. Do not use the `translate_including_floats` + // function here because the child has already translated floats past its border + // box. + let kid_base = kid.mut_base(); + current_block_offset = current_block_offset + kid_base.position.size.block; + } + + // Compute any explicitly-specified block size. + // Can't use `for` because we assign to + // `candidate_block_size_iterator.candidate_value`. + let mut block_size = current_block_offset - block_start_border_padding; + let mut candidate_block_size_iterator = CandidateBSizeIterator::new( + &self.fragment, + self.base.block_container_explicit_block_size, + ); + while let Some(candidate_block_size) = candidate_block_size_iterator.next() { + candidate_block_size_iterator.candidate_value = match candidate_block_size { + MaybeAuto::Auto => block_size, + MaybeAuto::Specified(value) => value, + }; + } + + // Adjust `current_block_offset` as necessary to account for the explicitly-specified + // block-size. + block_size = candidate_block_size_iterator.candidate_value; + let delta = block_size - (current_block_offset - block_start_border_padding); + current_block_offset = current_block_offset + delta; + + // Take border, padding, and spacing into account. + let block_end_offset = self.fragment.border_padding.block_end + + if has_rows { + block_direction_spacing + } else { + Au(0) + }; + current_block_offset = current_block_offset + block_end_offset; + + // Now that `current_block_offset` is at the block-end of the border box, compute the + // final border box position. + self.fragment.border_box.size.block = current_block_offset; + self.fragment.border_box.start.b = Au(0); + self.base.position.size.block = current_block_offset; + + // Fourth pass: Assign absolute position info + // Write in the size of the relative containing block for children. (This information + // is also needed to handle RTL.) + for kid in self.base.child_iter_mut() { + kid.mut_base().early_absolute_position_info = EarlyAbsolutePositionInfo { + relative_containing_block_size: self.fragment.content_box().size, + relative_containing_block_mode: self.fragment.style().writing_mode, + }; + } + } + + self.base + .restyle_damage + .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); + } +} + +/// Inline collapsed borders for the table itself. +#[derive(Debug)] +struct TableInlineCollapsedBorders { + /// The table border at the start of the inline direction. + start: CollapsedBorder, + /// The table border at the end of the inline direction. + end: CollapsedBorder, +} + +enum PreviousBlockCollapsedBorders { + FromPreviousRow(Vec<CollapsedBorder>), + FromTable(CollapsedBorder), +} + +enum NextBlockCollapsedBorders<'a> { + FromNextRow(&'a [CollapsedBorder]), + FromTable(CollapsedBorder), +} + +/// Iterator over all the rows of a table, which also +/// provides the Fragment for rowgroups if any +struct TableRowAndGroupIterator<'a> { + kids: FlowListIterator<'a>, + group: Option<(&'a Fragment, FlowListIterator<'a>)>, +} + +impl<'a> TableRowAndGroupIterator<'a> { + fn new(base: &'a BaseFlow) -> Self { + TableRowAndGroupIterator { + kids: base.child_iter(), + group: None, + } + } +} + +impl<'a> Iterator for TableRowAndGroupIterator<'a> { + type Item = (Option<&'a Fragment>, &'a TableRowFlow); + #[inline] + fn next(&mut self) -> Option<Self::Item> { + // If we're inside a rowgroup, iterate through the rowgroup's children. + if let Some(ref mut group) = self.group { + if let Some(grandkid) = group.1.next() { + return Some((Some(group.0), grandkid.as_table_row())); + } + } + // Otherwise, iterate through the table's children. + self.group = None; + match self.kids.next() { + Some(kid) => { + if kid.is_table_rowgroup() { + let rowgroup = kid.as_table_rowgroup(); + let iter = rowgroup.block_flow.base.child_iter(); + self.group = Some((&rowgroup.block_flow.fragment, iter)); + self.next() + } else if kid.is_table_row() { + Some((None, kid.as_table_row())) + } else { + self.next() // Skip children that are not rows or rowgroups + } + }, + None => None, + } + } +} + +/// Iterator over all the rows of a table, which also +/// provides the Fragment for rowgroups if any +struct MutTableRowAndGroupIterator<'a> { + kids: MutFlowListIterator<'a>, + group: Option<(&'a Fragment, MutFlowListIterator<'a>)>, +} + +impl<'a> MutTableRowAndGroupIterator<'a> { + fn new(base: &'a mut BaseFlow) -> Self { + MutTableRowAndGroupIterator { + kids: base.child_iter_mut(), + group: None, + } + } +} + +impl<'a> Iterator for MutTableRowAndGroupIterator<'a> { + type Item = (Option<&'a Fragment>, &'a mut TableRowFlow); + #[inline] + fn next(&mut self) -> Option<Self::Item> { + // If we're inside a rowgroup, iterate through the rowgroup's children. + if let Some(ref mut group) = self.group { + if let Some(grandkid) = group.1.next() { + return Some((Some(group.0), grandkid.as_mut_table_row())); + } + } + // Otherwise, iterate through the table's children. + self.group = None; + match self.kids.next() { + Some(kid) => { + if kid.is_table_rowgroup() { + let rowgroup = kid.as_mut_table_rowgroup(); + let iter = rowgroup.block_flow.base.child_iter_mut(); + self.group = Some((&rowgroup.block_flow.fragment, iter)); + self.next() + } else if kid.is_table_row() { + Some((None, kid.as_mut_table_row())) + } else { + self.next() // Skip children that are not rows or rowgroups + } + }, + None => None, + } + } +} + +/// Iterator over all the rows of a table +struct TableRowIterator<'a>(MutTableRowAndGroupIterator<'a>); + +impl<'a> TableRowIterator<'a> { + fn new(base: &'a mut BaseFlow) -> Self { + TableRowIterator(MutTableRowAndGroupIterator::new(base)) + } +} + +impl<'a> Iterator for TableRowIterator<'a> { + type Item = &'a mut TableRowFlow; + #[inline] + fn next(&mut self) -> Option<Self::Item> { + self.0.next().map(|n| n.1) + } +} + +/// An iterator over table cells, yielding all relevant style objects +/// for each cell +/// +/// Used for correctly handling table layers from +/// https://drafts.csswg.org/css2/tables.html#table-layers +struct TableCellStyleIterator<'table> { + column_styles: Vec<ColumnStyle<'table>>, + row_iterator: TableRowAndGroupIterator<'table>, + row_info: Option<TableCellStyleIteratorRowInfo<'table>>, + column_index: TableCellColumnIndexData, +} + +struct TableCellStyleIteratorRowInfo<'table> { + row: &'table TableRowFlow, + rowgroup: Option<&'table Fragment>, + cell_iterator: FlowListIterator<'table>, +} + +impl<'table> TableCellStyleIterator<'table> { + fn new(table: &'table TableFlow) -> Self { + let column_styles = table.column_styles(); + let mut row_iterator = TableRowAndGroupIterator::new(&table.block_flow.base); + let row_info = if let Some((group, row)) = row_iterator.next() { + Some(TableCellStyleIteratorRowInfo { + row: &row, + rowgroup: group, + cell_iterator: row.block_flow.base.child_iter(), + }) + } else { + None + }; + TableCellStyleIterator { + column_styles, + row_iterator, + row_info, + column_index: Default::default(), + } + } +} + +struct TableCellStyleInfo<'table> { + cell: &'table TableCellFlow, + colgroup_style: Option<&'table ComputedValues>, + col_style: Option<&'table ComputedValues>, + rowgroup_style: Option<&'table ComputedValues>, + row_style: &'table ComputedValues, +} + +struct TableCellColumnIndexData { + /// Which column this is in the table + pub absolute: u32, + /// The index of the current column in column_styles + /// (i.e. which <col> element it is) + pub relative: u32, + /// In case of multispan <col>s, where we are in the + /// span of the current <col> element + pub relative_offset: u32, +} + +impl Default for TableCellColumnIndexData { + fn default() -> Self { + TableCellColumnIndexData { + absolute: 0, + relative: 0, + relative_offset: 0, + } + } +} + +impl TableCellColumnIndexData { + /// Moves forward by `amount` columns, updating the various indices used + /// + /// This totally ignores rowspan -- if colspan and rowspan clash, + /// they just overlap, so we ignore it. + fn advance(&mut self, amount: u32, column_styles: &[ColumnStyle]) { + self.absolute += amount; + self.relative_offset += amount; + if let Some(mut current_col) = column_styles.get(self.relative as usize) { + while self.relative_offset >= current_col.span { + // move to the next column + self.relative += 1; + self.relative_offset -= current_col.span; + if let Some(column_style) = column_styles.get(self.relative as usize) { + current_col = column_style; + } else { + // we ran out of column_styles, + // so we don't need to update the indices + break; + } + } + } + } +} + +impl<'table> Iterator for TableCellStyleIterator<'table> { + type Item = TableCellStyleInfo<'table>; + #[inline] + fn next(&mut self) -> Option<Self::Item> { + // FIXME We do this awkward .take() followed by shoving it back in + // because without NLL the row_info borrow lasts too long + if let Some(mut row_info) = self.row_info.take() { + if let Some(rowspan) = row_info + .row + .incoming_rowspan + .get(self.column_index.absolute as usize) + { + // we are not allowed to use this column as a starting point. Try the next one. + if *rowspan > 1 { + self.column_index.advance(1, &self.column_styles); + // put row_info back in + self.row_info = Some(row_info); + // try again + return self.next(); + } + } + if let Some(cell) = row_info.cell_iterator.next() { + let rowgroup_style = row_info.rowgroup.map(|r| r.style()); + let row_style = row_info.row.block_flow.fragment.style(); + let cell = cell.as_table_cell(); + let (col_style, colgroup_style) = if let Some(column_style) = + self.column_styles.get(self.column_index.relative as usize) + { + let styles = ( + column_style.col_style.clone(), + column_style.colgroup_style.clone(), + ); + self.column_index + .advance(cell.column_span, &self.column_styles); + + styles + } else { + (None, None) + }; + // put row_info back in + self.row_info = Some(row_info); + return Some(TableCellStyleInfo { + cell, + colgroup_style, + col_style, + rowgroup_style, + row_style, + }); + } else { + // next row + if let Some((group, row)) = self.row_iterator.next() { + self.row_info = Some(TableCellStyleIteratorRowInfo { + row: &row, + rowgroup: group, + cell_iterator: row.block_flow.base.child_iter(), + }); + self.column_index = Default::default(); + self.next() + } else { + // out of rows + // row_info stays None + None + } + } + } else { + // empty table + None + } + } +} + +impl<'table> TableCellStyleInfo<'table> { + fn build_display_list(&self, mut state: &mut DisplayListBuildState) { + use style::computed_values::visibility::T as Visibility; + + if !self.cell.visible || + self.cell + .block_flow + .fragment + .style() + .get_inherited_box() + .visibility != + Visibility::Visible + { + return; + } + let border_painting_mode = match self + .cell + .block_flow + .fragment + .style + .get_inherited_table() + .border_collapse + { + border_collapse::T::Separate => BorderPaintingMode::Separate, + border_collapse::T::Collapse => { + BorderPaintingMode::Collapse(&self.cell.collapsed_borders) + }, + }; + { + let cell_flow = &self.cell.block_flow; + let initial = ComputedValues::initial_values(); + + let build_dl = |sty: &ComputedValues, state: &mut &mut DisplayListBuildState| { + let background = sty.get_background(); + // Don't redraw backgrounds that we've already drawn + if background as *const Background == initial.get_background() as *const _ { + return; + } + let background_color = sty.resolve_color(background.background_color); + cell_flow.build_display_list_for_background_if_applicable_with_background( + state, + background, + background_color, + ); + }; + + if let Some(ref sty) = self.colgroup_style { + build_dl(&sty, &mut state); + } + if let Some(ref sty) = self.col_style { + build_dl(&sty, &mut state); + } + if let Some(ref sty) = self.rowgroup_style { + build_dl(sty, &mut state); + } + build_dl(self.row_style, &mut state); + } + // the restyle damage will be set in TableCellFlow::build_display_list() + self.cell + .block_flow + .build_display_list_for_block_no_damage(state, border_painting_mode) + } +} |