1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
/* This Source Code Form is subject to the terms of the Mozilla Public
 * 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/. */
#![allow(rustdoc::private_intra_doc_links)]

//! # HTML Tables (╯°□°)╯︵ ┻━┻
//!
//! This implementation is based on the [table section of the HTML 5 Specification][1],
//! the draft [CSS Table Module Level! 3][2] and the [LayoutNG implementation of tables][3] in Blink.
//! In general, the draft specification differs greatly from what other browsers do, so we
//! generally follow LayoutNG when in question.
//!
//! [1]: https://html.spec.whatwg.org/multipage/#tables
//! [2]: https://drafts.csswg.org/css-tables
//! [3]: https://source.chromium.org/chromium/chromium/src/third_party/+/main:blink/renderer/core/layout/table
//!
//! Table layout is divided into two phases:
//!
//! 1. Box Tree Construction
//! 2. Fragment Tree Construction
//!
//! ## Box Tree Construction
//!
//! During box tree construction, table layout (`construct.rs`) will traverse the DOM and construct
//! the basic box tree representation of a table, using the structs defined in this file ([`Table`],
//! [`TableTrackGroup`], [`TableTrack`], etc). When processing the DOM, elements are handled
//! differently depending on their `display` value. For instance, an element with `display:
//! table-cell` is treated as a table cell. HTML table elements like `<table>` and `<td>` are
//! assigned the corresponding display value from the user agent stylesheet.
//!
//! Every [`Table`] holds an array of [`TableSlot`]. A [`TableSlot`] represents either a cell, a cell
//! location occupied by a cell originating from another place in the table, or is empty. In
//! addition, when there are table model errors, a slot may spanned by more than one cell.
//!
//! During processing, the box tree construction agorithm will also fix up table structure, for
//! instance, creating anonymous rows for lone table cells and putting non-table content into
//! anonymous cells. In addition, flow layout will collect table elements outside of tables and create
//! anonymous tables for them.
//!
//! After processing, box tree construction does a fix up pass on tables, converting rowspan=0 into
//! definite rowspan values and making sure that rowspan and celspan values are not larger than the
//! table itself. Finally, row groups may be reordered to enforce the fact that the first `<thead>`
//! comes before `<tbody>` which comes before the first `<tfoot>`.
//!
//! ## Fragment Tree Construction
//!
//! Fragment tree construction involves calculating the size and positioning of all table elements,
//! given their style, content, and cell and row spans. This happens both during intrinsic inline
//! size computation as well as layout into Fragments. In both of these cases, measurement and
//! layout is done by [`layout::TableLayout`], though for intrinsic size computation only a partial
//! layout is done.
//!
//! In general, we follow the following steps when laying out table content:
//!
//! 1. Compute track constrainedness and has originating cells
//! 2. Compute cell measures
//! 3. Compute column measures
//! 4. Compute intrinsic inline sizes for columns and the table
//! 5. Compute the final table inline size
//! 6. Distribute size to columns
//! 7. Do first pass cell layout
//! 8. Do row layout
//! 9. Compute table height and final row sizes
//! 10. Create fragments for table elements (columns, column groups, rows, row groups, cells)
//!
//! For intrinsic size computation this process stops at step 4.

mod construct;
mod layout;

use std::ops::Range;

pub(crate) use construct::AnonymousTableContent;
pub use construct::TableBuilder;
use euclid::{Point2D, Size2D, UnknownUnit, Vector2D};
use serde::Serialize;
use servo_arc::Arc;
use style::properties::style_structs::Font;
use style::properties::ComputedValues;
use style_traits::dom::OpaqueNode;

use super::flow::BlockFormattingContext;
use crate::cell::ArcRefCell;
use crate::flow::BlockContainer;
use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragment_tree::BaseFragmentInfo;
use crate::layout_box_base::LayoutBoxBase;

pub type TableSize = Size2D<usize, UnknownUnit>;

#[derive(Debug, Serialize)]
pub struct Table {
    /// The style of this table. These are the properties that apply to the "wrapper" ie the element
    /// that contains both the grid and the captions. Not all properties are actually used on the
    /// wrapper though, such as background and borders, which apply to the grid.
    #[serde(skip_serializing)]
    style: Arc<ComputedValues>,

    /// The style of this table's grid. This is an anonymous style based on the table's style, but
    /// eliminating all the properties handled by the "wrapper."
    #[serde(skip_serializing)]
    grid_style: Arc<ComputedValues>,

    /// The [`BaseFragmentInfo`] for this table's grid. This is necessary so that when the
    /// grid has a background image, it can be associated with the table's node.
    grid_base_fragment_info: BaseFragmentInfo,

    /// The captions for this table.
    pub captions: Vec<TableCaption>,

    /// The column groups for this table.
    pub column_groups: Vec<TableTrackGroup>,

    /// The columns of this table defined by `<colgroup> | display: table-column-group`
    /// and `<col> | display: table-column` elements as well as `display: table-column`.
    pub columns: Vec<TableTrack>,

    /// The rows groups for this table defined by `<tbody>`, `<thead>`, and `<tfoot>`.
    pub row_groups: Vec<TableTrackGroup>,

    /// The rows of this table defined by `<tr>` or `display: table-row` elements.
    pub rows: Vec<TableTrack>,

    /// The content of the slots of this table.
    pub slots: Vec<Vec<TableSlot>>,

    /// The size of this table.
    pub size: TableSize,

    /// Whether or not this Table is anonymous.
    anonymous: bool,
}

impl Table {
    pub(crate) fn new(
        style: Arc<ComputedValues>,
        grid_style: Arc<ComputedValues>,
        base_fragment_info: BaseFragmentInfo,
    ) -> Self {
        Self {
            style,
            grid_style,
            grid_base_fragment_info: base_fragment_info,
            captions: Vec::new(),
            column_groups: Vec::new(),
            columns: Vec::new(),
            row_groups: Vec::new(),
            rows: Vec::new(),
            slots: Vec::new(),
            size: TableSize::zero(),
            anonymous: false,
        }
    }

    /// Return the slot at the given coordinates, if it exists in the table, otherwise
    /// return None.
    fn get_slot(&self, coords: TableSlotCoordinates) -> Option<&TableSlot> {
        self.slots.get(coords.y)?.get(coords.x)
    }

    fn resolve_first_cell_coords(
        &self,
        coords: TableSlotCoordinates,
    ) -> Option<TableSlotCoordinates> {
        match self.get_slot(coords) {
            Some(&TableSlot::Cell(_)) => Some(coords),
            Some(TableSlot::Spanned(offsets)) => Some(coords - offsets[0]),
            _ => 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(cell)) => Some(cell),
            _ => unreachable!(
                "Spanned slot should not point to an empty cell or another spanned slot."
            ),
        }
    }
}

type TableSlotCoordinates = Point2D<usize, UnknownUnit>;
pub type TableSlotOffset = Vector2D<usize, UnknownUnit>;

#[derive(Debug, Serialize)]
pub struct TableSlotCell {
    /// The [`LayoutBoxBase`] of this table cell.
    base: LayoutBoxBase,

    /// The contents of this cell, with its own layout.
    contents: BlockFormattingContext,

    /// Number of columns that the cell is to span. Must be greater than zero.
    colspan: usize,

    /// Number of rows that the cell is to span. Zero means that the cell is to span all
    /// the remaining rows in the row group.
    rowspan: usize,
}

impl TableSlotCell {
    pub fn mock_for_testing(id: usize, colspan: usize, rowspan: usize) -> Self {
        Self {
            base: LayoutBoxBase::new(
                BaseFragmentInfo::new_for_node(OpaqueNode(id)),
                ComputedValues::initial_values_with_font_override(Font::initial_values()).to_arc(),
            ),
            contents: BlockFormattingContext {
                contents: BlockContainer::BlockLevelBoxes(Vec::new()),
                contains_floats: false,
            },
            colspan,
            rowspan,
        }
    }

    /// Get the node id of this cell's [`BaseFragmentInfo`]. This is used for unit tests.
    pub fn node_id(&self) -> usize {
        self.base.base_fragment_info.tag.map_or(0, |tag| tag.node.0)
    }
}

#[derive(Serialize)]
/// A single table slot. It may be an actual cell, or a reference
/// to a previous cell that is spanned here
///
/// In case of table model errors, it may be multiple references
pub enum TableSlot {
    /// A table cell, with a colspan and a rowspan.
    Cell(TableSlotCell),

    /// This slot is spanned by one or more multiple cells earlier in the table, which are
    /// found at the given negative coordinate offsets. The vector is in the order of most
    /// recent to earliest cell.
    ///
    /// If there is more than one cell that spans a slot, this is a table model error, but
    /// we still keep track of it. See
    /// <https://html.spec.whatwg.org/multipage/#table-model-error>
    Spanned(Vec<TableSlotOffset>),

    /// An empty spot in the table. This can happen when there is a gap in columns between
    /// cells that are defined and one which should exist because of cell with a rowspan
    /// from a previous row.
    Empty,
}

impl std::fmt::Debug for TableSlot {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Cell(_) => f.debug_tuple("Cell").finish(),
            Self::Spanned(spanned) => f.debug_tuple("Spanned").field(spanned).finish(),
            Self::Empty => write!(f, "Empty"),
        }
    }
}

impl TableSlot {
    fn new_spanned(offset: TableSlotOffset) -> Self {
        Self::Spanned(vec![offset])
    }
}

/// A row or column of a table.
#[derive(Clone, Debug, Serialize)]
pub struct TableTrack {
    /// The [`BaseFragmentInfo`] of this cell.
    base_fragment_info: BaseFragmentInfo,

    /// The style of this table column.
    #[serde(skip_serializing)]
    style: Arc<ComputedValues>,

    /// The index of the table row or column group parent in the table's list of row or column
    /// groups.
    group_index: Option<usize>,

    /// Whether or not this [`TableTrack`] was anonymous, for instance created due to
    /// a `span` attribute set on a parent `<colgroup>`.
    is_anonymous: bool,
}

#[derive(Debug, PartialEq, Serialize)]
pub enum TableTrackGroupType {
    HeaderGroup,
    FooterGroup,
    RowGroup,
    ColumnGroup,
}

#[derive(Debug, Serialize)]
pub struct TableTrackGroup {
    /// The [`BaseFragmentInfo`] of this [`TableTrackGroup`].
    base_fragment_info: BaseFragmentInfo,

    /// The style of this [`TableTrackGroup`].
    #[serde(skip_serializing)]
    style: Arc<ComputedValues>,

    /// The type of this [`TableTrackGroup`].
    group_type: TableTrackGroupType,

    /// The range of tracks in this [`TableTrackGroup`].
    track_range: Range<usize>,
}

impl TableTrackGroup {
    pub(super) fn is_empty(&self) -> bool {
        self.track_range.is_empty()
    }
}

#[derive(Debug, Serialize)]
pub struct TableCaption {
    /// The contents of this cell, with its own layout.
    context: ArcRefCell<IndependentFormattingContext>,
}