mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Auto merge of #20128 - Manishearth:rowspan, r=mbrubeck
Rowspan support for tables fixes #20092 This just contains the first steps. We apply a naive algorithm: Spanning cells apply a pressure equal to `block_size / rowspan` on each row they are in. We move table row block size computation into the tables, and make it two pass. In the first pass we compute the sizes of each row, and in the second pass we assign them, adding them up for any involved cells. This is missing: - [x] Accounting for border sizes - [x] Applying pressure to rows that are not the row containing the cell - [ ] Reducing pressure on future rows if the current row is able to accomodate more of the cell - [x] For tables containing both rows and rowgroups, reset the rowspan info when we hit a rowgroup - [x] Correctly handle overflowing rowspans cc @mbrubeck @pcwalton <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/20128) <!-- Reviewable:end -->
This commit is contained in:
commit
e2f2814018
8 changed files with 300 additions and 108 deletions
|
@ -496,10 +496,10 @@ impl Flow for TableFlow {
|
|||
});
|
||||
}
|
||||
|
||||
fn assign_block_size(&mut self, _: &LayoutContext) {
|
||||
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)
|
||||
self.block_flow.assign_block_size_for_table_like_flow(vertical_spacing, lc)
|
||||
}
|
||||
|
||||
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
|
||||
|
@ -567,13 +567,6 @@ impl Flow for TableFlow {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
// XXXManishearth We might be able to avoid the Arc<T>s if
|
||||
// the table is structured such that the columns always come
|
||||
// first in the flow tree, at which point we can
|
||||
// reuse the iterator that we use for colgroups
|
||||
// for rows (and have no borrowing issues between
|
||||
// holding on to both ColumnStyle<'table> and
|
||||
// the rows)
|
||||
struct ColumnStyle<'table> {
|
||||
span: u32,
|
||||
colgroup_style: Option<&'table ComputedValues>,
|
||||
|
@ -771,34 +764,95 @@ fn perform_border_collapse_for_row(child_table_row: &mut TableRowFlow,
|
|||
/// 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);
|
||||
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) {
|
||||
fn assign_block_size_for_table_like_flow(&mut self, block_direction_spacing: Au,
|
||||
layout_context: &LayoutContext) {
|
||||
debug_assert!(self.fragment.style.get_inheritedtable().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_inheritedtable().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) {
|
||||
// (size, cumulative_border_spacing)
|
||||
let mut sizes = vec![(Au(0), Au(0))];
|
||||
// The amount of border spacing up to and including this row,
|
||||
// but not including the spacing beneath it
|
||||
let mut cumulative_border = Au(0);
|
||||
let mut incoming_rowspan_data = vec![];
|
||||
|
||||
// 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()
|
||||
.filter(|k| k.is_table_row())
|
||||
.skip(1) {
|
||||
cumulative_border +=
|
||||
border_spacing_for_row(&self.fragment, kid.as_table_row(),
|
||||
block_direction_spacing);
|
||||
// we haven't calculated sizes yet
|
||||
sizes.push((Au(0), cumulative_border));
|
||||
}
|
||||
|
||||
// Second pass: Compute row block sizes
|
||||
// [expensive: iterates over cells]
|
||||
let mut i = 0;
|
||||
let mut overflow = Au(0);
|
||||
for kid in self.base.child_iter_mut() {
|
||||
if kid.is_table_row() {
|
||||
let (size, oflo) = kid.as_mut_table_row()
|
||||
.compute_block_size_table_row_base(layout_context,
|
||||
&mut incoming_rowspan_data,
|
||||
&sizes,
|
||||
i);
|
||||
sizes[i].0 = size;
|
||||
overflow = oflo;
|
||||
i += 1;
|
||||
// new rowgroups stop rowspans
|
||||
} else if kid.is_table_rowgroup() {
|
||||
if i > 0 {
|
||||
sizes[i - 1].0 = cmp::max(sizes[i - 1].0, overflow);
|
||||
}
|
||||
}
|
||||
}
|
||||
if i > 0 {
|
||||
sizes[i - 1].0 = cmp::max(sizes[i - 1].0, overflow);
|
||||
}
|
||||
|
||||
|
||||
// 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() {
|
||||
// Account for spacing or collapsed borders.
|
||||
if kid.is_table_row() {
|
||||
has_rows = true;
|
||||
let child_table_row = kid.as_table_row();
|
||||
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 +
|
||||
match self.fragment.style.get_inheritedtable().border_collapse {
|
||||
border_collapse::T::Separate => block_direction_spacing,
|
||||
border_collapse::T::Collapse => {
|
||||
child_table_row.collapsed_border_spacing.block_start
|
||||
}
|
||||
}
|
||||
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.
|
||||
|
@ -843,6 +897,7 @@ impl TableLikeFlow for BlockFlow {
|
|||
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() {
|
||||
|
|
|
@ -148,6 +148,17 @@ impl TableCellFlow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Total block size of child
|
||||
//
|
||||
// Call after block size calculation
|
||||
pub fn total_block_size(&mut self) -> Au {
|
||||
// TODO: Percentage block-size
|
||||
let specified = MaybeAuto::from_style(self.fragment().style()
|
||||
.content_block_size(),
|
||||
Au(0)).specified_or_zero();
|
||||
specified + self.fragment().border_padding.block_start_end()
|
||||
}
|
||||
}
|
||||
|
||||
impl Flow for TableCellFlow {
|
||||
|
|
|
@ -19,7 +19,7 @@ use gfx_traits::print_tree::PrintTree;
|
|||
use layout_debug;
|
||||
use model::MaybeAuto;
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::cmp::max;
|
||||
use std::cmp::{max, min};
|
||||
use std::fmt;
|
||||
use std::iter::{Enumerate, IntoIterator, Peekable};
|
||||
use style::computed_values::border_collapse::T as BorderCollapse;
|
||||
|
@ -27,7 +27,6 @@ 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::servo::restyle_damage::ServoRestyleDamage;
|
||||
use style::values::computed::{Color, LengthOrPercentageOrAuto};
|
||||
use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, InternalTable, VecExt};
|
||||
use table_cell::{CollapsedBordersForCell, TableCellFlow};
|
||||
|
@ -107,96 +106,154 @@ impl TableRowFlow {
|
|||
}
|
||||
}
|
||||
|
||||
/// Assign block-size for table-row flow.
|
||||
/// Compute block-size for table-row flow.
|
||||
///
|
||||
/// TODO(pcwalton): This doesn't handle floats and positioned elements right.
|
||||
///
|
||||
/// inline(always) because this is only ever called by in-order or non-in-order top-level
|
||||
/// methods
|
||||
#[inline(always)]
|
||||
fn assign_block_size_table_row_base(&mut self, layout_context: &LayoutContext) {
|
||||
if self.block_flow.base.restyle_damage.contains(ServoRestyleDamage::REFLOW) {
|
||||
// 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;
|
||||
for kid in self.block_flow.base.child_iter_mut() {
|
||||
kid.place_float_if_applicable();
|
||||
if !kid.base().flags.is_float() {
|
||||
kid.assign_block_size_for_inorder_child_if_necessary(layout_context,
|
||||
thread_id,
|
||||
content_box);
|
||||
/// Returns the block size, as well as the size this should be if this is the last row
|
||||
pub fn compute_block_size_table_row_base<'a>(&'a mut self, layout_context: &LayoutContext,
|
||||
incoming_rowspan_data: &mut Vec<Au>,
|
||||
border_info: &[(Au, Au)], // (_, cumulative_border_size)
|
||||
row_index: usize) -> (Au, Au) {
|
||||
fn include_sizes_from_previous_rows(col: &mut usize,
|
||||
incoming_rowspan: &[u32],
|
||||
incoming_rowspan_data: &mut Vec<Au>,
|
||||
max_block_size: &mut Au,
|
||||
largest_leftover_incoming_size: &mut Au) {
|
||||
while let Some(span) = incoming_rowspan.get(*col) {
|
||||
if *span <= 1 {
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
let child_fragment = kid.as_mut_table_cell().fragment();
|
||||
// TODO: Percentage block-size
|
||||
let child_specified_block_size =
|
||||
MaybeAuto::from_style(child_fragment.style().content_block_size(),
|
||||
Au(0)).specified_or_zero();
|
||||
max_block_size =
|
||||
max(max_block_size,
|
||||
child_specified_block_size +
|
||||
child_fragment.border_padding.block_start_end());
|
||||
let incoming = incoming_rowspan_data[*col];
|
||||
*max_block_size = max(*max_block_size, incoming);
|
||||
if *span > 2 {
|
||||
*largest_leftover_incoming_size = max(*largest_leftover_incoming_size,
|
||||
incoming * (*span - 1) as i32)
|
||||
}
|
||||
let child_node = kid.mut_base();
|
||||
child_node.position.start.b = Au(0);
|
||||
max_block_size = max(max_block_size, child_node.position.size.block);
|
||||
}
|
||||
|
||||
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),
|
||||
};
|
||||
|
||||
// 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;
|
||||
|
||||
// Assign the block-size of kid fragments, which is the same value as own block-size.
|
||||
for kid in self.block_flow.base.child_iter_mut() {
|
||||
let child_table_cell = kid.as_mut_table_cell();
|
||||
{
|
||||
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,
|
||||
};
|
||||
*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 mut largest_leftover_incoming_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;
|
||||
|
||||
self.block_flow.base.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
|
||||
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,
|
||||
&mut largest_leftover_incoming_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 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, row_span);
|
||||
let pressure_copy = cell_block_size_pressure;
|
||||
|
||||
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;
|
||||
|
||||
// If this ends up being the last row, it needs to cover
|
||||
// *all* this space
|
||||
largest_leftover_incoming_size = max(largest_leftover_incoming_size,
|
||||
pressure_copy);
|
||||
}
|
||||
|
||||
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,
|
||||
&mut largest_leftover_incoming_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, largest_leftover_incoming_size)
|
||||
}
|
||||
|
||||
pub fn assign_block_size_to_self_and_children(&mut self, sizes: &[(Au, Au)], index: usize) {
|
||||
// Assign the block-size of kid fragments, which is the same value as own block-size.
|
||||
let block_size = sizes[index].0;
|
||||
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 row_sizes = sizes[index..].iter()
|
||||
.take(child_table_cell.row_span as usize)
|
||||
.fold(Au(0), |accum, size| accum + size.0);
|
||||
let border_sizes_spanned =
|
||||
get_spanned_border_size(sizes, index, child_table_cell.row_span);
|
||||
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>(
|
||||
|
@ -222,6 +279,14 @@ impl TableRowFlow {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
fn get_spanned_border_size(sizes: &[(Au, Au)], row_index: usize, row_span: u32) -> Au {
|
||||
let last_row_idx = min(row_index + row_span as usize - 1, sizes.len() - 1);
|
||||
sizes[last_row_idx].1 - sizes[row_index].1
|
||||
}
|
||||
|
||||
impl Flow for TableRowFlow {
|
||||
fn class(&self) -> FlowClass {
|
||||
FlowClass::TableRow
|
||||
|
@ -449,9 +514,8 @@ impl Flow for TableRowFlow {
|
|||
})
|
||||
}
|
||||
|
||||
fn assign_block_size(&mut self, layout_context: &LayoutContext) {
|
||||
debug!("assign_block_size: assigning block_size for table_row");
|
||||
self.assign_block_size_table_row_base(layout_context);
|
||||
fn assign_block_size(&mut self, _: &LayoutContext) {
|
||||
// the surrounding table or rowgroup does this
|
||||
}
|
||||
|
||||
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
|
||||
|
|
|
@ -159,9 +159,9 @@ impl Flow for TableRowGroupFlow {
|
|||
});
|
||||
}
|
||||
|
||||
fn assign_block_size(&mut self, _: &LayoutContext) {
|
||||
fn assign_block_size(&mut self, lc: &LayoutContext) {
|
||||
debug!("assign_block_size: assigning block_size for table_rowgroup");
|
||||
self.block_flow.assign_block_size_for_table_like_flow(self.spacing.vertical());
|
||||
self.block_flow.assign_block_size_for_table_like_flow(self.spacing.vertical(), lc);
|
||||
}
|
||||
|
||||
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue