aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout_2020/table_row.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout_2020/table_row.rs')
-rw-r--r--components/layout_2020/table_row.rs1158
1 files changed, 1158 insertions, 0 deletions
diff --git a/components/layout_2020/table_row.rs b/components/layout_2020/table_row.rs
new file mode 100644
index 00000000000..824da31edad
--- /dev/null
+++ b/components/layout_2020/table_row.rs
@@ -0,0 +1,1158 @@
+/* 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, ISizeAndMarginsComputer};
+use crate::context::LayoutContext;
+use crate::display_list::{
+ DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState,
+};
+use crate::flow::{
+ EarlyAbsolutePositionInfo, Flow, FlowClass, GetBaseFlow, ImmutableFlowUtils, OpaqueFlow,
+};
+use crate::flow_list::MutFlowListIterator;
+use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
+use crate::layout_debug;
+use crate::table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, InternalTable, VecExt};
+use crate::table_cell::{CollapsedBordersForCell, TableCellFlow};
+use app_units::Au;
+use euclid::Point2D;
+use gfx_traits::print_tree::PrintTree;
+use serde::{Serialize, Serializer};
+use std::cmp::max;
+use std::fmt;
+use std::iter::{Enumerate, IntoIterator, Peekable};
+use style::computed_values::border_collapse::T as BorderCollapse;
+use style::computed_values::border_spacing::T as BorderSpacing;
+use style::computed_values::border_top_style::T as BorderStyle;
+use style::logical_geometry::{LogicalSize, PhysicalSide, WritingMode};
+use style::properties::ComputedValues;
+use style::values::computed::{Color, Size};
+
+#[allow(unsafe_code)]
+unsafe impl crate::flow::HasBaseFlow for TableRowFlow {}
+
+/// A single row of a table.
+#[repr(C)]
+pub struct TableRowFlow {
+ /// Fields common to all block flows.
+ pub block_flow: BlockFlow,
+
+ /// Information about the intrinsic inline-sizes of each cell.
+ pub cell_intrinsic_inline_sizes: Vec<CellIntrinsicInlineSize>,
+
+ /// Information about the computed inline-sizes of each column.
+ pub column_computed_inline_sizes: Vec<ColumnComputedInlineSize>,
+
+ /// The number of remaining rows spanned by cells in previous rows, indexed by column.
+ ///
+ /// Columns that are not included in this vector have the default rowspan of "1". If there are
+ /// no cells with rowspan != 1 in previous rows, this vector may be empty.
+ pub incoming_rowspan: Vec<u32>,
+
+ /// The spacing for this row, propagated down from the table during the inline-size assignment
+ /// phase.
+ pub spacing: BorderSpacing,
+
+ /// The direction of the columns, propagated down from the table during the inline-size
+ /// assignment phase.
+ pub table_writing_mode: WritingMode,
+
+ /// Information about the borders for each cell that we bubble up to our parent. This is only
+ /// computed if `border-collapse` is `collapse`.
+ pub preliminary_collapsed_borders: CollapsedBordersForRow,
+
+ /// Information about the borders for each cell, post-collapse. This is only computed if
+ /// `border-collapse` is `collapse`.
+ pub final_collapsed_borders: CollapsedBordersForRow,
+
+ /// The computed cell spacing widths post-collapse.
+ pub collapsed_border_spacing: CollapsedBorderSpacingForRow,
+}
+
+impl Serialize for TableRowFlow {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ self.block_flow.serialize(serializer)
+ }
+}
+
+/// Information about the column inline size and span for each cell.
+#[derive(Clone, Copy, Serialize)]
+pub struct CellIntrinsicInlineSize {
+ /// Inline sizes that this cell contributes to the column.
+ pub column_size: ColumnIntrinsicInlineSize,
+ /// The column span of this cell.
+ pub column_span: u32,
+ /// The row span of this cell.
+ pub row_span: u32,
+}
+
+impl TableRowFlow {
+ pub fn from_fragment(fragment: Fragment) -> TableRowFlow {
+ let writing_mode = fragment.style().writing_mode;
+ TableRowFlow {
+ block_flow: BlockFlow::from_fragment(fragment),
+ cell_intrinsic_inline_sizes: Vec::new(),
+ column_computed_inline_sizes: Vec::new(),
+ incoming_rowspan: Vec::new(),
+ spacing: BorderSpacing::zero(),
+ table_writing_mode: writing_mode,
+ preliminary_collapsed_borders: CollapsedBordersForRow::new(),
+ final_collapsed_borders: CollapsedBordersForRow::new(),
+ collapsed_border_spacing: CollapsedBorderSpacingForRow::new(),
+ }
+ }
+
+ /// Compute block-size for table-row flow.
+ ///
+ /// TODO(pcwalton): This doesn't handle floats and positioned elements right.
+ ///
+ /// Returns the block size
+ pub fn compute_block_size_table_row_base<'a>(
+ &'a mut self,
+ layout_context: &LayoutContext,
+ incoming_rowspan_data: &mut Vec<Au>,
+ border_info: &[TableRowSizeData],
+ row_index: usize,
+ ) -> Au {
+ fn include_sizes_from_previous_rows(
+ col: &mut usize,
+ incoming_rowspan: &[u32],
+ incoming_rowspan_data: &mut Vec<Au>,
+ max_block_size: &mut Au,
+ ) {
+ while let Some(span) = incoming_rowspan.get(*col) {
+ if *span == 1 {
+ break;
+ }
+ let incoming = if let Some(incoming) = incoming_rowspan_data.get(*col) {
+ *incoming
+ } else {
+ // This happens when we have a cell with both rowspan and colspan
+ // incoming_rowspan_data only records the data for the first column,
+ // but that's ok because we only need to account for each spanning cell
+ // once. So we skip ahead.
+ *col += 1;
+ continue;
+ };
+ *max_block_size = max(*max_block_size, incoming);
+ *col += 1;
+ }
+ }
+ // Per CSS 2.1 § 17.5.3, find max_y = max(computed `block-size`, minimum block-size of
+ // all cells).
+ let mut max_block_size = Au(0);
+ let thread_id = self.block_flow.base.thread_id;
+ let content_box = self.block_flow.base.position -
+ self.block_flow.fragment.border_padding -
+ self.block_flow.fragment.margin;
+
+ let mut col = 0;
+ for kid in self.block_flow.base.child_iter_mut() {
+ include_sizes_from_previous_rows(
+ &mut col,
+ &self.incoming_rowspan,
+ incoming_rowspan_data,
+ &mut max_block_size,
+ );
+ kid.place_float_if_applicable();
+ debug_assert!(
+ !kid.base().flags.is_float(),
+ "table cells should never float"
+ );
+ kid.assign_block_size_for_inorder_child_if_necessary(
+ layout_context,
+ thread_id,
+ content_box,
+ );
+
+ let mut row_span;
+ let column_span;
+ let cell_total;
+ {
+ let cell = kid.as_mut_table_cell();
+ row_span = cell.row_span;
+ column_span = cell.column_span as usize;
+ cell_total = cell.total_block_size();
+ }
+ let child_node = kid.mut_base();
+ child_node.position.start.b = Au(0);
+ let mut cell_block_size_pressure = max(cell_total, child_node.position.size.block);
+
+ if row_span != 1 {
+ if incoming_rowspan_data.len() <= col {
+ incoming_rowspan_data.resize(col + 1, Au(0));
+ }
+ let border_sizes_spanned =
+ get_spanned_border_size(border_info, row_index, &mut row_span);
+
+ cell_block_size_pressure -= border_sizes_spanned;
+
+ // XXXManishearth in case this row covers more than cell_block_size_pressure / row_span
+ // anyway, we should use that to reduce the pressure on future rows. This will
+ // require an extra slow-path loop, sadly.
+ cell_block_size_pressure /= row_span as i32;
+ incoming_rowspan_data[col] = cell_block_size_pressure;
+ }
+
+ max_block_size = max(max_block_size, cell_block_size_pressure);
+ col += column_span;
+ }
+ include_sizes_from_previous_rows(
+ &mut col,
+ &self.incoming_rowspan,
+ incoming_rowspan_data,
+ &mut max_block_size,
+ );
+
+ // TODO: Percentage block-size
+ let block_size = self
+ .block_flow
+ .fragment
+ .style()
+ .content_block_size()
+ .to_used_value(Au(0))
+ .unwrap_or(max_block_size);
+ max(block_size, max_block_size)
+ }
+
+ pub fn assign_block_size_to_self_and_children(
+ &mut self,
+ sizes: &[TableRowSizeData],
+ index: usize,
+ ) {
+ // Assign the block-size of kid fragments, which is the same value as own block-size.
+ let block_size = sizes[index].size;
+ for kid in self.block_flow.base.child_iter_mut() {
+ let child_table_cell = kid.as_mut_table_cell();
+ let block_size = if child_table_cell.row_span != 1 {
+ let mut row_span = child_table_cell.row_span;
+ let border_sizes_spanned = get_spanned_border_size(sizes, index, &mut row_span);
+ let row_sizes = sizes[index..]
+ .iter()
+ .take(row_span as usize)
+ .fold(Au(0), |accum, r| accum + r.size);
+ row_sizes + border_sizes_spanned
+ } else {
+ block_size
+ };
+ {
+ let kid_fragment = child_table_cell.mut_fragment();
+ let mut position = kid_fragment.border_box;
+ position.size.block = block_size;
+ kid_fragment.border_box = position;
+ }
+
+ // Assign the child's block size.
+ child_table_cell.block_flow.base.position.size.block = block_size;
+
+ // Now we know the cell height, vertical align the cell's children.
+ child_table_cell.valign_children();
+
+ // Write in the size of the relative containing block for children. (This
+ // information is also needed to handle RTL.)
+ child_table_cell
+ .block_flow
+ .base
+ .early_absolute_position_info = EarlyAbsolutePositionInfo {
+ relative_containing_block_size: self.block_flow.fragment.content_box().size,
+ relative_containing_block_mode: self.block_flow.fragment.style().writing_mode,
+ };
+ }
+
+ // Assign the block-size of own fragment
+ let mut position = self.block_flow.fragment.border_box;
+ position.size.block = block_size;
+ self.block_flow.fragment.border_box = position;
+ self.block_flow.base.position.size.block = block_size;
+ }
+
+ pub fn populate_collapsed_border_spacing<'a, I>(
+ &mut self,
+ collapsed_inline_direction_border_widths_for_table: &[Au],
+ collapsed_block_direction_border_widths_for_table: &mut Peekable<I>,
+ ) where
+ I: Iterator<Item = &'a Au>,
+ {
+ self.collapsed_border_spacing.inline.clear();
+ self.collapsed_border_spacing.inline.extend(
+ collapsed_inline_direction_border_widths_for_table
+ .into_iter()
+ .map(|x| *x),
+ );
+
+ if let Some(collapsed_block_direction_border_width_for_table) =
+ collapsed_block_direction_border_widths_for_table.next()
+ {
+ self.collapsed_border_spacing.block_start =
+ *collapsed_block_direction_border_width_for_table
+ }
+ if let Some(collapsed_block_direction_border_width_for_table) =
+ collapsed_block_direction_border_widths_for_table.peek()
+ {
+ self.collapsed_border_spacing.block_end =
+ **collapsed_block_direction_border_width_for_table
+ }
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct TableRowSizeData {
+ /// The block-size of the row.
+ pub size: Au,
+ /// Border spacing up to this row (not including spacing below the row)
+ pub cumulative_border_spacing: Au,
+ /// The "segment" of the table it is in. Tables containing
+ /// both row groups and rows have the bare rows grouped in
+ /// segments separated by row groups. It's helpful to look
+ /// at these as if they are rowgroups themselves.
+ ///
+ /// This is enough information for us to be able to check whether we
+ /// are in a case where we are overflowing a rowgroup with rowspan,
+ /// however calculating the amount of overflow requires lookahead.
+ pub rowgroup_id: u32,
+}
+
+/// Given an array of (_, cumulative_border_size), the index of the
+/// current row, and the >1 row_span of the cell, calculate the amount of
+/// border-spacing spanned by the row. In case the rowspan was larger
+/// than required, this will fix it up.
+fn get_spanned_border_size(sizes: &[TableRowSizeData], row_index: usize, row_span: &mut u32) -> Au {
+ // A zero rowspan is functionally equivalent to rowspan=infinity
+ if *row_span == 0 || row_index + *row_span as usize > sizes.len() {
+ *row_span = (sizes.len() - row_index) as u32;
+ }
+ let mut last_row_idx = row_index + *row_span as usize - 1;
+ // This is a slow path and should be rare -- this should only get triggered
+ // when you use `rowspan=0` or an overlarge rowspan in a table with
+ // mixed rows + rowgroups
+ if sizes[last_row_idx].rowgroup_id != sizes[row_index].rowgroup_id {
+ // XXXManishearth this loop can be avoided by also storing
+ // a "last_rowgroup_at" index so we can leapfrog back quickly
+ *row_span = sizes[row_index..last_row_idx + 1]
+ .iter()
+ .position(|s| s.rowgroup_id != sizes[row_index].rowgroup_id)
+ .unwrap() as u32;
+ last_row_idx = row_index + *row_span as usize - 1;
+ }
+ sizes[last_row_idx].cumulative_border_spacing - sizes[row_index].cumulative_border_spacing
+}
+
+impl Flow for TableRowFlow {
+ fn class(&self) -> FlowClass {
+ FlowClass::TableRow
+ }
+
+ fn as_mut_table_row(&mut self) -> &mut TableRowFlow {
+ self
+ }
+
+ fn as_table_row(&self) -> &TableRowFlow {
+ self
+ }
+
+ fn as_mut_block(&mut self) -> &mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn as_block(&self) -> &BlockFlow {
+ &self.block_flow
+ }
+
+ /// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When
+ /// called on this context, all child contexts have had their min/pref inline-sizes set. This
+ /// function must decide min/pref inline-sizes based on child context inline-sizes and
+ /// dimensions of any fragments it is responsible for flowing.
+ /// Min/pref inline-sizes set by this function are used in automatic table layout calculation.
+ /// The specified column inline-sizes of children cells are used in fixed table layout
+ /// calculation.
+ fn bubble_inline_sizes(&mut self) {
+ let _scope = layout_debug_scope!(
+ "table_row::bubble_inline_sizes {:x}",
+ self.block_flow.base.debug_id()
+ );
+
+ // Bubble up the specified inline-sizes from child table cells.
+ let (mut min_inline_size, mut pref_inline_size) = (Au(0), Au(0));
+ let collapsing_borders = self
+ .block_flow
+ .fragment
+ .style()
+ .get_inherited_table()
+ .border_collapse ==
+ BorderCollapse::Collapse;
+ let row_style = &*self.block_flow.fragment.style;
+ self.preliminary_collapsed_borders
+ .reset(CollapsedBorder::inline_start(
+ &row_style,
+ CollapsedBorderProvenance::FromTableRow,
+ ));
+
+ {
+ let children_count = self.block_flow.base.children.len();
+ let mut iterator = self.block_flow.base.child_iter_mut().enumerate().peekable();
+ while let Some((i, kid)) = iterator.next() {
+ assert!(kid.is_table_cell());
+
+ // Collect the specified column inline-size of the cell. This is used in both
+ // fixed and automatic table layout calculation.
+ let child_specified_inline_size;
+ let child_column_span;
+ let child_row_span;
+ {
+ let child_table_cell = kid.as_mut_table_cell();
+ child_specified_inline_size = child_table_cell
+ .block_flow
+ .fragment
+ .style
+ .content_inline_size();
+ child_column_span = child_table_cell.column_span;
+ child_row_span = child_table_cell.row_span;
+
+ // Perform border collapse if necessary.
+ if collapsing_borders {
+ perform_inline_direction_border_collapse_for_row(
+ row_style,
+ children_count,
+ i,
+ child_table_cell,
+ &mut iterator,
+ &mut self.preliminary_collapsed_borders,
+ )
+ }
+ }
+
+ // Collect minimum and preferred inline-sizes of the cell for automatic table layout
+ // calculation.
+ let child_base = kid.mut_base();
+ let child_column_inline_size = ColumnIntrinsicInlineSize {
+ minimum_length: match child_specified_inline_size {
+ Size::Auto => None,
+ Size::LengthPercentage(ref lp) => lp.0.maybe_to_used_value(None),
+ }
+ .unwrap_or(child_base.intrinsic_inline_sizes.minimum_inline_size),
+ percentage: match child_specified_inline_size {
+ Size::Auto => 0.0,
+ Size::LengthPercentage(ref lp) => lp.0.as_percentage().map_or(0.0, |p| p.0),
+ },
+ preferred: child_base.intrinsic_inline_sizes.preferred_inline_size,
+ constrained: match child_specified_inline_size {
+ Size::Auto => false,
+ Size::LengthPercentage(ref lp) => lp.0.maybe_to_used_value(None).is_some(),
+ },
+ };
+ min_inline_size = min_inline_size + child_column_inline_size.minimum_length;
+ pref_inline_size = pref_inline_size + child_column_inline_size.preferred;
+ self.cell_intrinsic_inline_sizes
+ .push(CellIntrinsicInlineSize {
+ column_size: child_column_inline_size,
+ column_span: child_column_span,
+ row_span: child_row_span,
+ });
+ }
+ }
+
+ self.block_flow
+ .base
+ .intrinsic_inline_sizes
+ .minimum_inline_size = min_inline_size;
+ self.block_flow
+ .base
+ .intrinsic_inline_sizes
+ .preferred_inline_size = max(min_inline_size, pref_inline_size);
+ }
+
+ fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
+ let _scope = layout_debug_scope!(
+ "table_row::assign_inline_sizes {:x}",
+ self.block_flow.base.debug_id()
+ );
+ debug!(
+ "assign_inline_sizes({}): assigning inline_size for flow",
+ "table_row"
+ );
+
+ let shared_context = layout_context.shared_context();
+ // The position was set to the containing block by the flow's parent.
+ let containing_block_inline_size = self.block_flow.base.block_container_inline_size;
+ // FIXME: In case of border-collapse: collapse, inline_start_content_edge should be
+ // border_inline_start.
+ let inline_start_content_edge = Au(0);
+ let inline_end_content_edge = Au(0);
+
+ let inline_size_computer = InternalTable;
+ inline_size_computer.compute_used_inline_size(
+ &mut self.block_flow,
+ shared_context,
+ containing_block_inline_size,
+ );
+
+ // Spread out the completed inline sizes among columns with spans > 1.
+ let num_columns = self.column_computed_inline_sizes.len();
+ let mut computed_inline_size_for_cells = Vec::with_capacity(num_columns);
+ let mut col = 0;
+
+ for cell_intrinsic_inline_size in &self.cell_intrinsic_inline_sizes {
+ // Skip any column occupied by a cell from a previous row.
+ while col < self.incoming_rowspan.len() && self.incoming_rowspan[col] != 1 {
+ let size = match self.column_computed_inline_sizes.get(col) {
+ Some(column_computed_inline_size) => *column_computed_inline_size,
+ None => ColumnComputedInlineSize { size: Au(0) }, // See FIXME below.
+ };
+ computed_inline_size_for_cells.push(size);
+ col += 1;
+ }
+ // Start with the computed inline size for the first column in the span.
+ let mut column_computed_inline_size = match self.column_computed_inline_sizes.get(col) {
+ Some(column_computed_inline_size) => *column_computed_inline_size,
+ None => {
+ // We're in fixed layout mode and there are more cells in this row than
+ // columns we know about. According to CSS 2.1 § 17.5.2.1, the behavior is
+ // now undefined. So just use zero.
+ //
+ // FIXME(pcwalton): $10 says this isn't Web compatible.
+ ColumnComputedInlineSize { size: Au(0) }
+ },
+ };
+ col += 1;
+
+ // Add in computed inline sizes for any extra columns in the span.
+ for _ in 1..cell_intrinsic_inline_size.column_span {
+ let extra_column_computed_inline_size =
+ match self.column_computed_inline_sizes.get(col) {
+ Some(column_computed_inline_size) => column_computed_inline_size,
+ None => break,
+ };
+ column_computed_inline_size.size = column_computed_inline_size.size +
+ extra_column_computed_inline_size.size +
+ self.spacing.horizontal();
+ col += 1;
+ }
+
+ computed_inline_size_for_cells.push(column_computed_inline_size)
+ }
+
+ // Set up border collapse info.
+ let border_collapse_info = match self
+ .block_flow
+ .fragment
+ .style()
+ .get_inherited_table()
+ .border_collapse
+ {
+ BorderCollapse::Collapse => Some(BorderCollapseInfoForChildTableCell {
+ collapsed_borders_for_row: &self.final_collapsed_borders,
+ collapsed_border_spacing_for_row: &self.collapsed_border_spacing,
+ }),
+ BorderCollapse::Separate => None,
+ };
+
+ // Push those inline sizes down to the cells.
+ let spacing = self.spacing;
+ let row_writing_mode = self.block_flow.base.writing_mode;
+ let table_writing_mode = self.table_writing_mode;
+ let incoming_rowspan = &self.incoming_rowspan;
+ let mut column_index = 0;
+
+ self.block_flow.propagate_assigned_inline_size_to_children(
+ shared_context,
+ inline_start_content_edge,
+ inline_end_content_edge,
+ containing_block_inline_size,
+ |child_flow,
+ child_index,
+ content_inline_size,
+ _writing_mode,
+ inline_start_margin_edge,
+ inline_end_margin_edge| {
+ set_inline_position_of_child_flow(
+ child_flow,
+ child_index,
+ &mut column_index,
+ incoming_rowspan,
+ row_writing_mode,
+ table_writing_mode,
+ &computed_inline_size_for_cells,
+ &spacing,
+ &border_collapse_info,
+ content_inline_size,
+ inline_start_margin_edge,
+ inline_end_margin_edge,
+ );
+ },
+ )
+ }
+
+ fn assign_block_size(&mut self, _: &LayoutContext) {
+ // the surrounding table or rowgroup does this
+ }
+
+ fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
+ self.block_flow
+ .compute_stacking_relative_position(layout_context)
+ }
+
+ 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, _: &mut DisplayListBuildState) {
+ use style::servo::restyle_damage::ServoRestyleDamage;
+ // handled in TableCellStyleInfo::build_display_list
+ // we skip setting the damage in TableCellStyleInfo::build_display_list()
+ // because we only have immutable access
+ self.block_flow
+ .fragment
+ .restyle_damage
+ .remove(ServoRestyleDamage::REPAINT);
+ }
+
+ fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
+ self.block_flow
+ .collect_stacking_contexts_for_block(state, StackingContextCollectionFlags::empty());
+ }
+
+ 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 contains_roots_of_absolute_flow_tree(&self) -> bool {
+ self.block_flow.contains_roots_of_absolute_flow_tree()
+ }
+
+ fn is_absolute_containing_block(&self) -> bool {
+ self.block_flow.is_absolute_containing_block()
+ }
+
+ fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
+ self.block_flow.generated_containing_block_size(flow)
+ }
+
+ 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);
+ }
+}
+
+impl fmt::Debug for TableRowFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "TableRowFlow: {:?}", self.block_flow)
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct CollapsedBordersForRow {
+ /// The size of this vector should be equal to the number of cells plus one.
+ pub inline: Vec<CollapsedBorder>,
+ /// The size of this vector should be equal to the number of cells.
+ pub block_start: Vec<CollapsedBorder>,
+ /// The size of this vector should be equal to the number of cells.
+ pub block_end: Vec<CollapsedBorder>,
+}
+
+impl CollapsedBordersForRow {
+ pub fn new() -> CollapsedBordersForRow {
+ CollapsedBordersForRow {
+ inline: Vec::new(),
+ block_start: Vec::new(),
+ block_end: Vec::new(),
+ }
+ }
+
+ pub fn reset(&mut self, first_inline_border: CollapsedBorder) {
+ self.inline.clear();
+ self.inline.push(first_inline_border);
+ self.block_start.clear();
+ self.block_end.clear()
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct CollapsedBorderSpacingForRow {
+ /// The spacing in between each column.
+ inline: Vec<Au>,
+ /// The spacing above this row.
+ pub block_start: Au,
+ /// The spacing below this row.
+ block_end: Au,
+}
+
+impl CollapsedBorderSpacingForRow {
+ fn new() -> CollapsedBorderSpacingForRow {
+ CollapsedBorderSpacingForRow {
+ inline: Vec::new(),
+ block_start: Au(0),
+ block_end: Au(0),
+ }
+ }
+}
+
+/// All aspects of a border that can collapse with adjacent borders. See CSS 2.1 § 17.6.2.1.
+#[derive(Clone, Copy, Debug)]
+pub struct CollapsedBorder {
+ /// The style of the border.
+ pub style: BorderStyle,
+ /// The width of the border.
+ pub width: Au,
+ /// The color of the border.
+ pub color: Color,
+ /// The type of item that this border comes from.
+ pub provenance: CollapsedBorderProvenance,
+}
+
+impl Serialize for CollapsedBorder {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ serializer.serialize_unit()
+ }
+}
+
+/// Where a border style comes from.
+///
+/// The integer values here correspond to the border conflict resolution rules in CSS 2.1 §
+/// 17.6.2.1. Higher values override lower values.
+// FIXME(#8586): FromTableRow, FromTableRowGroup, FromTableColumn,
+// FromTableColumnGroup are unused
+#[allow(dead_code)]
+#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
+pub enum CollapsedBorderProvenance {
+ FromPreviousTableCell = 6,
+ FromNextTableCell = 5,
+ FromTableRow = 4,
+ FromTableRowGroup = 3,
+ FromTableColumn = 2,
+ FromTableColumnGroup = 1,
+ FromTable = 0,
+}
+
+impl CollapsedBorder {
+ /// Creates a collapsible border style for no border.
+ pub fn new() -> CollapsedBorder {
+ CollapsedBorder {
+ style: BorderStyle::None,
+ width: Au(0),
+ color: Color::transparent(),
+ provenance: CollapsedBorderProvenance::FromTable,
+ }
+ }
+
+ /// Creates a collapsed border from the block-start border described in the given CSS style
+ /// object.
+ fn top(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) -> CollapsedBorder {
+ CollapsedBorder {
+ style: css_style.get_border().border_top_style,
+ width: Au::from(css_style.get_border().border_top_width),
+ color: css_style.get_border().border_top_color,
+ provenance: provenance,
+ }
+ }
+
+ /// Creates a collapsed border style from the right border described in the given CSS style
+ /// object.
+ fn right(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) -> CollapsedBorder {
+ CollapsedBorder {
+ style: css_style.get_border().border_right_style,
+ width: Au::from(css_style.get_border().border_right_width),
+ color: css_style.get_border().border_right_color,
+ provenance: provenance,
+ }
+ }
+
+ /// Creates a collapsed border style from the bottom border described in the given CSS style
+ /// object.
+ fn bottom(
+ css_style: &ComputedValues,
+ provenance: CollapsedBorderProvenance,
+ ) -> CollapsedBorder {
+ CollapsedBorder {
+ style: css_style.get_border().border_bottom_style,
+ width: Au::from(css_style.get_border().border_bottom_width),
+ color: css_style.get_border().border_bottom_color,
+ provenance: provenance,
+ }
+ }
+
+ /// Creates a collapsed border style from the left border described in the given CSS style
+ /// object.
+ fn left(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) -> CollapsedBorder {
+ CollapsedBorder {
+ style: css_style.get_border().border_left_style,
+ width: Au::from(css_style.get_border().border_left_width),
+ color: css_style.get_border().border_left_color,
+ provenance: provenance,
+ }
+ }
+
+ /// Creates a collapsed border style from the given physical side.
+ fn from_side(
+ side: PhysicalSide,
+ css_style: &ComputedValues,
+ provenance: CollapsedBorderProvenance,
+ ) -> CollapsedBorder {
+ match side {
+ PhysicalSide::Top => CollapsedBorder::top(css_style, provenance),
+ PhysicalSide::Right => CollapsedBorder::right(css_style, provenance),
+ PhysicalSide::Bottom => CollapsedBorder::bottom(css_style, provenance),
+ PhysicalSide::Left => CollapsedBorder::left(css_style, provenance),
+ }
+ }
+
+ /// Creates a collapsed border style from the inline-start border described in the given CSS
+ /// style object.
+ pub fn inline_start(
+ css_style: &ComputedValues,
+ provenance: CollapsedBorderProvenance,
+ ) -> CollapsedBorder {
+ CollapsedBorder::from_side(
+ css_style.writing_mode.inline_start_physical_side(),
+ css_style,
+ provenance,
+ )
+ }
+
+ /// Creates a collapsed border style from the inline-start border described in the given CSS
+ /// style object.
+ pub fn inline_end(
+ css_style: &ComputedValues,
+ provenance: CollapsedBorderProvenance,
+ ) -> CollapsedBorder {
+ CollapsedBorder::from_side(
+ css_style.writing_mode.inline_end_physical_side(),
+ css_style,
+ provenance,
+ )
+ }
+
+ /// Creates a collapsed border style from the block-start border described in the given CSS
+ /// style object.
+ pub fn block_start(
+ css_style: &ComputedValues,
+ provenance: CollapsedBorderProvenance,
+ ) -> CollapsedBorder {
+ CollapsedBorder::from_side(
+ css_style.writing_mode.block_start_physical_side(),
+ css_style,
+ provenance,
+ )
+ }
+
+ /// Creates a collapsed border style from the block-end border described in the given CSS style
+ /// object.
+ pub fn block_end(
+ css_style: &ComputedValues,
+ provenance: CollapsedBorderProvenance,
+ ) -> CollapsedBorder {
+ CollapsedBorder::from_side(
+ css_style.writing_mode.block_end_physical_side(),
+ css_style,
+ provenance,
+ )
+ }
+
+ /// If `other` has a higher priority per CSS 2.1 § 17.6.2.1, replaces `self` with it.
+ pub fn combine(&mut self, other: &CollapsedBorder) {
+ match (self.style, other.style) {
+ // Step 1.
+ (BorderStyle::Hidden, _) => {},
+ (_, BorderStyle::Hidden) => *self = *other,
+ // Step 2.
+ (BorderStyle::None, _) => *self = *other,
+ (_, BorderStyle::None) => {},
+ // Step 3.
+ _ if self.width > other.width => {},
+ _ if self.width < other.width => *self = *other,
+ (this_style, other_style) if this_style > other_style => {},
+ (this_style, other_style) if this_style < other_style => *self = *other,
+ // Step 4.
+ _ if (self.provenance as i8) >= other.provenance as i8 => {},
+ _ => *self = *other,
+ }
+ }
+}
+
+/// Pushes column inline size, incoming rowspan, and border collapse info down to a child.
+pub fn propagate_column_inline_sizes_to_child(
+ child_flow: &mut dyn Flow,
+ table_writing_mode: WritingMode,
+ column_computed_inline_sizes: &[ColumnComputedInlineSize],
+ border_spacing: &BorderSpacing,
+ incoming_rowspan: &mut Vec<u32>,
+) {
+ // If the child is a row group or a row, the column inline-size and rowspan info should be copied from its
+ // parent.
+ //
+ // FIXME(pcwalton): This seems inefficient. Reference count it instead?
+ match child_flow.class() {
+ FlowClass::TableRowGroup => {
+ incoming_rowspan.clear();
+ let child_table_rowgroup_flow = child_flow.as_mut_table_rowgroup();
+ child_table_rowgroup_flow.spacing = *border_spacing;
+ for kid in child_table_rowgroup_flow.block_flow.base.child_iter_mut() {
+ propagate_column_inline_sizes_to_child(
+ kid,
+ table_writing_mode,
+ column_computed_inline_sizes,
+ border_spacing,
+ incoming_rowspan,
+ );
+ }
+ },
+ FlowClass::TableRow => {
+ let child_table_row_flow = child_flow.as_mut_table_row();
+ child_table_row_flow.column_computed_inline_sizes =
+ column_computed_inline_sizes.to_vec();
+ child_table_row_flow.spacing = *border_spacing;
+ child_table_row_flow.table_writing_mode = table_writing_mode;
+ child_table_row_flow.incoming_rowspan = incoming_rowspan.clone();
+
+ // Update the incoming rowspan for the next row.
+ let mut col = 0;
+ for cell in &child_table_row_flow.cell_intrinsic_inline_sizes {
+ // Skip any column occupied by a cell from a previous row.
+ while col < incoming_rowspan.len() && incoming_rowspan[col] != 1 {
+ if incoming_rowspan[col] > 1 {
+ incoming_rowspan[col] -= 1;
+ }
+ col += 1;
+ }
+ for _ in 0..cell.column_span {
+ if col < incoming_rowspan.len() && incoming_rowspan[col] > 1 {
+ incoming_rowspan[col] -= 1;
+ }
+ // If this cell spans later rows, record its rowspan.
+ if cell.row_span != 1 {
+ if incoming_rowspan.len() < col + 1 {
+ incoming_rowspan.resize(col + 1, 1);
+ }
+ // HTML § 4.9.11: For rowspan, the value 0 means the cell is to span all
+ // the remaining rows in the rowgroup.
+ if cell.row_span > incoming_rowspan[col] || cell.row_span == 0 {
+ incoming_rowspan[col] = cell.row_span;
+ }
+ }
+ col += 1;
+ }
+ }
+ },
+ c => warn!("unexpected flow in table {:?}", c),
+ }
+}
+
+/// Lay out table cells inline according to the computer column sizes.
+fn set_inline_position_of_child_flow(
+ child_flow: &mut dyn Flow,
+ child_index: usize,
+ column_index: &mut usize,
+ incoming_rowspan: &[u32],
+ row_writing_mode: WritingMode,
+ table_writing_mode: WritingMode,
+ column_computed_inline_sizes: &[ColumnComputedInlineSize],
+ border_spacing: &BorderSpacing,
+ border_collapse_info: &Option<BorderCollapseInfoForChildTableCell>,
+ parent_content_inline_size: Au,
+ inline_start_margin_edge: &mut Au,
+ inline_end_margin_edge: &mut Au,
+) {
+ if !child_flow.is_table_cell() {
+ return;
+ }
+
+ let reverse_column_order = table_writing_mode.is_bidi_ltr() != row_writing_mode.is_bidi_ltr();
+
+ // Advance past any column occupied by a cell from a previous row.
+ while *column_index < incoming_rowspan.len() && incoming_rowspan[*column_index] != 1 {
+ let column_inline_size = column_computed_inline_sizes[*column_index].size;
+ let border_inline_size = match *border_collapse_info {
+ Some(_) => Au(0), // FIXME: Make collapsed borders account for colspan/rowspan.
+ None => border_spacing.horizontal(),
+ };
+ if reverse_column_order {
+ *inline_end_margin_edge += column_inline_size + border_inline_size;
+ } else {
+ *inline_start_margin_edge += column_inline_size + border_inline_size;
+ }
+ *column_index += 1;
+ }
+
+ // Handle border collapsing, if necessary.
+ let child_table_cell = child_flow.as_mut_table_cell();
+ match *border_collapse_info {
+ Some(ref border_collapse_info) => {
+ // Write in the child's border collapse state.
+ child_table_cell.collapsed_borders = CollapsedBordersForCell {
+ inline_start_border: border_collapse_info
+ .collapsed_borders_for_row
+ .inline
+ .get(child_index)
+ .map_or(CollapsedBorder::new(), |x| *x),
+ inline_end_border: border_collapse_info
+ .collapsed_borders_for_row
+ .inline
+ .get(child_index + 1)
+ .map_or(CollapsedBorder::new(), |x| *x),
+ block_start_border: border_collapse_info
+ .collapsed_borders_for_row
+ .block_start
+ .get(child_index)
+ .map_or(CollapsedBorder::new(), |x| *x),
+ block_end_border: border_collapse_info
+ .collapsed_borders_for_row
+ .block_end
+ .get(child_index)
+ .map_or(CollapsedBorder::new(), |x| *x),
+ inline_start_width: border_collapse_info
+ .collapsed_border_spacing_for_row
+ .inline
+ .get(child_index)
+ .map_or(Au(0), |x| *x),
+ inline_end_width: border_collapse_info
+ .collapsed_border_spacing_for_row
+ .inline
+ .get(child_index + 1)
+ .map_or(Au(0), |x| *x),
+ block_start_width: border_collapse_info
+ .collapsed_border_spacing_for_row
+ .block_start,
+ block_end_width: border_collapse_info
+ .collapsed_border_spacing_for_row
+ .block_end,
+ };
+
+ // Move over past the collapsed border.
+ if reverse_column_order {
+ *inline_end_margin_edge += child_table_cell.collapsed_borders.inline_start_width;
+ } else {
+ *inline_start_margin_edge += child_table_cell.collapsed_borders.inline_start_width;
+ }
+ },
+ None => {
+ // Take spacing into account.
+ if reverse_column_order {
+ *inline_end_margin_edge += border_spacing.horizontal();
+ } else {
+ *inline_start_margin_edge += border_spacing.horizontal();
+ }
+ },
+ }
+
+ let column_inline_size = column_computed_inline_sizes[*column_index].size;
+ *column_index += 1;
+
+ let kid_base = &mut child_table_cell.block_flow.base;
+ kid_base.block_container_inline_size = column_inline_size;
+
+ if reverse_column_order {
+ // Columns begin from the inline-end edge.
+ kid_base.position.start.i =
+ parent_content_inline_size - *inline_end_margin_edge - column_inline_size;
+ *inline_end_margin_edge += column_inline_size;
+ } else {
+ // Columns begin from the inline-start edge.
+ kid_base.position.start.i = *inline_start_margin_edge;
+ *inline_start_margin_edge += column_inline_size;
+ }
+}
+
+#[derive(Clone, Copy)]
+pub struct BorderCollapseInfoForChildTableCell<'a> {
+ collapsed_borders_for_row: &'a CollapsedBordersForRow,
+ collapsed_border_spacing_for_row: &'a CollapsedBorderSpacingForRow,
+}
+
+/// Performs border-collapse in the inline direction for all the cells' inside borders in the
+/// inline-direction cells and propagates the outside borders (the far left and right) up to the
+/// table row. This is done eagerly here so that at least the inline inside border collapse
+/// computations can be parallelized across all the rows of the table.
+fn perform_inline_direction_border_collapse_for_row(
+ row_style: &ComputedValues,
+ children_count: usize,
+ child_index: usize,
+ child_table_cell: &mut TableCellFlow,
+ iterator: &mut Peekable<Enumerate<MutFlowListIterator>>,
+ preliminary_collapsed_borders: &mut CollapsedBordersForRow,
+) {
+ // In the first cell, combine its border with the one coming from the row.
+ if child_index == 0 {
+ let first_inline_border = &mut preliminary_collapsed_borders.inline[0];
+ first_inline_border.combine(&CollapsedBorder::inline_start(
+ &*child_table_cell.block_flow.fragment.style,
+ CollapsedBorderProvenance::FromNextTableCell,
+ ));
+ }
+
+ let inline_collapsed_border = preliminary_collapsed_borders.inline.push_or_set(
+ child_index + 1,
+ CollapsedBorder::inline_end(
+ &*child_table_cell.block_flow.fragment.style,
+ CollapsedBorderProvenance::FromPreviousTableCell,
+ ),
+ );
+
+ if let Some(&(_, ref next_child_flow)) = iterator.peek() {
+ let next_child_flow = next_child_flow.as_block();
+ inline_collapsed_border.combine(&CollapsedBorder::inline_start(
+ &*next_child_flow.fragment.style,
+ CollapsedBorderProvenance::FromNextTableCell,
+ ))
+ };
+
+ // In the last cell, also take into account the border that may
+ // come from the row.
+ if child_index + 1 == children_count {
+ inline_collapsed_border.combine(&CollapsedBorder::inline_end(
+ &row_style,
+ CollapsedBorderProvenance::FromTableRow,
+ ));
+ }
+
+ let mut block_start_border = CollapsedBorder::block_start(
+ &*child_table_cell.block_flow.fragment.style,
+ CollapsedBorderProvenance::FromNextTableCell,
+ );
+ block_start_border.combine(&CollapsedBorder::block_start(
+ row_style,
+ CollapsedBorderProvenance::FromTableRow,
+ ));
+ preliminary_collapsed_borders
+ .block_start
+ .push_or_set(child_index, block_start_border);
+ let mut block_end_border = CollapsedBorder::block_end(
+ &*child_table_cell.block_flow.fragment.style,
+ CollapsedBorderProvenance::FromPreviousTableCell,
+ );
+ block_end_border.combine(&CollapsedBorder::block_end(
+ row_style,
+ CollapsedBorderProvenance::FromTableRow,
+ ));
+
+ preliminary_collapsed_borders
+ .block_end
+ .push_or_set(child_index, block_end_border);
+}