mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
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:
parent
3d520f2668
commit
fc31e69f79
115 changed files with 842 additions and 315 deletions
|
@ -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)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue