layout: Add *very* basic support for table layout (#31121)

* layout: Add *very* basic support for table layout

This is the first step to proper table layout. It implements a naive
layout algorithm, notably only taking into account the preferred widths
of the first table row. Still, it causes some float tests to start
passing, so turn on the `layout.tables.enabled` preference for those
directories.

Co-authored-by: Oriol Brufau <obrufau@igalia.com>

* Address review comments

* Fix a crash with rowspan=0

* Turn on pref and update results for `/css/css-tables` and `/css/CSS2/tables`

---------

Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Martin Robinson 2024-01-19 14:20:20 +01:00 committed by GitHub
parent 3d520f2668
commit fc31e69f79
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
115 changed files with 842 additions and 315 deletions

View file

@ -7,6 +7,8 @@ use std::convert::{TryFrom, TryInto};
use log::warn;
use script_layout_interface::wrapper_traits::ThreadSafeLayoutNode;
use servo_arc::Arc;
use style::properties::ComputedValues;
use style::selector_parser::PseudoElement;
use style::str::char_is_whitespace;
use style::values::specified::TextDecorationLine;
@ -20,13 +22,14 @@ use crate::formatting_contexts::{
IndependentFormattingContext, NonReplacedFormattingContext,
NonReplacedFormattingContextContents,
};
use crate::fragment_tree::{BaseFragmentInfo, FragmentFlags, Tag};
use crate::style_ext::{DisplayGeneratingBox, DisplayLayoutInternal};
/// A reference to a slot and its coordinates in the table
#[derive(Clone, Copy, Debug)]
struct ResolvedSlotAndLocation<'a> {
cell: &'a TableSlotCell,
coords: TableSlotCoordinates,
pub(super) struct ResolvedSlotAndLocation<'a> {
pub cell: &'a TableSlotCell,
pub coords: TableSlotCoordinates,
}
impl<'a> ResolvedSlotAndLocation<'a> {
@ -114,12 +117,15 @@ impl Table {
/// Push a new slot into the last row of this table.
fn push_new_slot_to_last_row(&mut self, slot: TableSlot) {
self.slots.last_mut().expect("Should have rows").push(slot)
}
let last_row = match self.slots.last_mut() {
Some(row) => row,
None => {
unreachable!("Should have some rows before calling `push_new_slot_to_last_row`")
},
};
/// Convenience method for get() that returns a SlotAndLocation
fn get_slot<'a>(&'a self, coords: TableSlotCoordinates) -> Option<&'a TableSlot> {
self.slots.get(coords.y)?.get(coords.x)
self.size.width = self.size.width.max(last_row.len() + 1);
last_row.push(slot);
}
/// Find [`ResolvedSlotAndLocation`] of all the slots that cover the slot at the given
@ -127,7 +133,7 @@ impl Table {
/// the target and returns a [`ResolvedSlotAndLocation`] for each of them. If there is
/// no slot at the given coordinates or that slot is an empty space, an empty vector
/// is returned.
fn resolve_slot_at<'a>(
pub(super) fn resolve_slot_at<'a>(
&'a self,
coords: TableSlotCoordinates,
) -> Vec<ResolvedSlotAndLocation<'a>> {
@ -191,7 +197,6 @@ impl TableSlot {
}
}
#[derive(Default)]
pub struct TableBuilder {
/// The table that we are building.
table: Table,
@ -209,7 +214,22 @@ pub struct TableBuilder {
}
impl TableBuilder {
pub fn finish(self) -> Table {
pub(super) fn new(style: Arc<ComputedValues>) -> Self {
Self {
table: Table::new(style),
incoming_rowspans: Vec::new(),
}
}
pub fn new_for_tests() -> Self {
Self::new(ComputedValues::initial_values().to_arc())
}
pub fn finish(mut self) -> Table {
// Make sure that every row has the same number of cells.
for row in self.table.slots.iter_mut() {
row.resize_with(self.table.size.width, || TableSlot::Empty);
}
self.table
}
@ -227,6 +247,7 @@ impl TableBuilder {
pub fn start_row<'builder>(&'builder mut self) {
self.table.slots.push(Vec::new());
self.table.size.height += 1;
self.create_slots_for_cells_above_with_rowspan(true);
}
@ -385,7 +406,7 @@ where
context,
info,
propagated_text_decoration_line,
builder: Default::default(),
builder: TableBuilder::new(info.style.clone()),
current_anonymous_row_content: Vec::new(),
}
}
@ -592,12 +613,22 @@ where
}
}
let tag = Tag::new_pseudo(
self.info.node.opaque(),
Some(PseudoElement::ServoAnonymousTableCell),
);
let base_fragment_info = BaseFragmentInfo {
tag,
flags: FragmentFlags::empty(),
};
let block_container = builder.finish();
self.table_traversal.builder.add_cell(TableSlotCell {
contents: BlockFormattingContext::from_block_container(block_container),
colspan: 1,
rowspan: 1,
id: 0, // This is just an id used for testing purposes.
style: anonymous_info.style,
base_fragment_info,
});
}
}
@ -657,7 +688,8 @@ where
contents,
colspan,
rowspan,
id: 0, // This is just an id used for testing purposes.
style: info.style.clone(),
base_fragment_info: info.into(),
});
// We are doing this until we have actually set a Box for this `BoxSlot`.