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::SharedStyle;
88use crate::cell::{ArcRefCell, WeakRefCell};
89use crate::dom::WeakLayoutBox;
90use crate::flow::BlockContainer;
91use crate::formatting_contexts::IndependentFormattingContext;
92use crate::fragment_tree::BaseFragmentInfo;
93use crate::geom::PhysicalVec;
94use crate::layout_box_base::LayoutBoxBase;
95use crate::style_ext::BorderStyleColor;
96use crate::table::layout::TableLayout;
97
98pub type TableSize = Size2D<usize, UnknownUnit>;
99
100#[derive(Debug, MallocSizeOf)]
101pub struct Table {
102    /// The style of this table. These are the properties that apply to the "wrapper" ie the element
103    /// that contains both the grid and the captions. Not all properties are actually used on the
104    /// wrapper though, such as background and borders, which apply to the grid.
105    style: Arc<ComputedValues>,
106
107    /// The style of this table's grid. This is an anonymous style based on the table's style, but
108    /// eliminating all the properties handled by the "wrapper."
109    grid_style: Arc<ComputedValues>,
110
111    /// The [`BaseFragmentInfo`] for this table's grid. This is necessary so that when the
112    /// grid has a background image, it can be associated with the table's node.
113    grid_base_fragment_info: BaseFragmentInfo,
114
115    /// The captions for this table.
116    pub captions: Vec<ArcRefCell<TableCaption>>,
117
118    /// The column groups for this table.
119    pub column_groups: Vec<ArcRefCell<TableTrackGroup>>,
120
121    /// The columns of this table defined by `<colgroup> | display: table-column-group`
122    /// and `<col> | display: table-column` elements as well as `display: table-column`.
123    pub columns: Vec<ArcRefCell<TableTrack>>,
124
125    /// The rows groups for this table defined by `<tbody>`, `<thead>`, and `<tfoot>`.
126    pub row_groups: Vec<ArcRefCell<TableTrackGroup>>,
127
128    /// The rows of this table defined by `<tr>` or `display: table-row` elements.
129    pub rows: Vec<ArcRefCell<TableTrack>>,
130
131    /// The content of the slots of this table.
132    pub slots: Vec<Vec<TableSlot>>,
133
134    /// The size of this table.
135    pub size: TableSize,
136
137    /// Whether or not this Table is anonymous.
138    anonymous: bool,
139
140    /// Whether percentage columns are taken into account during inline content sizes calculation.
141    percentage_columns_allowed_for_inline_content_sizes: bool,
142}
143
144impl Table {
145    pub(crate) fn new(
146        style: Arc<ComputedValues>,
147        grid_style: Arc<ComputedValues>,
148        base_fragment_info: BaseFragmentInfo,
149        percentage_columns_allowed_for_inline_content_sizes: bool,
150    ) -> Self {
151        Self {
152            style,
153            grid_style,
154            grid_base_fragment_info: base_fragment_info,
155            captions: Vec::new(),
156            column_groups: Vec::new(),
157            columns: Vec::new(),
158            row_groups: Vec::new(),
159            rows: Vec::new(),
160            slots: Vec::new(),
161            size: TableSize::zero(),
162            anonymous: false,
163            percentage_columns_allowed_for_inline_content_sizes,
164        }
165    }
166
167    /// Return the slot at the given coordinates, if it exists in the table, otherwise
168    /// return None.
169    fn get_slot(&self, coords: TableSlotCoordinates) -> Option<&TableSlot> {
170        self.slots.get(coords.y)?.get(coords.x)
171    }
172
173    fn resolve_first_cell_coords(
174        &self,
175        coords: TableSlotCoordinates,
176    ) -> Option<TableSlotCoordinates> {
177        match self.get_slot(coords) {
178            Some(&TableSlot::Cell(_)) => Some(coords),
179            Some(TableSlot::Spanned(offsets)) => Some(coords - offsets[0]),
180            _ => None,
181        }
182    }
183
184    fn resolve_first_cell(
185        &self,
186        coords: TableSlotCoordinates,
187    ) -> Option<AtomicRef<'_, TableSlotCell>> {
188        let resolved_coords = self.resolve_first_cell_coords(coords)?;
189        let slot = self.get_slot(resolved_coords);
190        match slot {
191            Some(TableSlot::Cell(cell)) => Some(cell.borrow()),
192            _ => unreachable!(
193                "Spanned slot should not point to an empty cell or another spanned slot."
194            ),
195        }
196    }
197
198    pub(crate) fn repair_style(
199        &mut self,
200        context: &SharedStyleContext,
201        new_style: &Arc<ComputedValues>,
202    ) {
203        self.style = new_style.clone();
204        self.grid_style = context.stylist.style_for_anonymous::<ServoLayoutElement>(
205            &context.guards,
206            &PseudoElement::ServoTableGrid,
207            new_style,
208        );
209    }
210}
211
212type TableSlotCoordinates = Point2D<usize, UnknownUnit>;
213pub type TableSlotOffset = Vector2D<usize, UnknownUnit>;
214
215#[derive(Debug, MallocSizeOf)]
216pub struct TableSlotCell {
217    /// The [`LayoutBoxBase`] of this table cell.
218    base: LayoutBoxBase,
219
220    /// The contents of this cell, with its own layout.
221    contents: BlockFormattingContext,
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        Self {
234            base: LayoutBoxBase::new(
235                BaseFragmentInfo::new_for_testing(id),
236                ComputedValues::initial_values_with_font_override(Font::initial_values()).to_arc(),
237            ),
238            contents: BlockFormattingContext {
239                contents: BlockContainer::BlockLevelBoxes(Vec::new()),
240                contains_floats: false,
241            },
242            colspan,
243            rowspan,
244        }
245    }
246
247    /// Get the node id of this cell's [`BaseFragmentInfo`]. This is used for unit tests.
248    pub fn node_id(&self) -> usize {
249        self.base.base_fragment_info.tag.map_or(0, |tag| tag.node.0)
250    }
251
252    fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
253        self.base.repair_style(new_style);
254    }
255}
256
257/// A single table slot. It may be an actual cell, or a reference
258/// to a previous cell that is spanned here
259///
260/// In case of table model errors, it may be multiple references
261#[derive(MallocSizeOf)]
262pub enum TableSlot {
263    /// A table cell, with a colspan and a rowspan.
264    Cell(ArcRefCell<TableSlotCell>),
265
266    /// This slot is spanned by one or more multiple cells earlier in the table, which are
267    /// found at the given negative coordinate offsets. The vector is in the order of most
268    /// recent to earliest cell.
269    ///
270    /// If there is more than one cell that spans a slot, this is a table model error, but
271    /// we still keep track of it. See
272    /// <https://html.spec.whatwg.org/multipage/#table-model-error>
273    Spanned(Vec<TableSlotOffset>),
274
275    /// An empty spot in the table. This can happen when there is a gap in columns between
276    /// cells that are defined and one which should exist because of cell with a rowspan
277    /// from a previous row.
278    Empty,
279}
280
281impl std::fmt::Debug for TableSlot {
282    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
283        match self {
284            Self::Cell(_) => f.debug_tuple("Cell").finish(),
285            Self::Spanned(spanned) => f.debug_tuple("Spanned").field(spanned).finish(),
286            Self::Empty => write!(f, "Empty"),
287        }
288    }
289}
290
291impl TableSlot {
292    fn new_spanned(offset: TableSlotOffset) -> Self {
293        Self::Spanned(vec![offset])
294    }
295}
296
297/// A row or column of a table.
298#[derive(Debug, MallocSizeOf)]
299pub struct TableTrack {
300    /// The [`LayoutBoxBase`] of this [`TableTrack`].
301    base: LayoutBoxBase,
302
303    /// The index of the table row or column group parent in the table's list of row or column
304    /// groups.
305    group_index: Option<usize>,
306
307    /// Whether or not this [`TableTrack`] was anonymous, for instance created due to
308    /// a `span` attribute set on a parent `<colgroup>`.
309    is_anonymous: bool,
310
311    /// A shared container for this track's style, used to share the style for the purposes
312    /// of drawing backgrounds in individual cells. This allows updating the style in a
313    /// single place and having it affect all cell `Fragment`s.
314    shared_background_style: SharedStyle,
315}
316
317impl TableTrack {
318    fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
319        self.base.repair_style(new_style);
320        self.shared_background_style = SharedStyle::new(new_style.clone());
321    }
322}
323
324#[derive(Debug, MallocSizeOf, PartialEq)]
325pub enum TableTrackGroupType {
326    HeaderGroup,
327    FooterGroup,
328    RowGroup,
329    ColumnGroup,
330}
331
332#[derive(Debug, MallocSizeOf)]
333pub struct TableTrackGroup {
334    /// The [`LayoutBoxBase`] of this [`TableTrackGroup`].
335    base: LayoutBoxBase,
336
337    /// The type of this [`TableTrackGroup`].
338    group_type: TableTrackGroupType,
339
340    /// The range of tracks in this [`TableTrackGroup`].
341    track_range: Range<usize>,
342
343    /// A shared container for this track's style, used to share the style for the purposes
344    /// of drawing backgrounds in individual cells. This allows updating the style in a
345    /// single place and having it affect all cell `Fragment`s.
346    shared_background_style: SharedStyle,
347}
348
349impl TableTrackGroup {
350    pub(super) fn is_empty(&self) -> bool {
351        self.track_range.is_empty()
352    }
353
354    fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
355        self.base.repair_style(new_style);
356        self.shared_background_style = SharedStyle::new(new_style.clone());
357    }
358}
359
360#[derive(Debug, MallocSizeOf)]
361pub struct TableCaption {
362    /// The contents of this cell, with its own layout.
363    context: IndependentFormattingContext,
364}
365
366/// A calculated collapsed border.
367#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)]
368pub(crate) struct CollapsedBorder {
369    pub style_color: BorderStyleColor,
370    pub width: Au,
371}
372
373/// Represents a piecewise sequence of collapsed borders along a line.
374pub(crate) type CollapsedBorderLine = Vec<CollapsedBorder>;
375
376#[derive(Clone, Debug, MallocSizeOf)]
377pub(crate) struct SpecificTableGridInfo {
378    pub collapsed_borders: PhysicalVec<Vec<CollapsedBorderLine>>,
379    pub track_sizes: PhysicalVec<Vec<Au>>,
380}
381
382pub(crate) struct TableLayoutStyle<'a> {
383    table: &'a Table,
384    layout: Option<&'a TableLayout<'a>>,
385}
386
387/// Table parts that are stored in the DOM. This is used in order to map from
388/// the DOM to the box tree and will eventually be important for incremental
389/// layout.
390#[derive(Debug, MallocSizeOf)]
391pub(crate) enum TableLevelBox {
392    Caption(ArcRefCell<TableCaption>),
393    Cell(ArcRefCell<TableSlotCell>),
394    TrackGroup(ArcRefCell<TableTrackGroup>),
395    Track(ArcRefCell<TableTrack>),
396}
397
398impl TableLevelBox {
399    pub(crate) fn with_base<T>(&self, callback: impl FnOnce(&LayoutBoxBase) -> T) -> T {
400        match self {
401            TableLevelBox::Caption(caption) => callback(&caption.borrow().context.base),
402            TableLevelBox::Cell(cell) => callback(&cell.borrow().base),
403            TableLevelBox::TrackGroup(track_group) => callback(&track_group.borrow().base),
404            TableLevelBox::Track(track) => callback(&track.borrow().base),
405        }
406    }
407
408    pub(crate) fn with_base_mut<T>(&mut self, callback: impl FnOnce(&mut LayoutBoxBase) -> T) -> T {
409        match self {
410            TableLevelBox::Caption(caption) => callback(&mut caption.borrow_mut().context.base),
411            TableLevelBox::Cell(cell) => callback(&mut cell.borrow_mut().base),
412            TableLevelBox::TrackGroup(track_group) => callback(&mut track_group.borrow_mut().base),
413            TableLevelBox::Track(track) => callback(&mut track.borrow_mut().base),
414        }
415    }
416
417    pub(crate) fn repair_style(
418        &self,
419        context: &SharedStyleContext<'_>,
420        node: &ServoThreadSafeLayoutNode,
421        new_style: &Arc<ComputedValues>,
422    ) {
423        match self {
424            TableLevelBox::Caption(caption) => caption
425                .borrow_mut()
426                .context
427                .repair_style(context, node, new_style),
428            TableLevelBox::Cell(cell) => cell.borrow_mut().repair_style(new_style),
429            TableLevelBox::TrackGroup(track_group) => {
430                track_group.borrow_mut().repair_style(new_style);
431            },
432            TableLevelBox::Track(track) => track.borrow_mut().repair_style(new_style),
433        }
434    }
435
436    pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
437        match self {
438            Self::Caption(caption) => caption.borrow().context.attached_to_tree(layout_box),
439            Self::Cell(cell) => cell.borrow().contents.attached_to_tree(layout_box),
440            Self::TrackGroup(_) | Self::Track(_) => {
441                // The parentage of tracks within a track group, and cells within a row, is handled
442                // when the entire table is attached to the tree.
443            },
444        }
445    }
446
447    pub(crate) fn downgrade(&self) -> WeakTableLevelBox {
448        match self {
449            Self::Caption(caption) => WeakTableLevelBox::Caption(caption.downgrade()),
450            Self::Cell(cell) => WeakTableLevelBox::Cell(cell.downgrade()),
451            Self::TrackGroup(track_group) => WeakTableLevelBox::TrackGroup(track_group.downgrade()),
452            Self::Track(track) => WeakTableLevelBox::Track(track.downgrade()),
453        }
454    }
455}
456
457#[derive(Clone, Debug, MallocSizeOf)]
458pub(crate) enum WeakTableLevelBox {
459    Caption(WeakRefCell<TableCaption>),
460    Cell(WeakRefCell<TableSlotCell>),
461    TrackGroup(WeakRefCell<TableTrackGroup>),
462    Track(WeakRefCell<TableTrack>),
463}
464
465impl WeakTableLevelBox {
466    pub(crate) fn upgrade(&self) -> Option<TableLevelBox> {
467        Some(match self {
468            Self::Caption(caption) => TableLevelBox::Caption(caption.upgrade()?),
469            Self::Cell(cell) => TableLevelBox::Cell(cell.upgrade()?),
470            Self::TrackGroup(track_group) => TableLevelBox::TrackGroup(track_group.upgrade()?),
471            Self::Track(track) => TableLevelBox::Track(track.upgrade()?),
472        })
473    }
474}