layout/table/
mod.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4#![allow(rustdoc::private_intra_doc_links)]
5
6//! # HTML Tables (╯°□°)╯︵ ┻━┻
7//!
8//! This implementation is based on the [table section of the HTML 5 Specification][1],
9//! the draft [CSS Table Module Level! 3][2] and the [LayoutNG implementation of tables][3] in Blink.
10//! In general, the draft specification differs greatly from what other browsers do, so we
11//! generally follow LayoutNG when in question.
12//!
13//! [1]: https://html.spec.whatwg.org/multipage/#tables
14//! [2]: https://drafts.csswg.org/css-tables
15//! [3]: https://source.chromium.org/chromium/chromium/src/third_party/+/main:blink/renderer/core/layout/table
16//!
17//! Table layout is divided into two phases:
18//!
19//! 1. Box Tree Construction
20//! 2. Fragment Tree Construction
21//!
22//! ## Box Tree Construction
23//!
24//! During box tree construction, table layout (`construct.rs`) will traverse the DOM and construct
25//! the basic box tree representation of a table, using the structs defined in this file ([`Table`],
26//! [`TableTrackGroup`], [`TableTrack`], etc). When processing the DOM, elements are handled
27//! differently depending on their `display` value. For instance, an element with `display:
28//! table-cell` is treated as a table cell. HTML table elements like `<table>` and `<td>` are
29//! assigned the corresponding display value from the user agent stylesheet.
30//!
31//! Every [`Table`] holds an array of [`TableSlot`]. A [`TableSlot`] represents either a cell, a cell
32//! location occupied by a cell originating from another place in the table, or is empty. In
33//! addition, when there are table model errors, a slot may spanned by more than one cell.
34//!
35//! During processing, the box tree construction agorithm will also fix up table structure, for
36//! instance, creating anonymous rows for lone table cells and putting non-table content into
37//! anonymous cells. In addition, flow layout will collect table elements outside of tables and create
38//! anonymous tables for them.
39//!
40//! After processing, box tree construction does a fix up pass on tables, converting rowspan=0 into
41//! definite rowspan values and making sure that rowspan and celspan values are not larger than the
42//! table itself. Finally, row groups may be reordered to enforce the fact that the first `<thead>`
43//! comes before `<tbody>` which comes before the first `<tfoot>`.
44//!
45//! ## Fragment Tree Construction
46//!
47//! Fragment tree construction involves calculating the size and positioning of all table elements,
48//! given their style, content, and cell and row spans. This happens both during intrinsic inline
49//! size computation as well as layout into Fragments. In both of these cases, measurement and
50//! layout is done by [`layout::TableLayout`], though for intrinsic size computation only a partial
51//! layout is done.
52//!
53//! In general, we follow the following steps when laying out table content:
54//!
55//! 1. Compute track constrainedness and has originating cells
56//! 2. Compute cell measures
57//! 3. Compute column measures
58//! 4. Compute intrinsic inline sizes for columns and the table
59//! 5. Compute the final table inline size
60//! 6. Distribute size to columns
61//! 7. Do first pass cell layout
62//! 8. Do row layout
63//! 9. Compute table height and final row sizes
64//! 10. Create fragments for table elements (columns, column groups, rows, row groups, cells)
65//!
66//! For intrinsic size computation this process stops at step 4.
67
68mod construct;
69mod layout;
70
71use std::ops::Range;
72
73use app_units::Au;
74use atomic_refcell::AtomicRef;
75pub(crate) use construct::AnonymousTableContent;
76pub use construct::TableBuilder;
77use euclid::{Point2D, Size2D, UnknownUnit, Vector2D};
78use malloc_size_of_derive::MallocSizeOf;
79use script::layout_dom::{ServoLayoutElement, ServoThreadSafeLayoutNode};
80use servo_arc::Arc;
81use style::context::SharedStyleContext;
82use style::properties::ComputedValues;
83use style::properties::style_structs::Font;
84use style::selector_parser::PseudoElement;
85
86use super::flow::BlockFormattingContext;
87use crate::cell::{ArcRefCell, WeakRefCell};
88use crate::dom::WeakLayoutBox;
89use crate::flow::BlockContainer;
90use crate::formatting_contexts::{
91    IndependentFormattingContext, IndependentFormattingContextContents,
92};
93use crate::fragment_tree::BaseFragmentInfo;
94use crate::geom::PhysicalVec;
95use crate::layout_box_base::LayoutBoxBase;
96use crate::style_ext::BorderStyleColor;
97use crate::table::layout::TableLayout;
98use crate::{PropagatedBoxTreeData, SharedStyle};
99
100pub type TableSize = Size2D<usize, UnknownUnit>;
101
102#[derive(Debug, MallocSizeOf)]
103pub struct Table {
104    /// The style of this table. These are the properties that apply to the "wrapper" ie the element
105    /// that contains both the grid and the captions. Not all properties are actually used on the
106    /// wrapper though, such as background and borders, which apply to the grid.
107    style: Arc<ComputedValues>,
108
109    /// The style of this table's grid. This is an anonymous style based on the table's style, but
110    /// eliminating all the properties handled by the "wrapper."
111    grid_style: Arc<ComputedValues>,
112
113    /// The [`BaseFragmentInfo`] for this table's grid. This is necessary so that when the
114    /// grid has a background image, it can be associated with the table's node.
115    grid_base_fragment_info: BaseFragmentInfo,
116
117    /// The captions for this table.
118    pub captions: Vec<ArcRefCell<TableCaption>>,
119
120    /// The column groups for this table.
121    pub column_groups: Vec<ArcRefCell<TableTrackGroup>>,
122
123    /// The columns of this table defined by `<colgroup> | display: table-column-group`
124    /// and `<col> | display: table-column` elements as well as `display: table-column`.
125    pub columns: Vec<ArcRefCell<TableTrack>>,
126
127    /// The rows groups for this table defined by `<tbody>`, `<thead>`, and `<tfoot>`.
128    pub row_groups: Vec<ArcRefCell<TableTrackGroup>>,
129
130    /// The rows of this table defined by `<tr>` or `display: table-row` elements.
131    pub rows: Vec<ArcRefCell<TableTrack>>,
132
133    /// The content of the slots of this table.
134    pub slots: Vec<Vec<TableSlot>>,
135
136    /// The size of this table.
137    pub size: TableSize,
138
139    /// Whether or not this Table is anonymous.
140    anonymous: bool,
141
142    /// Whether percentage columns are taken into account during inline content sizes calculation.
143    percentage_columns_allowed_for_inline_content_sizes: bool,
144}
145
146impl Table {
147    pub(crate) fn new(
148        style: Arc<ComputedValues>,
149        grid_style: Arc<ComputedValues>,
150        base_fragment_info: BaseFragmentInfo,
151        percentage_columns_allowed_for_inline_content_sizes: bool,
152    ) -> Self {
153        Self {
154            style,
155            grid_style,
156            grid_base_fragment_info: base_fragment_info,
157            captions: Vec::new(),
158            column_groups: Vec::new(),
159            columns: Vec::new(),
160            row_groups: Vec::new(),
161            rows: Vec::new(),
162            slots: Vec::new(),
163            size: TableSize::zero(),
164            anonymous: false,
165            percentage_columns_allowed_for_inline_content_sizes,
166        }
167    }
168
169    /// Return the slot at the given coordinates, if it exists in the table, otherwise
170    /// return None.
171    fn get_slot(&self, coords: TableSlotCoordinates) -> Option<&TableSlot> {
172        self.slots.get(coords.y)?.get(coords.x)
173    }
174
175    fn resolve_first_cell_coords(
176        &self,
177        coords: TableSlotCoordinates,
178    ) -> Option<TableSlotCoordinates> {
179        match self.get_slot(coords) {
180            Some(&TableSlot::Cell(_)) => Some(coords),
181            Some(TableSlot::Spanned(offsets)) => Some(coords - offsets[0]),
182            _ => None,
183        }
184    }
185
186    fn resolve_first_cell(
187        &self,
188        coords: TableSlotCoordinates,
189    ) -> Option<AtomicRef<'_, TableSlotCell>> {
190        let resolved_coords = self.resolve_first_cell_coords(coords)?;
191        let slot = self.get_slot(resolved_coords);
192        match slot {
193            Some(TableSlot::Cell(cell)) => Some(cell.borrow()),
194            _ => unreachable!(
195                "Spanned slot should not point to an empty cell or another spanned slot."
196            ),
197        }
198    }
199
200    pub(crate) fn repair_style(
201        &mut self,
202        context: &SharedStyleContext,
203        new_style: &Arc<ComputedValues>,
204    ) {
205        self.style = new_style.clone();
206        self.grid_style = context.stylist.style_for_anonymous::<ServoLayoutElement>(
207            &context.guards,
208            &PseudoElement::ServoTableGrid,
209            new_style,
210        );
211    }
212}
213
214type TableSlotCoordinates = Point2D<usize, UnknownUnit>;
215pub type TableSlotOffset = Vector2D<usize, UnknownUnit>;
216
217#[derive(Debug, MallocSizeOf)]
218pub struct TableSlotCell {
219    /// The independent formatting context that the cell establishes for its contents.
220    /// Currently, this should always be a block formatting context.
221    pub(crate) context: IndependentFormattingContext,
222
223    /// Number of columns that the cell is to span. Must be greater than zero.
224    colspan: usize,
225
226    /// Number of rows that the cell is to span. Zero means that the cell is to span all
227    /// the remaining rows in the row group.
228    rowspan: usize,
229}
230
231impl TableSlotCell {
232    pub fn mock_for_testing(id: usize, colspan: usize, rowspan: usize) -> Self {
233        let base = LayoutBoxBase::new(
234            BaseFragmentInfo::new_for_testing(id),
235            ComputedValues::initial_values_with_font_override(Font::initial_values()).to_arc(),
236        );
237        let contents = IndependentFormattingContextContents::Flow(BlockFormattingContext {
238            contents: BlockContainer::BlockLevelBoxes(Vec::new()),
239            contains_floats: false,
240        });
241        let propagated_data =
242            PropagatedBoxTreeData::default().disallowing_percentage_table_columns();
243        Self {
244            context: IndependentFormattingContext::new(base, contents, propagated_data),
245            colspan,
246            rowspan,
247        }
248    }
249
250    /// Get the node id of this cell's [`BaseFragmentInfo`]. This is used for unit tests.
251    pub fn node_id(&self) -> usize {
252        self.context
253            .base
254            .base_fragment_info
255            .tag
256            .map_or(0, |tag| tag.node.0)
257    }
258}
259
260/// A single table slot. It may be an actual cell, or a reference
261/// to a previous cell that is spanned here
262///
263/// In case of table model errors, it may be multiple references
264#[derive(MallocSizeOf)]
265pub enum TableSlot {
266    /// A table cell, with a colspan and a rowspan.
267    Cell(ArcRefCell<TableSlotCell>),
268
269    /// This slot is spanned by one or more multiple cells earlier in the table, which are
270    /// found at the given negative coordinate offsets. The vector is in the order of most
271    /// recent to earliest cell.
272    ///
273    /// If there is more than one cell that spans a slot, this is a table model error, but
274    /// we still keep track of it. See
275    /// <https://html.spec.whatwg.org/multipage/#table-model-error>
276    Spanned(Vec<TableSlotOffset>),
277
278    /// An empty spot in the table. This can happen when there is a gap in columns between
279    /// cells that are defined and one which should exist because of cell with a rowspan
280    /// from a previous row.
281    Empty,
282}
283
284impl std::fmt::Debug for TableSlot {
285    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286        match self {
287            Self::Cell(_) => f.debug_tuple("Cell").finish(),
288            Self::Spanned(spanned) => f.debug_tuple("Spanned").field(spanned).finish(),
289            Self::Empty => write!(f, "Empty"),
290        }
291    }
292}
293
294impl TableSlot {
295    fn new_spanned(offset: TableSlotOffset) -> Self {
296        Self::Spanned(vec![offset])
297    }
298}
299
300/// A row or column of a table.
301#[derive(Debug, MallocSizeOf)]
302pub struct TableTrack {
303    /// The [`LayoutBoxBase`] of this [`TableTrack`].
304    base: LayoutBoxBase,
305
306    /// The index of the table row or column group parent in the table's list of row or column
307    /// groups.
308    group_index: Option<usize>,
309
310    /// Whether or not this [`TableTrack`] was anonymous, for instance created due to
311    /// a `span` attribute set on a parent `<colgroup>`.
312    is_anonymous: bool,
313
314    /// A shared container for this track's style, used to share the style for the purposes
315    /// of drawing backgrounds in individual cells. This allows updating the style in a
316    /// single place and having it affect all cell `Fragment`s.
317    shared_background_style: SharedStyle,
318}
319
320impl TableTrack {
321    fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
322        self.base.repair_style(new_style);
323        self.shared_background_style = SharedStyle::new(new_style.clone());
324    }
325}
326
327#[derive(Debug, MallocSizeOf, PartialEq)]
328pub enum TableTrackGroupType {
329    HeaderGroup,
330    FooterGroup,
331    RowGroup,
332    ColumnGroup,
333}
334
335#[derive(Debug, MallocSizeOf)]
336pub struct TableTrackGroup {
337    /// The [`LayoutBoxBase`] of this [`TableTrackGroup`].
338    base: LayoutBoxBase,
339
340    /// The type of this [`TableTrackGroup`].
341    group_type: TableTrackGroupType,
342
343    /// The range of tracks in this [`TableTrackGroup`].
344    track_range: Range<usize>,
345
346    /// A shared container for this track's style, used to share the style for the purposes
347    /// of drawing backgrounds in individual cells. This allows updating the style in a
348    /// single place and having it affect all cell `Fragment`s.
349    shared_background_style: SharedStyle,
350}
351
352impl TableTrackGroup {
353    pub(super) fn is_empty(&self) -> bool {
354        self.track_range.is_empty()
355    }
356
357    fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
358        self.base.repair_style(new_style);
359        self.shared_background_style = SharedStyle::new(new_style.clone());
360    }
361}
362
363#[derive(Debug, MallocSizeOf)]
364pub struct TableCaption {
365    /// The contents of this cell, with its own layout.
366    pub(crate) context: IndependentFormattingContext,
367}
368
369/// A calculated collapsed border.
370#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)]
371pub(crate) struct CollapsedBorder {
372    pub style_color: BorderStyleColor,
373    pub width: Au,
374}
375
376/// Represents a piecewise sequence of collapsed borders along a line.
377pub(crate) type CollapsedBorderLine = Vec<CollapsedBorder>;
378
379#[derive(Clone, Debug, MallocSizeOf)]
380pub(crate) struct SpecificTableGridInfo {
381    pub collapsed_borders: PhysicalVec<Vec<CollapsedBorderLine>>,
382    pub track_sizes: PhysicalVec<Vec<Au>>,
383}
384
385pub(crate) struct TableLayoutStyle<'a> {
386    table: &'a Table,
387    layout: Option<&'a TableLayout<'a>>,
388}
389
390/// Table parts that are stored in the DOM. This is used in order to map from
391/// the DOM to the box tree and will eventually be important for incremental
392/// layout.
393#[derive(Debug, MallocSizeOf)]
394pub(crate) enum TableLevelBox {
395    Caption(ArcRefCell<TableCaption>),
396    Cell(ArcRefCell<TableSlotCell>),
397    TrackGroup(ArcRefCell<TableTrackGroup>),
398    Track(ArcRefCell<TableTrack>),
399}
400
401impl TableLevelBox {
402    pub(crate) fn with_base<T>(&self, callback: impl FnOnce(&LayoutBoxBase) -> T) -> T {
403        match self {
404            TableLevelBox::Caption(caption) => callback(&caption.borrow().context.base),
405            TableLevelBox::Cell(cell) => callback(&cell.borrow().context.base),
406            TableLevelBox::TrackGroup(track_group) => callback(&track_group.borrow().base),
407            TableLevelBox::Track(track) => callback(&track.borrow().base),
408        }
409    }
410
411    pub(crate) fn with_base_mut<T>(&mut self, callback: impl FnOnce(&mut LayoutBoxBase) -> T) -> T {
412        match self {
413            TableLevelBox::Caption(caption) => callback(&mut caption.borrow_mut().context.base),
414            TableLevelBox::Cell(cell) => callback(&mut cell.borrow_mut().context.base),
415            TableLevelBox::TrackGroup(track_group) => callback(&mut track_group.borrow_mut().base),
416            TableLevelBox::Track(track) => callback(&mut track.borrow_mut().base),
417        }
418    }
419
420    pub(crate) fn repair_style(
421        &self,
422        context: &SharedStyleContext<'_>,
423        node: &ServoThreadSafeLayoutNode,
424        new_style: &Arc<ComputedValues>,
425    ) {
426        match self {
427            TableLevelBox::Caption(caption) => caption
428                .borrow_mut()
429                .context
430                .repair_style(context, node, new_style),
431            TableLevelBox::Cell(cell) => cell
432                .borrow_mut()
433                .context
434                .repair_style(context, node, new_style),
435            TableLevelBox::TrackGroup(track_group) => {
436                track_group.borrow_mut().repair_style(new_style);
437            },
438            TableLevelBox::Track(track) => track.borrow_mut().repair_style(new_style),
439        }
440    }
441
442    pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
443        match self {
444            Self::Caption(caption) => caption.borrow().context.attached_to_tree(layout_box),
445            Self::Cell(cell) => cell.borrow().context.attached_to_tree(layout_box),
446            Self::TrackGroup(_) | Self::Track(_) => {
447                // The parentage of tracks within a track group, and cells within a row, is handled
448                // when the entire table is attached to the tree.
449            },
450        }
451    }
452
453    pub(crate) fn downgrade(&self) -> WeakTableLevelBox {
454        match self {
455            Self::Caption(caption) => WeakTableLevelBox::Caption(caption.downgrade()),
456            Self::Cell(cell) => WeakTableLevelBox::Cell(cell.downgrade()),
457            Self::TrackGroup(track_group) => WeakTableLevelBox::TrackGroup(track_group.downgrade()),
458            Self::Track(track) => WeakTableLevelBox::Track(track.downgrade()),
459        }
460    }
461}
462
463#[derive(Clone, Debug, MallocSizeOf)]
464pub(crate) enum WeakTableLevelBox {
465    Caption(WeakRefCell<TableCaption>),
466    Cell(WeakRefCell<TableSlotCell>),
467    TrackGroup(WeakRefCell<TableTrackGroup>),
468    Track(WeakRefCell<TableTrack>),
469}
470
471impl WeakTableLevelBox {
472    pub(crate) fn upgrade(&self) -> Option<TableLevelBox> {
473        Some(match self {
474            Self::Caption(caption) => TableLevelBox::Caption(caption.upgrade()?),
475            Self::Cell(cell) => TableLevelBox::Cell(cell.upgrade()?),
476            Self::TrackGroup(track_group) => TableLevelBox::TrackGroup(track_group.upgrade()?),
477            Self::Track(track) => TableLevelBox::Track(track.upgrade()?),
478        })
479    }
480}