Blob Blame History Raw
/* 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 http://mozilla.org/MPL/2.0/. */

//! CSS table formatting contexts.

#![deny(unsafe_code)]

use app_units::Au;
use block::{BlockFlow, ISizeAndMarginsComputer};
use context::LayoutContext;
use display_list::{BlockFlowDisplayListBuilding, DisplayListBuildState};
use display_list::{StackingContextCollectionFlags, StackingContextCollectionState};
use euclid::Point2D;
use flow::{EarlyAbsolutePositionInfo, Flow, FlowClass, ImmutableFlowUtils, GetBaseFlow, OpaqueFlow};
use flow_list::MutFlowListIterator;
use fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
use gfx_traits::print_tree::PrintTree;
use layout_debug;
use model::MaybeAuto;
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, LengthOrPercentageOrAuto};
use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, InternalTable, VecExt};
use table_cell::{CollapsedBordersForCell, TableCellFlow};

#[allow(unsafe_code)]
unsafe impl ::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);

        let mut block_size = max_block_size;
        // TODO: Percentage block-size
        block_size = match MaybeAuto::from_style(self.block_flow
                                                     .fragment
                                                     .style()
                                                     .content_block_size(),
                                                 Au(0)) {
            MaybeAuto::Auto => block_size,
            MaybeAuto::Specified(value) => max(value, block_size),
        };
        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_inheritedtable()
                                     .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 {
                        LengthOrPercentageOrAuto::Auto |
                        LengthOrPercentageOrAuto::Calc(_) |
                        LengthOrPercentageOrAuto::Percentage(_) => {
                            child_base.intrinsic_inline_sizes.minimum_inline_size
                        }
                        LengthOrPercentageOrAuto::Length(length) => Au::from(length),
                    },
                    percentage: match child_specified_inline_size {
                        LengthOrPercentageOrAuto::Auto |
                        LengthOrPercentageOrAuto::Calc(_) |
                        LengthOrPercentageOrAuto::Length(_) => 0.0,
                        LengthOrPercentageOrAuto::Percentage(percentage) => percentage.0,
                    },
                    preferred: child_base.intrinsic_inline_sizes.preferred_inline_size,
                    constrained: match child_specified_inline_size {
                        LengthOrPercentageOrAuto::Length(_) => true,
                        LengthOrPercentageOrAuto::Auto |
                        LengthOrPercentageOrAuto::Calc(_) |
                        LengthOrPercentageOrAuto::Percentage(_) => false,
                    },
                };
                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_inheritedtable().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: &::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 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 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 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 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);
}