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

@ -2,46 +2,81 @@
* 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/. */
//! Table layout.
//! See https://html.spec.whatwg.org/multipage/table-processing-model.
//! HTML Tables (╯°□°)╯︵ ┻━┻.
//!
//! See <https://html.spec.whatwg.org/multipage/table-processing-model> and
//! <https://drafts.csswg.org/css-tables>. This is heavily based on the latter specification, but
//! note that it is still an Editor's Draft, so there is no guarantee that what is implemented here
//! matches other browsers or the current specification.
mod construct;
mod layout;
use app_units::Au;
pub(crate) use construct::AnonymousTableContent;
pub use construct::TableBuilder;
use euclid::num::Zero;
use euclid::{Point2D, UnknownUnit, Vector2D};
use euclid::{Point2D, Size2D, UnknownUnit, Vector2D};
use serde::Serialize;
use servo_arc::Arc;
use style::properties::ComputedValues;
use style_traits::dom::OpaqueNode;
use super::flow::BlockFormattingContext;
use crate::context::LayoutContext;
use crate::flow::BlockContainer;
use crate::formatting_contexts::IndependentLayout;
use crate::positioned::PositioningContext;
use crate::sizing::ContentSizes;
use crate::ContainingBlock;
use crate::fragment_tree::BaseFragmentInfo;
#[derive(Debug, Default, Serialize)]
pub type TableSize = Size2D<usize, UnknownUnit>;
#[derive(Debug, Serialize)]
pub struct Table {
/// The style of this table.
#[serde(skip_serializing)]
style: Arc<ComputedValues>,
/// The content of the slots of this table.
pub slots: Vec<Vec<TableSlot>>,
/// The size of this table.
pub size: TableSize,
}
impl Table {
pub(crate) fn inline_content_sizes(&self) -> ContentSizes {
ContentSizes::zero()
pub(crate) fn new(style: Arc<ComputedValues>) -> Self {
Self {
style,
slots: Vec::new(),
size: TableSize::zero(),
}
}
pub(crate) fn layout(
/// Return the slot at the given coordinates, if it exists in the table, otherwise
/// return None.
fn get_slot<'a>(&'a self, coords: TableSlotCoordinates) -> Option<&'a TableSlot> {
self.slots.get(coords.y)?.get(coords.x)
}
fn resolve_first_cell_coords(
&self,
_layout_context: &LayoutContext,
_positioning_context: &mut PositioningContext,
_containing_block: &ContainingBlock,
) -> IndependentLayout {
IndependentLayout {
fragments: Vec::new(),
content_block_size: Au::zero(),
last_inflow_baseline_offset: None,
coords: TableSlotCoordinates,
) -> Option<TableSlotCoordinates> {
match self.get_slot(coords) {
Some(&TableSlot::Cell(_)) => Some(coords),
Some(&TableSlot::Spanned(ref offsets)) => Some(coords - offsets[0]),
_ => return None,
}
}
fn resolve_first_cell(&self, coords: TableSlotCoordinates) -> Option<&TableSlotCell> {
let resolved_coords = match self.resolve_first_cell_coords(coords) {
Some(coords) => coords,
None => return None,
};
let slot = self.get_slot(resolved_coords);
match slot {
Some(&TableSlot::Cell(ref cell)) => Some(cell),
_ => unreachable!(
"Spanned slot should not point to an empty cell or another spanned slot."
),
}
}
}
@ -61,12 +96,16 @@ pub struct TableSlotCell {
/// the remaining rows in the row group.
rowspan: usize,
// An id used for testing purposes.
pub id: u8,
/// The style of this table cell.
#[serde(skip_serializing)]
style: Arc<ComputedValues>,
/// The [`BaseFragmentInfo`] of this cell.
base_fragment_info: BaseFragmentInfo,
}
impl TableSlotCell {
pub fn mock_for_testing(id: u8, colspan: usize, rowspan: usize) -> Self {
pub fn mock_for_testing(id: usize, colspan: usize, rowspan: usize) -> Self {
Self {
contents: BlockFormattingContext {
contents: BlockContainer::BlockLevelBoxes(Vec::new()),
@ -74,9 +113,15 @@ impl TableSlotCell {
},
colspan,
rowspan,
id,
style: ComputedValues::initial_values().to_arc(),
base_fragment_info: BaseFragmentInfo::new_for_node(OpaqueNode(id)),
}
}
/// Get the node id of this cell's [`BaseFragmentInfo`]. This is used for unit tests.
pub fn node_id(&self) -> usize {
self.base_fragment_info.tag.node.0
}
}
#[derive(Serialize)]