mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
layout: Start work on table row height and vertical-align (#31246)
This implements a very naive row height allocation approach. It has just enough to implement `vertical-align` in table cells. Rowspanned cells get enough space for their content, with the extra space necessary being allocated to the last row. There's still a lot missing here, including proper distribution of row height to rowspanned cells. Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
parent
39b3beda5d
commit
35fb95ca85
21 changed files with 261 additions and 132 deletions
|
@ -13,6 +13,7 @@ use style::computed_values::float::T as Float;
|
||||||
use style::logical_geometry::WritingMode;
|
use style::logical_geometry::WritingMode;
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use style::values::computed::{Length, LengthOrAuto};
|
use style::values::computed::{Length, LengthOrAuto};
|
||||||
|
use style::values::specified::Display;
|
||||||
use style::Zero;
|
use style::Zero;
|
||||||
|
|
||||||
use crate::cell::ArcRefCell;
|
use crate::cell::ArcRefCell;
|
||||||
|
@ -418,22 +419,33 @@ fn layout_block_level_children(
|
||||||
mut sequential_layout_state: Option<&mut SequentialLayoutState>,
|
mut sequential_layout_state: Option<&mut SequentialLayoutState>,
|
||||||
collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
|
collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
|
||||||
) -> FlowLayout {
|
) -> FlowLayout {
|
||||||
match sequential_layout_state {
|
let mut placement_state =
|
||||||
|
PlacementState::new(collapsible_with_parent_start_margin, containing_block.style);
|
||||||
|
|
||||||
|
let fragments = match sequential_layout_state {
|
||||||
Some(ref mut sequential_layout_state) => layout_block_level_children_sequentially(
|
Some(ref mut sequential_layout_state) => layout_block_level_children_sequentially(
|
||||||
layout_context,
|
layout_context,
|
||||||
positioning_context,
|
positioning_context,
|
||||||
child_boxes,
|
child_boxes,
|
||||||
containing_block,
|
containing_block,
|
||||||
sequential_layout_state,
|
sequential_layout_state,
|
||||||
collapsible_with_parent_start_margin,
|
&mut placement_state,
|
||||||
),
|
),
|
||||||
None => layout_block_level_children_in_parallel(
|
None => layout_block_level_children_in_parallel(
|
||||||
layout_context,
|
layout_context,
|
||||||
positioning_context,
|
positioning_context,
|
||||||
child_boxes,
|
child_boxes,
|
||||||
containing_block,
|
containing_block,
|
||||||
collapsible_with_parent_start_margin,
|
&mut placement_state,
|
||||||
),
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (content_block_size, collapsible_margins_in_children, baselines) = placement_state.finish();
|
||||||
|
FlowLayout {
|
||||||
|
fragments,
|
||||||
|
content_block_size,
|
||||||
|
collapsible_margins_in_children,
|
||||||
|
baselines,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,8 +454,8 @@ fn layout_block_level_children_in_parallel(
|
||||||
positioning_context: &mut PositioningContext,
|
positioning_context: &mut PositioningContext,
|
||||||
child_boxes: &[ArcRefCell<BlockLevelBox>],
|
child_boxes: &[ArcRefCell<BlockLevelBox>],
|
||||||
containing_block: &ContainingBlock,
|
containing_block: &ContainingBlock,
|
||||||
collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
|
placement_state: &mut PlacementState,
|
||||||
) -> FlowLayout {
|
) -> Vec<Fragment> {
|
||||||
let collects_for_nearest_positioned_ancestor =
|
let collects_for_nearest_positioned_ancestor =
|
||||||
positioning_context.collects_for_nearest_positioned_ancestor();
|
positioning_context.collects_for_nearest_positioned_ancestor();
|
||||||
let layout_results: Vec<(Fragment, PositioningContext)> = child_boxes
|
let layout_results: Vec<(Fragment, PositioningContext)> = child_boxes
|
||||||
|
@ -462,8 +474,7 @@ fn layout_block_level_children_in_parallel(
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut placement_state = PlacementState::new(collapsible_with_parent_start_margin);
|
layout_results
|
||||||
let fragments = layout_results
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(mut fragment, mut child_positioning_context)| {
|
.map(|(mut fragment, mut child_positioning_context)| {
|
||||||
placement_state.place_fragment_and_update_baseline(&mut fragment, None);
|
placement_state.place_fragment_and_update_baseline(&mut fragment, None);
|
||||||
|
@ -474,15 +485,7 @@ fn layout_block_level_children_in_parallel(
|
||||||
positioning_context.append(child_positioning_context);
|
positioning_context.append(child_positioning_context);
|
||||||
fragment
|
fragment
|
||||||
})
|
})
|
||||||
.collect();
|
.collect()
|
||||||
|
|
||||||
let (content_block_size, collapsible_margins_in_children, baselines) = placement_state.finish();
|
|
||||||
FlowLayout {
|
|
||||||
fragments,
|
|
||||||
content_block_size,
|
|
||||||
collapsible_margins_in_children,
|
|
||||||
baselines,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_block_level_children_sequentially(
|
fn layout_block_level_children_sequentially(
|
||||||
|
@ -491,14 +494,12 @@ fn layout_block_level_children_sequentially(
|
||||||
child_boxes: &[ArcRefCell<BlockLevelBox>],
|
child_boxes: &[ArcRefCell<BlockLevelBox>],
|
||||||
containing_block: &ContainingBlock,
|
containing_block: &ContainingBlock,
|
||||||
sequential_layout_state: &mut SequentialLayoutState,
|
sequential_layout_state: &mut SequentialLayoutState,
|
||||||
collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
|
placement_state: &mut PlacementState,
|
||||||
) -> FlowLayout {
|
) -> Vec<Fragment> {
|
||||||
let mut placement_state = PlacementState::new(collapsible_with_parent_start_margin);
|
|
||||||
|
|
||||||
// Because floats are involved, we do layout for this block formatting context in tree
|
// Because floats are involved, we do layout for this block formatting context in tree
|
||||||
// order without parallelism. This enables mutable access to a `SequentialLayoutState` that
|
// order without parallelism. This enables mutable access to a `SequentialLayoutState` that
|
||||||
// tracks every float encountered so far (again in tree order).
|
// tracks every float encountered so far (again in tree order).
|
||||||
let fragments = child_boxes
|
child_boxes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|child_box| {
|
.map(|child_box| {
|
||||||
let positioning_context_length_before_layout = positioning_context.len();
|
let positioning_context_length_before_layout = positioning_context.len();
|
||||||
|
@ -521,15 +522,7 @@ fn layout_block_level_children_sequentially(
|
||||||
|
|
||||||
fragment
|
fragment
|
||||||
})
|
})
|
||||||
.collect();
|
.collect()
|
||||||
|
|
||||||
let (content_block_size, collapsible_margins_in_children, baselines) = placement_state.finish();
|
|
||||||
FlowLayout {
|
|
||||||
fragments,
|
|
||||||
content_block_size,
|
|
||||||
collapsible_margins_in_children,
|
|
||||||
baselines,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockLevelBox {
|
impl BlockLevelBox {
|
||||||
|
@ -1387,12 +1380,16 @@ struct PlacementState {
|
||||||
current_margin: CollapsedMargin,
|
current_margin: CollapsedMargin,
|
||||||
current_block_direction_position: Length,
|
current_block_direction_position: Length,
|
||||||
inflow_baselines: Baselines,
|
inflow_baselines: Baselines,
|
||||||
|
is_inline_block_context: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlacementState {
|
impl PlacementState {
|
||||||
fn new(
|
fn new(
|
||||||
collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
|
collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
|
||||||
|
containing_block_style: &ComputedValues,
|
||||||
) -> PlacementState {
|
) -> PlacementState {
|
||||||
|
let is_inline_block_context =
|
||||||
|
containing_block_style.get_box().clone_display() == Display::InlineBlock;
|
||||||
PlacementState {
|
PlacementState {
|
||||||
next_in_flow_margin_collapses_with_parent_start_margin:
|
next_in_flow_margin_collapses_with_parent_start_margin:
|
||||||
collapsible_with_parent_start_margin.0,
|
collapsible_with_parent_start_margin.0,
|
||||||
|
@ -1401,6 +1398,7 @@ impl PlacementState {
|
||||||
current_margin: CollapsedMargin::zero(),
|
current_margin: CollapsedMargin::zero(),
|
||||||
current_block_direction_position: Length::zero(),
|
current_block_direction_position: Length::zero(),
|
||||||
inflow_baselines: Baselines::default(),
|
inflow_baselines: Baselines::default(),
|
||||||
|
is_inline_block_context,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1411,15 +1409,27 @@ impl PlacementState {
|
||||||
) {
|
) {
|
||||||
self.place_fragment(fragment, sequential_layout_state);
|
self.place_fragment(fragment, sequential_layout_state);
|
||||||
|
|
||||||
if let Fragment::Box(box_fragment) = fragment {
|
let box_fragment = match fragment {
|
||||||
let box_block_offset = box_fragment.content_rect.start_corner.block.into();
|
Fragment::Box(box_fragment) => box_fragment,
|
||||||
match (self.inflow_baselines.first, box_fragment.baselines.first) {
|
_ => return,
|
||||||
(None, Some(first)) => self.inflow_baselines.first = Some(first + box_block_offset),
|
};
|
||||||
_ => {},
|
|
||||||
}
|
// From <https://drafts.csswg.org/css-align-3/#baseline-export>:
|
||||||
if let Some(last) = box_fragment.baselines.last {
|
// > When finding the first/last baseline set of an inline-block, any baselines
|
||||||
self.inflow_baselines.last = Some(last + box_block_offset);
|
// > contributed by table boxes must be skipped. (This quirk is a legacy behavior from
|
||||||
}
|
// > [CSS2].)
|
||||||
|
let display = box_fragment.style.clone_display();
|
||||||
|
let is_table = display == Display::Table;
|
||||||
|
if self.is_inline_block_context && is_table {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let box_block_offset = box_fragment.content_rect.start_corner.block.into();
|
||||||
|
if let (None, Some(first)) = (self.inflow_baselines.first, box_fragment.baselines.first) {
|
||||||
|
self.inflow_baselines.first = Some(first + box_block_offset);
|
||||||
|
}
|
||||||
|
if let Some(last) = box_fragment.baselines.last {
|
||||||
|
self.inflow_baselines.last = Some(last + box_block_offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ pub(crate) enum NonReplacedFormattingContextContents {
|
||||||
|
|
||||||
/// The baselines of a layout or a [`BoxFragment`]. Some layout uses the first and some layout uses
|
/// The baselines of a layout or a [`BoxFragment`]. Some layout uses the first and some layout uses
|
||||||
/// the last.
|
/// the last.
|
||||||
#[derive(Default, Serialize)]
|
#[derive(Debug, Default, Serialize)]
|
||||||
pub(crate) struct Baselines {
|
pub(crate) struct Baselines {
|
||||||
pub first: Option<Au>,
|
pub first: Option<Au>,
|
||||||
pub last: Option<Au>,
|
pub last: Option<Au>,
|
||||||
|
|
|
@ -202,6 +202,7 @@ impl BoxFragment {
|
||||||
\nmargin={:?}\
|
\nmargin={:?}\
|
||||||
\nclearance={:?}\
|
\nclearance={:?}\
|
||||||
\nscrollable_overflow={:?}\
|
\nscrollable_overflow={:?}\
|
||||||
|
\nbaselines={:?}\
|
||||||
\noverflow={:?} / {:?}",
|
\noverflow={:?} / {:?}",
|
||||||
self.base,
|
self.base,
|
||||||
self.content_rect,
|
self.content_rect,
|
||||||
|
@ -210,6 +211,7 @@ impl BoxFragment {
|
||||||
self.margin,
|
self.margin,
|
||||||
self.clearance,
|
self.clearance,
|
||||||
self.scrollable_overflow(&PhysicalRect::zero()),
|
self.scrollable_overflow(&PhysicalRect::zero()),
|
||||||
|
self.baselines,
|
||||||
self.style.get_box().overflow_x,
|
self.style.get_box().overflow_x,
|
||||||
self.style.get_box().overflow_y,
|
self.style.get_box().overflow_y,
|
||||||
));
|
));
|
||||||
|
|
|
@ -107,11 +107,14 @@ impl Table {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut table = table_builder.finish();
|
||||||
|
table.anonymous = true;
|
||||||
|
|
||||||
IndependentFormattingContext::NonReplaced(NonReplacedFormattingContext {
|
IndependentFormattingContext::NonReplaced(NonReplacedFormattingContext {
|
||||||
base_fragment_info: (&anonymous_info).into(),
|
base_fragment_info: (&anonymous_info).into(),
|
||||||
style: anonymous_style,
|
style: anonymous_style,
|
||||||
content_sizes: None,
|
content_sizes: None,
|
||||||
contents: NonReplacedFormattingContextContents::Table(table_builder.finish()),
|
contents: NonReplacedFormattingContextContents::Table(table),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,6 +229,24 @@ impl TableBuilder {
|
||||||
for row in self.table.slots.iter_mut() {
|
for row in self.table.slots.iter_mut() {
|
||||||
row.resize_with(self.table.size.width, || TableSlot::Empty);
|
row.resize_with(self.table.size.width, || TableSlot::Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Turn all rowspan=0 rows into the real value to avoid having to
|
||||||
|
// make the calculation continually during layout. In addition, make
|
||||||
|
// sure that there are no rowspans that extend past the end of the
|
||||||
|
// table.
|
||||||
|
for row_index in 0..self.table.size.height {
|
||||||
|
for cell in self.table.slots[row_index].iter_mut() {
|
||||||
|
if let TableSlot::Cell(ref mut cell) = cell {
|
||||||
|
let rowspan_to_end_of_table = self.table.size.height - row_index;
|
||||||
|
if cell.rowspan == 0 {
|
||||||
|
cell.rowspan = rowspan_to_end_of_table;
|
||||||
|
} else {
|
||||||
|
cell.rowspan = cell.rowspan.min(rowspan_to_end_of_table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.table
|
self.table
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,21 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::ops::Add;
|
||||||
|
|
||||||
use app_units::{Au, MAX_AU};
|
use app_units::{Au, MAX_AU};
|
||||||
use euclid::num::Zero;
|
use euclid::num::Zero;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use style::computed_values::border_collapse::T as BorderCollapse;
|
use style::computed_values::border_collapse::T as BorderCollapse;
|
||||||
use style::logical_geometry::WritingMode;
|
use style::logical_geometry::WritingMode;
|
||||||
use style::values::computed::{CSSPixelLength, Length, LengthOrAuto, Percentage};
|
use style::values::computed::{CSSPixelLength, Length, LengthOrAuto, Percentage};
|
||||||
|
use style::values::generics::box_::{GenericVerticalAlign as VerticalAlign, VerticalAlignKeyword};
|
||||||
use style::values::generics::length::GenericLengthPercentageOrAuto::{Auto, LengthPercentage};
|
use style::values::generics::length::GenericLengthPercentageOrAuto::{Auto, LengthPercentage};
|
||||||
|
|
||||||
use super::{Table, TableSlot, TableSlotCell};
|
use super::{Table, TableSlot, TableSlotCell};
|
||||||
use crate::context::LayoutContext;
|
use crate::context::LayoutContext;
|
||||||
use crate::formatting_contexts::{Baselines, IndependentLayout};
|
use crate::formatting_contexts::{Baselines, IndependentLayout};
|
||||||
use crate::fragment_tree::{BoxFragment, CollapsedBlockMargins, Fragment};
|
use crate::fragment_tree::{AnonymousFragment, BoxFragment, CollapsedBlockMargins, Fragment};
|
||||||
use crate::geom::{LogicalRect, LogicalSides, LogicalVec2};
|
use crate::geom::{LogicalRect, LogicalSides, LogicalVec2};
|
||||||
use crate::positioned::{PositioningContext, PositioningContextLength};
|
use crate::positioned::{PositioningContext, PositioningContextLength};
|
||||||
use crate::sizing::ContentSizes;
|
use crate::sizing::ContentSizes;
|
||||||
|
@ -29,7 +32,20 @@ struct CellLayout {
|
||||||
padding: LogicalSides<Length>,
|
padding: LogicalSides<Length>,
|
||||||
border: LogicalSides<Length>,
|
border: LogicalSides<Length>,
|
||||||
positioning_context: PositioningContext,
|
positioning_context: PositioningContext,
|
||||||
rowspan: usize,
|
}
|
||||||
|
|
||||||
|
impl CellLayout {
|
||||||
|
fn ascent(&self) -> Au {
|
||||||
|
self.layout
|
||||||
|
.baselines
|
||||||
|
.first
|
||||||
|
.unwrap_or(self.layout.content_block_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The block size of this laid out cell including its border and padding.
|
||||||
|
fn outer_block_size(&self) -> Au {
|
||||||
|
self.layout.content_block_size + (self.border.block_sum() + self.padding.block_sum()).into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A helper struct that performs the layout of the box tree version
|
/// A helper struct that performs the layout of the box tree version
|
||||||
|
@ -45,6 +61,7 @@ struct TableLayout<'a> {
|
||||||
column_measures: Vec<CellOrColumnMeasure>,
|
column_measures: Vec<CellOrColumnMeasure>,
|
||||||
distributed_column_widths: Vec<Au>,
|
distributed_column_widths: Vec<Au>,
|
||||||
row_sizes: Vec<Au>,
|
row_sizes: Vec<Au>,
|
||||||
|
row_baselines: Vec<Au>,
|
||||||
cells_laid_out: Vec<Vec<Option<CellLayout>>>,
|
cells_laid_out: Vec<Vec<Option<CellLayout>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +92,7 @@ impl<'a> TableLayout<'a> {
|
||||||
column_measures: Vec::new(),
|
column_measures: Vec::new(),
|
||||||
distributed_column_widths: Vec::new(),
|
distributed_column_widths: Vec::new(),
|
||||||
row_sizes: Vec::new(),
|
row_sizes: Vec::new(),
|
||||||
|
row_baselines: Vec::new(),
|
||||||
cells_laid_out: Vec::new(),
|
cells_laid_out: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -883,6 +901,8 @@ impl<'a> TableLayout<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is an implementation of *Row layout (first pass)* from
|
||||||
|
/// <https://drafts.csswg.org/css-tables/#row-layout>.
|
||||||
fn do_row_layout_first_pass(
|
fn do_row_layout_first_pass(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout_context: &LayoutContext,
|
layout_context: &LayoutContext,
|
||||||
|
@ -936,7 +956,6 @@ impl<'a> TableLayout<'a> {
|
||||||
padding,
|
padding,
|
||||||
border,
|
border,
|
||||||
positioning_context,
|
positioning_context,
|
||||||
rowspan: cell.rowspan,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
self.cells_laid_out.push(cells_laid_out_row);
|
self.cells_laid_out.push(cells_laid_out_row);
|
||||||
|
@ -945,52 +964,110 @@ impl<'a> TableLayout<'a> {
|
||||||
|
|
||||||
fn distribute_height_to_rows(&mut self) {
|
fn distribute_height_to_rows(&mut self) {
|
||||||
for row_index in 0..self.table.size.height {
|
for row_index in 0..self.table.size.height {
|
||||||
let mut max_row_height = Au::zero();
|
let (mut max_ascent, mut max_descent, mut max_row_height) =
|
||||||
|
(Au::zero(), Au::zero(), Au::zero());
|
||||||
|
|
||||||
for column_index in 0..self.table.size.width {
|
for column_index in 0..self.table.size.width {
|
||||||
let coords = TableSlotCoordinates::new(column_index, row_index);
|
let coords = TableSlotCoordinates::new(column_index, row_index);
|
||||||
self.table
|
|
||||||
.resolve_first_cell_coords(coords)
|
let cell = match self.table.slots[row_index][column_index] {
|
||||||
.map(|resolved_coords| {
|
TableSlot::Cell(ref cell) => cell,
|
||||||
let cell = self.cells_laid_out[resolved_coords.y][resolved_coords.x]
|
TableSlot::Spanned(ref spanned_cells) if spanned_cells[0].y != 0 => {
|
||||||
.as_ref()
|
let offset = spanned_cells[0];
|
||||||
.unwrap();
|
let origin = coords - offset;
|
||||||
let total_height = cell.layout.content_block_size +
|
|
||||||
cell.border.block_sum().into() +
|
// We only allocate the remaining space for the last row of the rowspanned cell.
|
||||||
cell.padding.block_sum().into();
|
if let Some(TableSlot::Cell(origin_cell)) = self.table.get_slot(origin) {
|
||||||
// TODO: We are accounting for rowspan=0 here, but perhaps this should be
|
if origin_cell.rowspan != offset.y + 1 {
|
||||||
// translated into a real rowspan during table box tree construction.
|
continue;
|
||||||
let effective_rowspan = match cell.rowspan {
|
}
|
||||||
0 => (self.table.size.height - resolved_coords.y) as i32,
|
}
|
||||||
rowspan => rowspan as i32,
|
|
||||||
};
|
// This is all of the rows that are spanned except this one.
|
||||||
max_row_height = (total_height / effective_rowspan).max(max_row_height)
|
let used_block_size = (origin.y..coords.y)
|
||||||
});
|
.map(|row_index| self.row_sizes[row_index])
|
||||||
|
.fold(Au::zero(), |sum, size| sum + size);
|
||||||
|
if let Some(layout) = &self.cells_laid_out[origin.y][origin.x] {
|
||||||
|
max_row_height =
|
||||||
|
max_row_height.max(layout.outer_block_size() - used_block_size);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let layout = match self.cells_laid_out[row_index][column_index] {
|
||||||
|
Some(ref layout) => layout,
|
||||||
|
None => {
|
||||||
|
warn!("Did not find a layout at a slot index with an originating cell.");
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let outer_block_size = layout.outer_block_size();
|
||||||
|
if cell.rowspan == 1 {
|
||||||
|
max_row_height = max_row_height.max(outer_block_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if cell.effective_vertical_align() == VerticalAlignKeyword::Baseline {
|
||||||
|
let ascent = layout.ascent();
|
||||||
|
let border_padding_start =
|
||||||
|
layout.border.block_start + layout.padding.block_start;
|
||||||
|
let border_padding_end = layout.border.block_end + layout.padding.block_end;
|
||||||
|
max_ascent = max_ascent.max(ascent + border_padding_start.into());
|
||||||
|
|
||||||
|
// Only take into account the descent of this cell if doesn't span
|
||||||
|
// rows. The descent portion of the cell in cells that do span rows
|
||||||
|
// will be allocated to the other rows that it spans.
|
||||||
|
if cell.rowspan == 1 {
|
||||||
|
max_descent = max_descent.max(
|
||||||
|
layout.layout.content_block_size - ascent + border_padding_end.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.row_sizes.push(max_row_height);
|
|
||||||
|
self.row_baselines.push(max_ascent);
|
||||||
|
self.row_sizes
|
||||||
|
.push(max_row_height.max(max_ascent + max_descent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lay out the table of this [`TableLayout`] into fragments. This should only be be called
|
/// Lay out the table of this [`TableLayout`] into fragments. This should only be be called
|
||||||
/// after calling [`TableLayout.compute_measures`].
|
/// after calling [`TableLayout.compute_measures`].
|
||||||
fn layout_into_box_fragments(
|
fn layout(mut self, positioning_context: &mut PositioningContext) -> IndependentLayout {
|
||||||
mut self,
|
|
||||||
positioning_context: &mut PositioningContext,
|
|
||||||
) -> (Vec<Fragment>, Au) {
|
|
||||||
assert_eq!(self.table.size.height, self.row_sizes.len());
|
assert_eq!(self.table.size.height, self.row_sizes.len());
|
||||||
assert_eq!(self.table.size.width, self.distributed_column_widths.len());
|
assert_eq!(self.table.size.width, self.distributed_column_widths.len());
|
||||||
|
|
||||||
|
let mut baselines = Baselines::default();
|
||||||
let border_spacing = self.table.border_spacing();
|
let border_spacing = self.table.border_spacing();
|
||||||
let mut fragments = Vec::new();
|
let mut fragments = Vec::new();
|
||||||
let mut row_offset = border_spacing.block;
|
let mut row_offset = border_spacing.block;
|
||||||
for row_index in 0..self.table.size.height {
|
for row_index in 0..self.table.size.height {
|
||||||
let mut column_offset = border_spacing.inline;
|
let mut column_offset = border_spacing.inline;
|
||||||
let row_size = self.row_sizes[row_index];
|
let row_size = self.row_sizes[row_index];
|
||||||
|
let row_baseline = self.row_baselines[row_index];
|
||||||
|
|
||||||
|
// From <https://drafts.csswg.org/css-align-3/#baseline-export>
|
||||||
|
// > If any cells in the row participate in first baseline/last baseline alignment along
|
||||||
|
// > the inline axis, the first/last baseline set of the row is generated from their
|
||||||
|
// > shared alignment baseline and the row’s first available font, after alignment has
|
||||||
|
// > been performed. Otherwise, the first/last baseline set of the row is synthesized from
|
||||||
|
// > the lowest and highest content edges of the cells in the row. [CSS2]
|
||||||
|
//
|
||||||
|
// If any cell below has baseline alignment, these values will be overwritten,
|
||||||
|
// but they are initialized to the content edge of the first row.
|
||||||
|
if row_index == 0 {
|
||||||
|
baselines.first = Some(row_offset + row_size);
|
||||||
|
baselines.last = Some(row_offset + row_size);
|
||||||
|
}
|
||||||
|
|
||||||
for column_index in 0..self.table.size.width {
|
for column_index in 0..self.table.size.width {
|
||||||
let column_size = self.distributed_column_widths[column_index];
|
|
||||||
let layout = match self.cells_laid_out[row_index][column_index].take() {
|
let layout = match self.cells_laid_out[row_index][column_index].take() {
|
||||||
Some(layout) => layout,
|
Some(layout) => layout,
|
||||||
None => continue,
|
None => {
|
||||||
|
continue;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let cell = match self.table.slots[row_index][column_index] {
|
let cell = match self.table.slots[row_index][column_index] {
|
||||||
|
@ -1001,30 +1078,58 @@ impl<'a> TableLayout<'a> {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If this cell has baseline alignment, it can adjust the table's overall baseline.
|
||||||
|
if cell.effective_vertical_align() == VerticalAlignKeyword::Baseline {
|
||||||
|
if row_index == 0 {
|
||||||
|
baselines.first = Some(row_offset + row_baseline);
|
||||||
|
}
|
||||||
|
baselines.last = Some(row_offset + row_baseline);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the inline and block size of all rows and columns that this cell spans.
|
||||||
|
let inline_size: Au = (column_index..column_index + cell.colspan)
|
||||||
|
.map(|index| self.distributed_column_widths[index])
|
||||||
|
.fold(Au::zero(), Au::add) +
|
||||||
|
((cell.colspan - 1) as i32 * border_spacing.inline);
|
||||||
|
let block_size: Au = (row_index..row_index + cell.rowspan)
|
||||||
|
.map(|index| self.row_sizes[index])
|
||||||
|
.fold(Au::zero(), Au::add) +
|
||||||
|
((cell.rowspan - 1) as i32 * border_spacing.block);
|
||||||
|
|
||||||
let cell_rect: LogicalRect<Length> = LogicalRect {
|
let cell_rect: LogicalRect<Length> = LogicalRect {
|
||||||
start_corner: LogicalVec2 {
|
start_corner: LogicalVec2 {
|
||||||
inline: column_offset.into(),
|
inline: column_offset.into(),
|
||||||
block: row_offset.into(),
|
block: row_offset.into(),
|
||||||
},
|
},
|
||||||
size: LogicalVec2 {
|
size: LogicalVec2 {
|
||||||
inline: column_size.into(),
|
inline: inline_size.into(),
|
||||||
block: row_size.into(),
|
block: block_size.into(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
fragments.push(Fragment::Box(cell.create_fragment(
|
fragments.push(Fragment::Box(cell.create_fragment(
|
||||||
layout,
|
layout,
|
||||||
cell_rect,
|
cell_rect,
|
||||||
|
row_baseline,
|
||||||
positioning_context,
|
positioning_context,
|
||||||
)));
|
)));
|
||||||
|
|
||||||
column_offset += column_size + border_spacing.inline;
|
column_offset += inline_size + border_spacing.inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
row_offset += row_size + border_spacing.block;
|
row_offset += row_size + border_spacing.block;
|
||||||
}
|
}
|
||||||
|
|
||||||
(fragments, row_offset)
|
if self.table.anonymous {
|
||||||
|
baselines.first = None;
|
||||||
|
baselines.last = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
IndependentLayout {
|
||||||
|
fragments,
|
||||||
|
content_block_size: row_offset,
|
||||||
|
baselines,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1100,13 +1205,7 @@ impl Table {
|
||||||
) -> IndependentLayout {
|
) -> IndependentLayout {
|
||||||
let mut table_layout = TableLayout::new(self);
|
let mut table_layout = TableLayout::new(self);
|
||||||
table_layout.compute_measures(layout_context, positioning_context, containing_block);
|
table_layout.compute_measures(layout_context, positioning_context, containing_block);
|
||||||
let (fragments, content_block_size) =
|
table_layout.layout(positioning_context)
|
||||||
table_layout.layout_into_box_fragments(positioning_context);
|
|
||||||
IndependentLayout {
|
|
||||||
fragments,
|
|
||||||
content_block_size,
|
|
||||||
baselines: Baselines::default(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1136,24 +1235,63 @@ impl TableSlotCell {
|
||||||
sizes
|
sizes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn effective_vertical_align(&self) -> VerticalAlignKeyword {
|
||||||
|
match self.style.clone_vertical_align() {
|
||||||
|
VerticalAlign::Keyword(VerticalAlignKeyword::Top) => VerticalAlignKeyword::Top,
|
||||||
|
VerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => VerticalAlignKeyword::Bottom,
|
||||||
|
VerticalAlign::Keyword(VerticalAlignKeyword::Middle) => VerticalAlignKeyword::Middle,
|
||||||
|
_ => VerticalAlignKeyword::Baseline,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn create_fragment(
|
fn create_fragment(
|
||||||
&self,
|
&self,
|
||||||
mut layout: CellLayout,
|
mut layout: CellLayout,
|
||||||
cell_rect: LogicalRect<Length>,
|
cell_rect: LogicalRect<Length>,
|
||||||
|
cell_baseline: Au,
|
||||||
positioning_context: &mut PositioningContext,
|
positioning_context: &mut PositioningContext,
|
||||||
) -> BoxFragment {
|
) -> BoxFragment {
|
||||||
// This must be scoped to this function because it conflicts with euclid's Zero.
|
// This must be scoped to this function because it conflicts with euclid's Zero.
|
||||||
use style::Zero as StyleZero;
|
use style::Zero as StyleZero;
|
||||||
|
|
||||||
let fragments = layout.layout.fragments;
|
let cell_content_rect = cell_rect.deflate(&(&layout.padding + &layout.border));
|
||||||
let content_rect = cell_rect.deflate(&(&layout.padding + &layout.border));
|
let content_block_size = layout.layout.content_block_size.into();
|
||||||
|
let vertical_align_offset = match self.effective_vertical_align() {
|
||||||
|
VerticalAlignKeyword::Top => Length::new(0.),
|
||||||
|
VerticalAlignKeyword::Bottom => cell_content_rect.size.block - content_block_size,
|
||||||
|
VerticalAlignKeyword::Middle => {
|
||||||
|
(cell_content_rect.size.block - content_block_size).scale_by(0.5)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
Length::from(cell_baseline) -
|
||||||
|
(layout.padding.block_start + layout.border.block_start) -
|
||||||
|
Length::from(layout.ascent())
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create an `AnonymousFragment` to move the cell contents to the cell baseline.
|
||||||
|
let mut vertical_align_fragment_rect = cell_content_rect.clone();
|
||||||
|
vertical_align_fragment_rect.start_corner = LogicalVec2 {
|
||||||
|
inline: Length::new(0.),
|
||||||
|
block: vertical_align_offset,
|
||||||
|
};
|
||||||
|
let vertical_align_fragment = AnonymousFragment::new(
|
||||||
|
vertical_align_fragment_rect,
|
||||||
|
layout.layout.fragments,
|
||||||
|
self.style.writing_mode,
|
||||||
|
);
|
||||||
|
|
||||||
// Adjust the static position of all absolute children based on the
|
// Adjust the static position of all absolute children based on the
|
||||||
// final content rect of this fragment.
|
// final content rect of this fragment. Note that we are not shifting by the position of the
|
||||||
|
// Anonymous fragment we use to shift content to the baseline.
|
||||||
|
//
|
||||||
|
// TODO(mrobinson): This is correct for absolutes that are direct children of the table
|
||||||
|
// cell, but wrong for absolute fragments that are more deeply nested in the hierarchy of
|
||||||
|
// fragments.
|
||||||
layout
|
layout
|
||||||
.positioning_context
|
.positioning_context
|
||||||
.adjust_static_position_of_hoisted_fragments_with_offset(
|
.adjust_static_position_of_hoisted_fragments_with_offset(
|
||||||
&content_rect.start_corner,
|
&cell_content_rect.start_corner,
|
||||||
PositioningContextLength::zero(),
|
PositioningContextLength::zero(),
|
||||||
);
|
);
|
||||||
positioning_context.append(layout.positioning_context);
|
positioning_context.append(layout.positioning_context);
|
||||||
|
@ -1161,8 +1299,8 @@ impl TableSlotCell {
|
||||||
BoxFragment::new(
|
BoxFragment::new(
|
||||||
self.base_fragment_info,
|
self.base_fragment_info,
|
||||||
self.style.clone(),
|
self.style.clone(),
|
||||||
fragments,
|
vec![Fragment::Anonymous(vertical_align_fragment)],
|
||||||
content_rect,
|
cell_content_rect,
|
||||||
layout.padding,
|
layout.padding,
|
||||||
layout.border,
|
layout.border,
|
||||||
LogicalSides::zero(), /* margin */
|
LogicalSides::zero(), /* margin */
|
||||||
|
|
|
@ -37,6 +37,9 @@ pub struct Table {
|
||||||
|
|
||||||
/// The size of this table.
|
/// The size of this table.
|
||||||
pub size: TableSize,
|
pub size: TableSize,
|
||||||
|
|
||||||
|
/// Whether or not this Table is anonymous.
|
||||||
|
anonymous: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Table {
|
impl Table {
|
||||||
|
@ -45,6 +48,7 @@ impl Table {
|
||||||
style,
|
style,
|
||||||
slots: Vec::new(),
|
slots: Vec::new(),
|
||||||
size: TableSize::zero(),
|
size: TableSize::zero(),
|
||||||
|
anonymous: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
[margin-collapse-clear-002.xht]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[margin-collapse-clear-003.xht]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[margin-collapse-clear-008.xht]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[margin-collapse-clear-009.xht]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[table-vertical-align-baseline-001.xht]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[table-vertical-align-baseline-002.xht]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[table-vertical-align-baseline-003.xht]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[table-vertical-align-baseline-004.xht]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[table-vertical-align-baseline-005.xht]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[table-vertical-align-baseline-006.xht]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[table-vertical-align-baseline-007.xht]
|
|
||||||
expected: FAIL
|
|
|
@ -5,9 +5,6 @@
|
||||||
[Anonymous consecutive columns spanned by the same set of cells are merged]
|
[Anonymous consecutive columns spanned by the same set of cells are merged]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Anonymous consecutive rows spanned by the same set of cells are merged]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Explicitely-defined consecutive columns spanned by the same set of cells are not merged]
|
[Explicitely-defined consecutive columns spanned by the same set of cells are not merged]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,4 @@
|
||||||
[baseline-table.html]
|
[baseline-table.html]
|
||||||
[.container 3]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[.container 4]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[.container 5]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[.container 6]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[.container 11]
|
[.container 11]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -19,6 +7,3 @@
|
||||||
|
|
||||||
[.container 13]
|
[.container 13]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[.container 14]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -17,9 +17,6 @@
|
||||||
[table 7]
|
[table 7]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[table 8]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[table 9]
|
[table 9]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -29,9 +29,6 @@
|
||||||
[table 12]
|
[table 12]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[table 13]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[table 14]
|
[table 14]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue