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