blitz-pdf 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/THIRD_PARTY_NOTICES.md +5 -2
- package/docs/layout-regressions.md +65 -0
- package/docs/pagination-regressions.md +89 -0
- package/package.json +2 -1
- package/vendor/README.md +35 -0
- package/vendor/blitz-dom/Cargo.toml +113 -0
- package/vendor/blitz-dom/LICENSE-APACHE +176 -0
- package/vendor/blitz-dom/LICENSE-MIT +23 -0
- package/vendor/blitz-dom/assets/default.css +1142 -0
- package/vendor/blitz-dom/assets/moz-bullet-font.otf +0 -0
- package/vendor/blitz-dom/src/accessibility.rs +255 -0
- package/vendor/blitz-dom/src/config.rs +97 -0
- package/vendor/blitz-dom/src/cssom.rs +971 -0
- package/vendor/blitz-dom/src/debug.rs +206 -0
- package/vendor/blitz-dom/src/document.rs +3395 -0
- package/vendor/blitz-dom/src/events/driver.rs +427 -0
- package/vendor/blitz-dom/src/events/focus.rs +40 -0
- package/vendor/blitz-dom/src/events/ime.rs +31 -0
- package/vendor/blitz-dom/src/events/keyboard.rs +174 -0
- package/vendor/blitz-dom/src/events/mod.rs +315 -0
- package/vendor/blitz-dom/src/events/pointer.rs +849 -0
- package/vendor/blitz-dom/src/font_metrics.rs +177 -0
- package/vendor/blitz-dom/src/form.rs +463 -0
- package/vendor/blitz-dom/src/html.rs +38 -0
- package/vendor/blitz-dom/src/iframe.rs +186 -0
- package/vendor/blitz-dom/src/layout/construct.rs +1328 -0
- package/vendor/blitz-dom/src/layout/damage.rs +642 -0
- package/vendor/blitz-dom/src/layout/inline.rs +1160 -0
- package/vendor/blitz-dom/src/layout/list.rs +237 -0
- package/vendor/blitz-dom/src/layout/mod.rs +969 -0
- package/vendor/blitz-dom/src/layout/paint_tree.rs +462 -0
- package/vendor/blitz-dom/src/layout/replaced.rs +439 -0
- package/vendor/blitz-dom/src/layout/table.rs +967 -0
- package/vendor/blitz-dom/src/layout/text_transform.rs +979 -0
- package/vendor/blitz-dom/src/layout/writing_mode.rs +437 -0
- package/vendor/blitz-dom/src/lib.rs +160 -0
- package/vendor/blitz-dom/src/mutator.rs +1818 -0
- package/vendor/blitz-dom/src/net.rs +649 -0
- package/vendor/blitz-dom/src/node/attributes.rs +60 -0
- package/vendor/blitz-dom/src/node/custom_widget.rs +178 -0
- package/vendor/blitz-dom/src/node/element.rs +1106 -0
- package/vendor/blitz-dom/src/node/mod.rs +30 -0
- package/vendor/blitz-dom/src/node/node.rs +2036 -0
- package/vendor/blitz-dom/src/node/scrollbar.rs +237 -0
- package/vendor/blitz-dom/src/node/serialize.rs +225 -0
- package/vendor/blitz-dom/src/node/stylo_data.rs +142 -0
- package/vendor/blitz-dom/src/node/svg.rs +265 -0
- package/vendor/blitz-dom/src/node/text.rs +804 -0
- package/vendor/blitz-dom/src/query_selector.rs +275 -0
- package/vendor/blitz-dom/src/resolve.rs +474 -0
- package/vendor/blitz-dom/src/resolved_style.rs +797 -0
- package/vendor/blitz-dom/src/scrolling.rs +780 -0
- package/vendor/blitz-dom/src/selection.rs +122 -0
- package/vendor/blitz-dom/src/stylo.rs +1518 -0
- package/vendor/blitz-dom/src/stylo_device.rs +88 -0
- package/vendor/blitz-dom/src/stylo_to_cursor_icon.rs +49 -0
- package/vendor/blitz-dom/src/stylo_to_kurbo.rs +129 -0
- package/vendor/blitz-dom/src/stylo_to_parley.rs +655 -0
- package/vendor/blitz-dom/src/traversal.rs +600 -0
- package/vendor/blitz-dom/src/tree.rs +136 -0
- package/vendor/blitz-dom/src/url.rs +63 -0
- package/vendor/blitz-dom/src/util.rs +256 -0
- package/wasm/blitz_pdf_bg.wasm +0 -0
|
@@ -0,0 +1,967 @@
|
|
|
1
|
+
use blitz_traits::node_id::NodeId;
|
|
2
|
+
use std::{ops::Range, sync::Arc};
|
|
3
|
+
use style::logical_geometry::WritingMode;
|
|
4
|
+
use style::properties::ComputedValues;
|
|
5
|
+
|
|
6
|
+
use atomic_refcell::AtomicRefCell;
|
|
7
|
+
use markup5ever::local_name;
|
|
8
|
+
use style::properties::style_structs::Border;
|
|
9
|
+
use style::servo_arc::Arc as ServoArc;
|
|
10
|
+
use style::typed_om::NumericBaseType;
|
|
11
|
+
use style::values::computed::calc::CalcPercentageLeaf;
|
|
12
|
+
use style::values::computed::length_percentage::{
|
|
13
|
+
CalcLengthPercentage, CalcNode, ComputedLeaf, Unpacked as UnpackedLengthPercentage,
|
|
14
|
+
};
|
|
15
|
+
use style::values::computed::{Display, Length, LengthPercentage};
|
|
16
|
+
use style::values::generics::Optional;
|
|
17
|
+
use style::values::specified::box_::{DisplayInside, DisplayOutside};
|
|
18
|
+
use style::{
|
|
19
|
+
Atom, computed_values::border_collapse::T as BorderCollapse,
|
|
20
|
+
computed_values::table_layout::T as TableLayout, values::computed::BorderStyle,
|
|
21
|
+
};
|
|
22
|
+
use style_traits::values::specified::AllowedNumericType;
|
|
23
|
+
use taffy::{
|
|
24
|
+
DetailedGridInfo, LayoutPartialTree as _, ResolveOrZero, TrackSizingFunction, style_helpers,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
use crate::BaseDocument;
|
|
28
|
+
use crate::layout::LayoutPassState;
|
|
29
|
+
use thin_vec::ThinVec;
|
|
30
|
+
|
|
31
|
+
use super::construct::create_anonymous_box;
|
|
32
|
+
use super::damage::{CONSTRUCT_BOX, CONSTRUCT_DESCENDENT, CONSTRUCT_FC};
|
|
33
|
+
use super::resolve_calc_value;
|
|
34
|
+
|
|
35
|
+
/// CSS 2.1 §17.2.1: consecutive improper table children share an anonymous
|
|
36
|
+
/// row, and consecutive non-cell row children share an anonymous cell.
|
|
37
|
+
/// Keep these boxes out of the DOM so selectors still see the author's tree.
|
|
38
|
+
fn table_children(
|
|
39
|
+
doc: &mut BaseDocument,
|
|
40
|
+
parent_id: NodeId,
|
|
41
|
+
children: &[NodeId],
|
|
42
|
+
anonymous: &mut ThinVec<NodeId>,
|
|
43
|
+
) -> Vec<NodeId> {
|
|
44
|
+
fn flatten(doc: &BaseDocument, children: &[NodeId], output: &mut Vec<NodeId>) {
|
|
45
|
+
for &id in children {
|
|
46
|
+
let node = &doc.nodes[id];
|
|
47
|
+
if node.data.kind() == crate::node::NodeKind::Comment {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if node
|
|
51
|
+
.display_style()
|
|
52
|
+
.is_some_and(|d| d.inside() == DisplayInside::Contents)
|
|
53
|
+
{
|
|
54
|
+
flatten(doc, &node.children, output);
|
|
55
|
+
} else {
|
|
56
|
+
output.push(id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
let is_row = doc.nodes[parent_id]
|
|
61
|
+
.display_style()
|
|
62
|
+
.is_some_and(|d| d.inside() == DisplayInside::TableRow);
|
|
63
|
+
let mut flat = Vec::new();
|
|
64
|
+
flatten(doc, children, &mut flat);
|
|
65
|
+
let mut output = Vec::new();
|
|
66
|
+
let mut pending = Vec::new();
|
|
67
|
+
let flush = |doc: &mut BaseDocument,
|
|
68
|
+
pending: &mut Vec<NodeId>,
|
|
69
|
+
output: &mut Vec<NodeId>,
|
|
70
|
+
anonymous: &mut ThinVec<NodeId>| {
|
|
71
|
+
if pending
|
|
72
|
+
.iter()
|
|
73
|
+
.any(|id| !doc.nodes[*id].is_collapsible_whitespace_node())
|
|
74
|
+
{
|
|
75
|
+
let display = if is_row {
|
|
76
|
+
Display::TableCell
|
|
77
|
+
} else {
|
|
78
|
+
Display::TableRow
|
|
79
|
+
};
|
|
80
|
+
let id = create_anonymous_box(doc, parent_id, display);
|
|
81
|
+
doc.nodes[id].children.extend(pending.drain(..));
|
|
82
|
+
anonymous.push(id);
|
|
83
|
+
output.push(id);
|
|
84
|
+
} else {
|
|
85
|
+
pending.clear();
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
for id in flat {
|
|
89
|
+
let node = &doc.nodes[id];
|
|
90
|
+
let display = node.display_style();
|
|
91
|
+
let is_oof = node
|
|
92
|
+
.primary_styles()
|
|
93
|
+
.is_some_and(|s| s.clone_position().is_absolutely_positioned());
|
|
94
|
+
// Hidden and out-of-flow siblings do not split a consecutive run of
|
|
95
|
+
// in-flow content into separate anonymous cells (and separate margins).
|
|
96
|
+
if is_oof || display.is_some_and(|d| d.outside() == DisplayOutside::None) {
|
|
97
|
+
output.push(id);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
let proper = display.is_some_and(|d| {
|
|
101
|
+
if is_row {
|
|
102
|
+
matches!(
|
|
103
|
+
d.inside(),
|
|
104
|
+
DisplayInside::TableCell
|
|
105
|
+
| DisplayInside::TableColumn
|
|
106
|
+
| DisplayInside::TableColumnGroup
|
|
107
|
+
)
|
|
108
|
+
} else {
|
|
109
|
+
d.outside() == DisplayOutside::TableCaption
|
|
110
|
+
|| matches!(
|
|
111
|
+
d.inside(),
|
|
112
|
+
DisplayInside::TableRow
|
|
113
|
+
| DisplayInside::TableRowGroup
|
|
114
|
+
| DisplayInside::TableHeaderGroup
|
|
115
|
+
| DisplayInside::TableFooterGroup
|
|
116
|
+
| DisplayInside::TableColumn
|
|
117
|
+
| DisplayInside::TableColumnGroup
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
if proper {
|
|
122
|
+
flush(doc, &mut pending, &mut output, anonymous);
|
|
123
|
+
output.push(id);
|
|
124
|
+
} else {
|
|
125
|
+
pending.push(id);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
flush(doc, &mut pending, &mut output, anonymous);
|
|
129
|
+
output
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
pub struct TableTreeWrapper<'a, 'doc> {
|
|
133
|
+
pub(crate) doc: &'a mut LayoutPassState<'doc>,
|
|
134
|
+
pub(crate) ctx: Arc<TableContext>,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Deliberately not `Clone`: `style` may hold raw pointers into `calc_values`.
|
|
138
|
+
#[derive(Debug)]
|
|
139
|
+
pub struct TableContext {
|
|
140
|
+
pub style: taffy::Style<Atom>,
|
|
141
|
+
pub cells: Vec<TableCell>,
|
|
142
|
+
/// Absolutely positioned descendants of the table's row groups and rows (and of the
|
|
143
|
+
/// table itself). They take no part in the table grid.
|
|
144
|
+
pub oof_children: Vec<(NodeId, taffy::Position)>,
|
|
145
|
+
pub rows: Vec<TableRow>,
|
|
146
|
+
pub columns: Vec<TableColumn>,
|
|
147
|
+
pub computed_grid_info: AtomicRefCell<Option<DetailedGridInfo<Atom>>>,
|
|
148
|
+
pub border_style: Option<ServoArc<Border>>,
|
|
149
|
+
pub border_collapse: BorderCollapse,
|
|
150
|
+
/// Backing storage for `calc()` track sizes synthesised by the table layout code.
|
|
151
|
+
/// Taffy stores calc values as raw pointers, so these must outlive the `style`.
|
|
152
|
+
#[allow(dead_code)]
|
|
153
|
+
calc_values: Vec<LengthPercentage>,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// #[derive(Debug, Clone, Eq, PartialEq)]
|
|
157
|
+
// pub enum TableItemKind {
|
|
158
|
+
// Row,
|
|
159
|
+
// Cell,
|
|
160
|
+
// }
|
|
161
|
+
|
|
162
|
+
#[derive(Debug, Clone)]
|
|
163
|
+
pub struct TableCell {
|
|
164
|
+
// kind: TableItemKind,
|
|
165
|
+
pub node_id: NodeId,
|
|
166
|
+
/// Index (into `TableContext::rows`) of the row the cell originates in
|
|
167
|
+
pub row: u16,
|
|
168
|
+
/// Index of the column the cell originates in
|
|
169
|
+
pub column: u16,
|
|
170
|
+
style: taffy::Style<Atom>,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// Tracks the current column position while walking the table's cells, so that
|
|
174
|
+
/// cells are assigned the same columns Taffy's dense auto-placement will give them
|
|
175
|
+
/// (skipping columns still occupied by rowspan cells from earlier rows).
|
|
176
|
+
#[derive(Debug, Default)]
|
|
177
|
+
struct ColumnCursor {
|
|
178
|
+
/// The column the next cell in the current row will be placed in
|
|
179
|
+
col: u16,
|
|
180
|
+
/// The total number of columns seen so far
|
|
181
|
+
num_columns: u16,
|
|
182
|
+
/// For each column, the number of further rows it is occupied by a rowspan cell
|
|
183
|
+
rowspans: Vec<u16>,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
impl ColumnCursor {
|
|
187
|
+
fn start_row(&mut self) {
|
|
188
|
+
self.col = 0;
|
|
189
|
+
for remaining in self.rowspans.iter_mut() {
|
|
190
|
+
*remaining = remaining.saturating_sub(1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// Advance past occupied columns, returning the column of the next cell
|
|
195
|
+
fn next_free(&mut self) -> u16 {
|
|
196
|
+
while self.rowspans.get(self.col as usize).is_some_and(|r| *r > 0) {
|
|
197
|
+
self.col += 1;
|
|
198
|
+
}
|
|
199
|
+
self.col
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
fn place(&mut self, colspan: u16, rowspan: u16) {
|
|
203
|
+
let end = self.col + colspan;
|
|
204
|
+
if self.rowspans.len() < end as usize {
|
|
205
|
+
self.rowspans.resize(end as usize, 0);
|
|
206
|
+
}
|
|
207
|
+
for remaining in &mut self.rowspans[self.col as usize..end as usize] {
|
|
208
|
+
*remaining = rowspan - 1;
|
|
209
|
+
}
|
|
210
|
+
self.col = end;
|
|
211
|
+
self.num_columns = self.num_columns.max(end);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#[derive(Debug, Clone)]
|
|
216
|
+
pub struct TableColumn {
|
|
217
|
+
pub node_id: NodeId,
|
|
218
|
+
/// The column group (if any) that contains the column
|
|
219
|
+
pub group: Option<NodeId>,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#[derive(Debug, Clone)]
|
|
223
|
+
pub struct TableRow {
|
|
224
|
+
// kind: TableItemKind,
|
|
225
|
+
pub node_id: NodeId,
|
|
226
|
+
/// The row group (if any) that contains the row
|
|
227
|
+
pub group: Option<NodeId>,
|
|
228
|
+
/// The row's specified `height`, if it is a length
|
|
229
|
+
pub height: Option<f32>,
|
|
230
|
+
/// Whether the row or any cell originating in it (and spanning only this row) has a
|
|
231
|
+
/// specified length `height`. Such rows do not receive excess table height when there
|
|
232
|
+
/// are unconstrained rows to receive it instead.
|
|
233
|
+
pub is_constrained: bool,
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/// The used width of one border side: border widths are not adjusted for
|
|
237
|
+
/// border-style in computed styles, so a border with `none`/`hidden` style
|
|
238
|
+
/// must be treated as zero-width.
|
|
239
|
+
/// The writing mode the table's grid runs in (the table's own)
|
|
240
|
+
#[cfg_attr(not(feature = "writing-mode"), allow(unused_variables))]
|
|
241
|
+
fn table_wm(doc: &BaseDocument, table_root_node_id: NodeId) -> WritingMode {
|
|
242
|
+
#[cfg(feature = "writing-mode")]
|
|
243
|
+
{
|
|
244
|
+
if doc.try_root_element().map(|root| root.id) == Some(table_root_node_id) {
|
|
245
|
+
doc.root_layout_wm(table_root_node_id)
|
|
246
|
+
} else {
|
|
247
|
+
doc.nodes[table_root_node_id].writing_mode()
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
#[cfg(not(feature = "writing-mode"))]
|
|
251
|
+
{
|
|
252
|
+
WritingMode::empty()
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/// Convert a table part's style into the table's writing mode
|
|
257
|
+
fn table_taffy_style(style: &ComputedValues, table_wm: WritingMode) -> taffy::Style<Atom> {
|
|
258
|
+
stylo_taffy::to_taffy_style_in(style, table_wm)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
fn side_width(width: app_units::Au, style: BorderStyle) -> f32 {
|
|
262
|
+
if style.none_or_hidden() {
|
|
263
|
+
0.0
|
|
264
|
+
} else {
|
|
265
|
+
width.to_f32_px()
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/// Build a `calc(<percent> + <length>)` track sizing function. The calc value is
|
|
270
|
+
/// boxed and stored in `calc_values` so that the raw pointer Taffy holds stays valid.
|
|
271
|
+
fn percent_plus_length(
|
|
272
|
+
calc_values: &mut Vec<LengthPercentage>,
|
|
273
|
+
percent: f32,
|
|
274
|
+
length: f32,
|
|
275
|
+
) -> TrackSizingFunction {
|
|
276
|
+
let node = CalcNode::Sum(
|
|
277
|
+
vec![
|
|
278
|
+
CalcNode::Leaf(ComputedLeaf::Percentage(CalcPercentageLeaf::new(
|
|
279
|
+
percent,
|
|
280
|
+
Optional::Some(NumericBaseType::Length),
|
|
281
|
+
))),
|
|
282
|
+
CalcNode::Leaf(ComputedLeaf::Length(Length::new(length))),
|
|
283
|
+
]
|
|
284
|
+
.into(),
|
|
285
|
+
);
|
|
286
|
+
let value = LengthPercentage::new_calc(node, AllowedNumericType::NonNegative);
|
|
287
|
+
let dim = match value.unpack() {
|
|
288
|
+
UnpackedLengthPercentage::Calc(calc) => {
|
|
289
|
+
let ptr = calc as *const CalcLengthPercentage as *const ();
|
|
290
|
+
// SAFETY: the pointer targets the heap allocation owned by `value`, which
|
|
291
|
+
// is kept alive by `calc_values` for as long as the TableContext exists.
|
|
292
|
+
unsafe { taffy::Dimension::from_raw(taffy::CompactLength::calc(ptr)) }
|
|
293
|
+
}
|
|
294
|
+
UnpackedLengthPercentage::Length(len) => style_helpers::length(len.px()),
|
|
295
|
+
UnpackedLengthPercentage::Percentage(p) => style_helpers::percent(p.0),
|
|
296
|
+
};
|
|
297
|
+
calc_values.push(value);
|
|
298
|
+
dim.into()
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
pub(crate) fn build_table_context(
|
|
302
|
+
doc: &mut BaseDocument,
|
|
303
|
+
table_root_node_id: NodeId,
|
|
304
|
+
anonymous: &mut ThinVec<NodeId>,
|
|
305
|
+
) -> (TableContext, Vec<NodeId>) {
|
|
306
|
+
let mut cells: Vec<TableCell> = Vec::new();
|
|
307
|
+
let mut oof_children: Vec<(NodeId, taffy::Position)> = Vec::new();
|
|
308
|
+
let mut rows: Vec<TableRow> = Vec::new();
|
|
309
|
+
let mut row = 0u16;
|
|
310
|
+
let mut cursor = ColumnCursor::default();
|
|
311
|
+
|
|
312
|
+
let wm = table_wm(doc, table_root_node_id);
|
|
313
|
+
let root_node = &mut doc.nodes[table_root_node_id];
|
|
314
|
+
|
|
315
|
+
let dom_children = std::mem::take(&mut root_node.children);
|
|
316
|
+
|
|
317
|
+
let Some(stylo_styles) = root_node.primary_styles() else {
|
|
318
|
+
panic!("Ignoring table because it has no styles");
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
let mut style = table_taffy_style(&stylo_styles, wm);
|
|
322
|
+
style.item_is_table = true;
|
|
323
|
+
// Use `dense` row-flow so that each cell scans the row from its
|
|
324
|
+
// leftmost column for the first free track. Without `dense`,
|
|
325
|
+
// `place_definite_secondary_axis_item` keeps a per-item secondary
|
|
326
|
+
// cursor across rows, which means cells in later rows do not
|
|
327
|
+
// backfill columns freed up by rowspan cells from earlier rows.
|
|
328
|
+
style.grid_auto_flow = taffy::GridAutoFlow::RowDense;
|
|
329
|
+
style.grid_auto_columns = Vec::new();
|
|
330
|
+
style.grid_auto_rows = Vec::new();
|
|
331
|
+
|
|
332
|
+
// The fixed table layout algorithm only applies when the table has a non-auto width
|
|
333
|
+
let is_fixed = match stylo_styles.clone_table_layout() {
|
|
334
|
+
TableLayout::Fixed => !style.size.width.is_auto(),
|
|
335
|
+
TableLayout::Auto => false,
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
let border_collapse = stylo_styles.clone_border_collapse();
|
|
339
|
+
let border_spacing = stylo_styles.clone_border_spacing().0;
|
|
340
|
+
|
|
341
|
+
drop(stylo_styles);
|
|
342
|
+
|
|
343
|
+
let children = table_children(doc, table_root_node_id, &dom_children, anonymous);
|
|
344
|
+
|
|
345
|
+
let mut columns: Vec<TableColumn> = Vec::new();
|
|
346
|
+
let mut column_sizes: Vec<taffy::TrackSizingFunction> = Vec::new();
|
|
347
|
+
for child_id in children.iter().copied() {
|
|
348
|
+
collect_columns(doc, child_id, wm, &mut columns, &mut column_sizes);
|
|
349
|
+
}
|
|
350
|
+
// Percentage column widths only take effect in the fixed table layout algorithm
|
|
351
|
+
if !is_fixed {
|
|
352
|
+
for column in column_sizes.iter_mut() {
|
|
353
|
+
if column.max.into_raw().tag() == taffy::CompactLength::PERCENT_TAG {
|
|
354
|
+
*column = style_helpers::auto();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
let mut first_cell_border: Option<ServoArc<Border>> = None;
|
|
359
|
+
// Percentage widths set on first-row cells: (column index, percentage, padding + border)
|
|
360
|
+
let mut percent_columns: Vec<(u16, f32, f32)> = Vec::new();
|
|
361
|
+
let mut calc_values: Vec<LengthPercentage> = Vec::new();
|
|
362
|
+
// Header row groups are laid out before other rows, and footer row groups after
|
|
363
|
+
let row_group_order = |doc: &BaseDocument, child_id: NodeId| -> u8 {
|
|
364
|
+
let display = doc.nodes[child_id]
|
|
365
|
+
.primary_styles()
|
|
366
|
+
.map(|s| s.clone_display());
|
|
367
|
+
match display.map(|d| d.inside()) {
|
|
368
|
+
Some(DisplayInside::TableHeaderGroup) => 0,
|
|
369
|
+
Some(DisplayInside::TableFooterGroup) => 2,
|
|
370
|
+
_ => 1,
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
for order in 0..3 {
|
|
374
|
+
for child_id in children.iter().copied() {
|
|
375
|
+
if row_group_order(doc, child_id) != order {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
collect_table_cells(
|
|
379
|
+
doc,
|
|
380
|
+
child_id,
|
|
381
|
+
wm,
|
|
382
|
+
is_fixed,
|
|
383
|
+
border_collapse,
|
|
384
|
+
&mut row,
|
|
385
|
+
&mut cursor,
|
|
386
|
+
&mut cells,
|
|
387
|
+
&mut oof_children,
|
|
388
|
+
&mut rows,
|
|
389
|
+
&mut column_sizes,
|
|
390
|
+
&mut first_cell_border,
|
|
391
|
+
&mut percent_columns,
|
|
392
|
+
anonymous,
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
let remaining_column = if is_fixed {
|
|
397
|
+
style_helpers::minmax(style_helpers::length(0.0), style_helpers::fr(1.0))
|
|
398
|
+
} else {
|
|
399
|
+
style_helpers::auto()
|
|
400
|
+
};
|
|
401
|
+
// Trailing `<col>` columns which contain no cells and have no definite width
|
|
402
|
+
// (i.e. would be zero-width) are dropped, so they don't add border-spacing.
|
|
403
|
+
while column_sizes.len() > cursor.num_columns as usize
|
|
404
|
+
&& column_sizes
|
|
405
|
+
.last()
|
|
406
|
+
.is_some_and(|c| c.min.into_raw().tag() == taffy::CompactLength::AUTO_TAG)
|
|
407
|
+
{
|
|
408
|
+
column_sizes.pop();
|
|
409
|
+
columns.pop();
|
|
410
|
+
}
|
|
411
|
+
let num_columns = cursor.num_columns.max(column_sizes.len() as u16);
|
|
412
|
+
column_sizes.resize(num_columns as usize, remaining_column);
|
|
413
|
+
if is_fixed {
|
|
414
|
+
// In the fixed table layout algorithm, percentage column widths resolve against
|
|
415
|
+
// the table's content width minus the horizontal border-spacing between columns,
|
|
416
|
+
// whereas Taffy resolves percentage tracks against the container's inner width
|
|
417
|
+
// (from which only the outer border-spacing has been removed via padding). Fold
|
|
418
|
+
// the inner border-spacing into the percentage using `calc(N% - N * spacing)`.
|
|
419
|
+
let spacing_x = match border_collapse {
|
|
420
|
+
BorderCollapse::Separate => border_spacing.width.px(),
|
|
421
|
+
BorderCollapse::Collapse => 0.0,
|
|
422
|
+
};
|
|
423
|
+
let inner_spacing = spacing_x * num_columns.saturating_sub(1) as f32;
|
|
424
|
+
let mut percent_track = |percent: f32, extra: f32| -> TrackSizingFunction {
|
|
425
|
+
let extra = extra - percent * inner_spacing;
|
|
426
|
+
if extra == 0.0 {
|
|
427
|
+
style_helpers::percent(percent)
|
|
428
|
+
} else {
|
|
429
|
+
percent_plus_length(&mut calc_values, percent, extra)
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
for column in column_sizes.iter_mut() {
|
|
434
|
+
if column.max.is_auto() {
|
|
435
|
+
*column = remaining_column;
|
|
436
|
+
} else if column.max.into_raw().tag() == taffy::CompactLength::PERCENT_TAG {
|
|
437
|
+
*column = percent_track(column.max.into_raw().value(), 0.0);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Percentage widths on cells apply to the cell's content box, so the
|
|
442
|
+
// cell's horizontal padding and border must be added to the column width.
|
|
443
|
+
for (col_idx, percent, extra) in percent_columns {
|
|
444
|
+
if let Some(column) = column_sizes.get_mut(col_idx as usize) {
|
|
445
|
+
*column = percent_track(percent, extra);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
style.grid_template_columns = column_sizes.into_iter().map(|dim| dim.into()).collect();
|
|
451
|
+
// A row's `height` is a minimum: the row grows to fit the content of its cells.
|
|
452
|
+
// Excess table height is distributed to unconstrained rows (via the stretching of
|
|
453
|
+
// `auto` tracks) if there are any, and to all rows otherwise. So constrained rows
|
|
454
|
+
// use a `max-content` (rather than `auto`) maximum when there are unconstrained rows.
|
|
455
|
+
let has_unconstrained_rows = rows.iter().any(|row| !row.is_constrained);
|
|
456
|
+
style.grid_template_rows = rows
|
|
457
|
+
.iter()
|
|
458
|
+
.map(|row| {
|
|
459
|
+
let min = match row.height {
|
|
460
|
+
Some(height) => style_helpers::length(height),
|
|
461
|
+
None => style_helpers::auto(),
|
|
462
|
+
};
|
|
463
|
+
let max = if row.is_constrained && has_unconstrained_rows {
|
|
464
|
+
style_helpers::max_content()
|
|
465
|
+
} else {
|
|
466
|
+
style_helpers::auto()
|
|
467
|
+
};
|
|
468
|
+
style_helpers::minmax(min, max)
|
|
469
|
+
})
|
|
470
|
+
.collect();
|
|
471
|
+
|
|
472
|
+
style.gap = match border_collapse {
|
|
473
|
+
BorderCollapse::Separate => {
|
|
474
|
+
// In the separated borders model, `border-spacing` also applies between
|
|
475
|
+
// the table border and the outermost cells, in addition to between cells.
|
|
476
|
+
let spacing_x = border_spacing.width.px();
|
|
477
|
+
let spacing_y = border_spacing.height.px();
|
|
478
|
+
let padding = style.padding.resolve_or_zero(None, resolve_calc_value);
|
|
479
|
+
style.padding = taffy::Rect {
|
|
480
|
+
left: style_helpers::length(padding.left + spacing_x),
|
|
481
|
+
right: style_helpers::length(padding.right + spacing_x),
|
|
482
|
+
top: style_helpers::length(padding.top + spacing_y),
|
|
483
|
+
bottom: style_helpers::length(padding.bottom + spacing_y),
|
|
484
|
+
};
|
|
485
|
+
taffy::Size {
|
|
486
|
+
width: style_helpers::length(spacing_x),
|
|
487
|
+
height: style_helpers::length(spacing_y),
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
BorderCollapse::Collapse => first_cell_border
|
|
491
|
+
.as_ref()
|
|
492
|
+
.map(|border| {
|
|
493
|
+
let x = side_width(border.border_left_width.0, border.border_left_style).max(
|
|
494
|
+
side_width(border.border_right_width.0, border.border_right_style),
|
|
495
|
+
);
|
|
496
|
+
let y = side_width(border.border_top_width.0, border.border_top_style).max(
|
|
497
|
+
side_width(border.border_bottom_width.0, border.border_bottom_style),
|
|
498
|
+
);
|
|
499
|
+
taffy::Size {
|
|
500
|
+
width: style_helpers::length(x),
|
|
501
|
+
height: style_helpers::length(y),
|
|
502
|
+
}
|
|
503
|
+
})
|
|
504
|
+
.unwrap_or(taffy::Size::ZERO.map(style_helpers::length)),
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
if border_collapse == BorderCollapse::Collapse {
|
|
508
|
+
style.border = taffy::Rect {
|
|
509
|
+
left: style.gap.width,
|
|
510
|
+
right: style.gap.width,
|
|
511
|
+
top: style.gap.height,
|
|
512
|
+
bottom: style.gap.height,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
let layout_children = cells
|
|
517
|
+
.iter()
|
|
518
|
+
.map(|cell| cell.node_id)
|
|
519
|
+
.chain(oof_children.iter().map(|(node_id, _)| *node_id))
|
|
520
|
+
.collect();
|
|
521
|
+
let root_node = &mut doc.nodes[table_root_node_id];
|
|
522
|
+
root_node.children = dom_children;
|
|
523
|
+
|
|
524
|
+
(
|
|
525
|
+
TableContext {
|
|
526
|
+
style,
|
|
527
|
+
cells,
|
|
528
|
+
oof_children,
|
|
529
|
+
rows,
|
|
530
|
+
columns,
|
|
531
|
+
computed_grid_info: AtomicRefCell::new(None),
|
|
532
|
+
border_collapse,
|
|
533
|
+
border_style: first_cell_border,
|
|
534
|
+
calc_values,
|
|
535
|
+
},
|
|
536
|
+
layout_children,
|
|
537
|
+
)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/// Collect `<col>` elements (and `display: table-column` elements) along with their
|
|
541
|
+
/// widths, which take precedence over cell widths in the fixed table layout algorithm.
|
|
542
|
+
/// Columns without a specified width are recorded as `auto`.
|
|
543
|
+
fn collect_columns(
|
|
544
|
+
doc: &mut BaseDocument,
|
|
545
|
+
node_id: NodeId,
|
|
546
|
+
wm: WritingMode,
|
|
547
|
+
columns: &mut Vec<TableColumn>,
|
|
548
|
+
column_sizes: &mut Vec<TrackSizingFunction>,
|
|
549
|
+
) {
|
|
550
|
+
let node = &doc.nodes[node_id];
|
|
551
|
+
if !node.is_element() {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
let Some(display) = node.display_style() else {
|
|
555
|
+
return;
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
match display.inside() {
|
|
559
|
+
DisplayInside::TableColumnGroup => {
|
|
560
|
+
let first_column = columns.len();
|
|
561
|
+
let children = std::mem::take(&mut doc.nodes[node_id].children);
|
|
562
|
+
for child_id in children.iter().copied() {
|
|
563
|
+
collect_columns(doc, child_id, wm, columns, column_sizes);
|
|
564
|
+
}
|
|
565
|
+
doc.nodes[node_id].children = children;
|
|
566
|
+
for column in &mut columns[first_column..] {
|
|
567
|
+
column.group.get_or_insert(node_id);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
DisplayInside::TableColumn => {
|
|
571
|
+
let style = table_taffy_style(&node.primary_styles().unwrap(), wm);
|
|
572
|
+
let span: u16 = node
|
|
573
|
+
.attr(local_name!("span"))
|
|
574
|
+
.and_then(|val| val.parse::<u16>().ok())
|
|
575
|
+
.map(|v| v.max(1))
|
|
576
|
+
.unwrap_or(1);
|
|
577
|
+
let column: TrackSizingFunction = match style.size.width.tag() {
|
|
578
|
+
taffy::CompactLength::LENGTH_TAG => {
|
|
579
|
+
// A definite `max-width` clamps the column width; a zero width is treated as auto
|
|
580
|
+
let mut width = style.size.width.value();
|
|
581
|
+
if style.max_size.width.into_raw().tag() == taffy::CompactLength::LENGTH_TAG {
|
|
582
|
+
width = width.min(style.max_size.width.into_raw().value());
|
|
583
|
+
}
|
|
584
|
+
if width > 0.0 {
|
|
585
|
+
style_helpers::length(width)
|
|
586
|
+
} else {
|
|
587
|
+
style_helpers::auto()
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
taffy::CompactLength::PERCENT_TAG => {
|
|
591
|
+
style_helpers::percent(style.size.width.value())
|
|
592
|
+
}
|
|
593
|
+
// Browsers treat calc() widths on columns as auto
|
|
594
|
+
_ => style_helpers::auto(),
|
|
595
|
+
};
|
|
596
|
+
// A definite `min-width` on a column acts as a floor on its width
|
|
597
|
+
let column = match style.min_size.width.into_raw().tag() {
|
|
598
|
+
taffy::CompactLength::LENGTH_TAG if column.max.is_auto() => style_helpers::minmax(
|
|
599
|
+
style_helpers::length(style.min_size.width.into_raw().value()),
|
|
600
|
+
style_helpers::auto(),
|
|
601
|
+
),
|
|
602
|
+
_ => column,
|
|
603
|
+
};
|
|
604
|
+
for _ in 0..span {
|
|
605
|
+
columns.push(TableColumn {
|
|
606
|
+
node_id,
|
|
607
|
+
group: None,
|
|
608
|
+
});
|
|
609
|
+
column_sizes.push(column);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
_ => {}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
#[allow(clippy::too_many_arguments)]
|
|
617
|
+
fn collect_table_cells(
|
|
618
|
+
doc: &mut BaseDocument,
|
|
619
|
+
node_id: NodeId,
|
|
620
|
+
wm: WritingMode,
|
|
621
|
+
is_fixed: bool,
|
|
622
|
+
border_collapse: BorderCollapse,
|
|
623
|
+
row: &mut u16,
|
|
624
|
+
cursor: &mut ColumnCursor,
|
|
625
|
+
cells: &mut Vec<TableCell>,
|
|
626
|
+
oof_children: &mut Vec<(NodeId, taffy::Position)>,
|
|
627
|
+
rows: &mut Vec<TableRow>,
|
|
628
|
+
columns: &mut Vec<TrackSizingFunction>,
|
|
629
|
+
first_cell_border: &mut Option<ServoArc<Border>>,
|
|
630
|
+
percent_columns: &mut Vec<(u16, f32, f32)>,
|
|
631
|
+
anonymous: &mut ThinVec<NodeId>,
|
|
632
|
+
) {
|
|
633
|
+
let node = &mut doc.nodes[node_id];
|
|
634
|
+
|
|
635
|
+
if node.element_data().is_none() {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
let Some(display) = node.display_style() else {
|
|
640
|
+
#[cfg(feature = "tracing")]
|
|
641
|
+
tracing::info!("Ignoring table descendent because it has no styles");
|
|
642
|
+
return;
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
if display.outside() == DisplayOutside::None {
|
|
646
|
+
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Absolutely positioned boxes are out-of-flow: they are laid out by their containing
|
|
651
|
+
// block rather than as part of the table grid.
|
|
652
|
+
let position = node.primary_styles().unwrap().clone_position();
|
|
653
|
+
if position.is_absolutely_positioned() {
|
|
654
|
+
oof_children.push((node_id, stylo_taffy::convert::position(position)));
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
match display.inside() {
|
|
659
|
+
DisplayInside::TableRowGroup
|
|
660
|
+
| DisplayInside::TableHeaderGroup
|
|
661
|
+
| DisplayInside::TableFooterGroup
|
|
662
|
+
| DisplayInside::Contents => {
|
|
663
|
+
let first_row = rows.len();
|
|
664
|
+
let dom_children = doc.nodes[node_id].children.clone();
|
|
665
|
+
let children = table_children(doc, node_id, &dom_children, anonymous);
|
|
666
|
+
for child_id in children.iter().copied() {
|
|
667
|
+
doc.nodes[child_id]
|
|
668
|
+
.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
669
|
+
collect_table_cells(
|
|
670
|
+
doc,
|
|
671
|
+
child_id,
|
|
672
|
+
wm,
|
|
673
|
+
is_fixed,
|
|
674
|
+
border_collapse,
|
|
675
|
+
row,
|
|
676
|
+
cursor,
|
|
677
|
+
cells,
|
|
678
|
+
oof_children,
|
|
679
|
+
rows,
|
|
680
|
+
columns,
|
|
681
|
+
first_cell_border,
|
|
682
|
+
percent_columns,
|
|
683
|
+
anonymous,
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
if display.inside() != DisplayInside::Contents {
|
|
687
|
+
for row in &mut rows[first_row..] {
|
|
688
|
+
row.group.get_or_insert(node_id);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
DisplayInside::TableRow => {
|
|
693
|
+
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
694
|
+
*row += 1;
|
|
695
|
+
cursor.start_row();
|
|
696
|
+
|
|
697
|
+
let height = node.primary_styles().and_then(|style| {
|
|
698
|
+
let height = stylo_taffy::convert::dimension(&style.clone_height());
|
|
699
|
+
(height.tag() == taffy::CompactLength::LENGTH_TAG).then(|| height.value())
|
|
700
|
+
});
|
|
701
|
+
rows.push(TableRow {
|
|
702
|
+
node_id,
|
|
703
|
+
group: None,
|
|
704
|
+
height,
|
|
705
|
+
is_constrained: height.is_some(),
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
let dom_children = doc.nodes[node_id].children.clone();
|
|
709
|
+
let children = table_children(doc, node_id, &dom_children, anonymous);
|
|
710
|
+
for child_id in children.iter().copied() {
|
|
711
|
+
collect_table_cells(
|
|
712
|
+
doc,
|
|
713
|
+
child_id,
|
|
714
|
+
wm,
|
|
715
|
+
is_fixed,
|
|
716
|
+
border_collapse,
|
|
717
|
+
row,
|
|
718
|
+
cursor,
|
|
719
|
+
cells,
|
|
720
|
+
oof_children,
|
|
721
|
+
rows,
|
|
722
|
+
columns,
|
|
723
|
+
first_cell_border,
|
|
724
|
+
percent_columns,
|
|
725
|
+
anonymous,
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
DisplayInside::TableCell => {
|
|
730
|
+
// node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
731
|
+
let stylo_style = &node.primary_styles().unwrap();
|
|
732
|
+
let colspan: u16 = node
|
|
733
|
+
.attr(local_name!("colspan"))
|
|
734
|
+
.and_then(|val| val.parse().ok())
|
|
735
|
+
.unwrap_or(1);
|
|
736
|
+
let rowspan: u16 = node
|
|
737
|
+
.attr(local_name!("rowspan"))
|
|
738
|
+
.and_then(|val| val.parse::<u16>().ok())
|
|
739
|
+
.map(|v| v.clamp(1, 65534))
|
|
740
|
+
.unwrap_or(1);
|
|
741
|
+
let mut style = table_taffy_style(stylo_style, wm);
|
|
742
|
+
let col = cursor.next_free();
|
|
743
|
+
|
|
744
|
+
// In the collapsed borders model the borders are laid out as gutters between
|
|
745
|
+
// the cells (see the table's `gap`) rather than as part of the cells
|
|
746
|
+
if border_collapse == BorderCollapse::Collapse {
|
|
747
|
+
style.border = taffy::Rect::ZERO.map(style_helpers::length);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if first_cell_border.is_none() {
|
|
751
|
+
*first_cell_border = Some(stylo_style.clone_border());
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// In the fixed table layout algorithm the widths of columns are not
|
|
755
|
+
// affected by cell contents, so cells must not impose a min-content
|
|
756
|
+
// floor on their tracks.
|
|
757
|
+
if is_fixed {
|
|
758
|
+
style.min_size.width = style_helpers::length(0.0);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Column widths from `<col>` elements take precedence over cell widths
|
|
762
|
+
let col_needs_width =
|
|
763
|
+
(col as usize) >= columns.len() || columns[col as usize].max.is_auto();
|
|
764
|
+
if *row == 1 && col_needs_width {
|
|
765
|
+
let column: TrackSizingFunction = match style.size.width.tag() {
|
|
766
|
+
taffy::CompactLength::LENGTH_TAG => {
|
|
767
|
+
let len = style.size.width.value();
|
|
768
|
+
let padding = style.padding.resolve_or_zero(None, resolve_calc_value);
|
|
769
|
+
let border = style.border.resolve_or_zero(None, resolve_calc_value);
|
|
770
|
+
match style.box_sizing {
|
|
771
|
+
taffy::BoxSizing::ContentBox => style_helpers::length(
|
|
772
|
+
len + padding.left + padding.right + border.left + border.right,
|
|
773
|
+
),
|
|
774
|
+
taffy::BoxSizing::BorderBox => style_helpers::length(len),
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
taffy::CompactLength::PERCENT_TAG => {
|
|
778
|
+
if is_fixed {
|
|
779
|
+
let extra = match style.box_sizing {
|
|
780
|
+
taffy::BoxSizing::ContentBox => {
|
|
781
|
+
let padding =
|
|
782
|
+
style.padding.resolve_or_zero(None, resolve_calc_value);
|
|
783
|
+
let border =
|
|
784
|
+
style.border.resolve_or_zero(None, resolve_calc_value);
|
|
785
|
+
padding.left + padding.right + border.left + border.right
|
|
786
|
+
}
|
|
787
|
+
taffy::BoxSizing::BorderBox => 0.0,
|
|
788
|
+
};
|
|
789
|
+
// Resolved in `build_table_context` once the column count is known
|
|
790
|
+
percent_columns.push((col, style.size.width.value(), extra));
|
|
791
|
+
style_helpers::percent(style.size.width.value())
|
|
792
|
+
} else {
|
|
793
|
+
style_helpers::auto()
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
taffy::CompactLength::AUTO_TAG => style_helpers::auto(),
|
|
797
|
+
// Dimension values are always length, percentage, auto or calc(),
|
|
798
|
+
// so any other tag is a calc() value. Pass it through so that
|
|
799
|
+
// Taffy resolves it against the table's inner width.
|
|
800
|
+
_ => style.size.width.into(),
|
|
801
|
+
};
|
|
802
|
+
if (col as usize) < columns.len() {
|
|
803
|
+
if !column.max.is_auto() {
|
|
804
|
+
columns[col as usize] = column;
|
|
805
|
+
}
|
|
806
|
+
} else {
|
|
807
|
+
columns.resize(col as usize, style_helpers::auto());
|
|
808
|
+
columns.push(column);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// The margin properties do not apply to table-internal elements
|
|
813
|
+
style.margin = taffy::Rect::ZERO.map(style_helpers::length);
|
|
814
|
+
|
|
815
|
+
// Let Taffy auto-place the column. Combined with
|
|
816
|
+
// `grid_auto_flow: RowDense` set on the table root, each cell
|
|
817
|
+
// scans from the first track in its row for a free position,
|
|
818
|
+
// which makes cells automatically skip columns occupied by
|
|
819
|
+
// rowspan cells from earlier rows.
|
|
820
|
+
style.grid_column = taffy::Line {
|
|
821
|
+
start: style_helpers::auto(),
|
|
822
|
+
end: style_helpers::span(colspan),
|
|
823
|
+
};
|
|
824
|
+
style.grid_row = taffy::Line {
|
|
825
|
+
start: style_helpers::line(*row as i16),
|
|
826
|
+
end: style_helpers::span(rowspan),
|
|
827
|
+
};
|
|
828
|
+
if rowspan == 1 && style.size.height.tag() == taffy::CompactLength::LENGTH_TAG {
|
|
829
|
+
if let Some(row) = rows.last_mut() {
|
|
830
|
+
row.is_constrained = true;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
style.size.width = style_helpers::auto();
|
|
835
|
+
// A cell's max-width constrains its intrinsic contribution, not the
|
|
836
|
+
// final column width. The cell's own style still supplies the limit
|
|
837
|
+
// during intrinsic measurement; the grid must be allowed to stretch it.
|
|
838
|
+
style.max_size.width = style_helpers::auto();
|
|
839
|
+
cells.push(TableCell {
|
|
840
|
+
node_id,
|
|
841
|
+
row: row.saturating_sub(1),
|
|
842
|
+
column: col,
|
|
843
|
+
style,
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
cursor.place(colspan, rowspan);
|
|
847
|
+
}
|
|
848
|
+
DisplayInside::Flow
|
|
849
|
+
| DisplayInside::FlowRoot
|
|
850
|
+
| DisplayInside::Flex
|
|
851
|
+
| DisplayInside::Grid => {
|
|
852
|
+
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
853
|
+
// Probably a table caption: ignore
|
|
854
|
+
// println!(
|
|
855
|
+
// "Warning: ignoring non-table typed descendent of table ({:?})",
|
|
856
|
+
// display.inside()
|
|
857
|
+
// );
|
|
858
|
+
}
|
|
859
|
+
DisplayInside::TableColumnGroup | DisplayInside::TableColumn | DisplayInside::Table => {
|
|
860
|
+
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
861
|
+
//Ignore
|
|
862
|
+
}
|
|
863
|
+
DisplayInside::None => {
|
|
864
|
+
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
865
|
+
// Ignore
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
pub struct RangeIter(Range<usize>);
|
|
871
|
+
|
|
872
|
+
impl Iterator for RangeIter {
|
|
873
|
+
type Item = taffy::NodeId;
|
|
874
|
+
|
|
875
|
+
fn next(&mut self) -> Option<Self::Item> {
|
|
876
|
+
self.0.next().map(taffy::NodeId::from)
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
impl taffy::TraversePartialTree for TableTreeWrapper<'_, '_> {
|
|
881
|
+
type ChildIter<'a>
|
|
882
|
+
= RangeIter
|
|
883
|
+
where
|
|
884
|
+
Self: 'a;
|
|
885
|
+
|
|
886
|
+
#[inline(always)]
|
|
887
|
+
fn child_ids(&self, _node_id: taffy::NodeId) -> Self::ChildIter<'_> {
|
|
888
|
+
RangeIter(0..self.ctx.cells.len())
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
#[inline(always)]
|
|
892
|
+
fn child_count(&self, _node_id: taffy::NodeId) -> usize {
|
|
893
|
+
self.ctx.cells.len()
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
#[inline(always)]
|
|
897
|
+
fn get_child_id(&self, _node_id: taffy::NodeId, index: usize) -> taffy::NodeId {
|
|
898
|
+
index.into()
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
impl taffy::TraverseTree for TableTreeWrapper<'_, '_> {}
|
|
902
|
+
|
|
903
|
+
impl taffy::LayoutPartialTree for TableTreeWrapper<'_, '_> {
|
|
904
|
+
type CoreContainerStyle<'a>
|
|
905
|
+
= &'a taffy::Style<Atom>
|
|
906
|
+
where
|
|
907
|
+
Self: 'a;
|
|
908
|
+
|
|
909
|
+
type CustomIdent = Atom;
|
|
910
|
+
|
|
911
|
+
fn get_core_container_style(&self, _node_id: taffy::NodeId) -> &taffy::Style<Atom> {
|
|
912
|
+
&self.ctx.style
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
fn resolve_calc_value(&self, calc_ptr: *const (), parent_size: f32) -> f32 {
|
|
916
|
+
resolve_calc_value(calc_ptr, parent_size)
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
fn set_unrounded_layout(&mut self, node_id: taffy::NodeId, layout: &taffy::Layout) {
|
|
920
|
+
let node_id = crate::taffy_node_id(self.ctx.cells[usize::from(node_id)].node_id);
|
|
921
|
+
self.doc.set_unrounded_layout(node_id, layout)
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
fn compute_child_layout(
|
|
925
|
+
&mut self,
|
|
926
|
+
node_id: taffy::NodeId,
|
|
927
|
+
mut inputs: taffy::tree::LayoutInput,
|
|
928
|
+
) -> taffy::LayoutOutput {
|
|
929
|
+
let cell = &self.ctx.cells[usize::from(node_id)];
|
|
930
|
+
let node_id = crate::taffy_node_id(cell.node_id);
|
|
931
|
+
if inputs.known_dimensions.width.is_some() {
|
|
932
|
+
// Column/row sizing has already applied the cell's constraints.
|
|
933
|
+
// Reapplying max-width here would shrink its contents back to zero
|
|
934
|
+
// for the common td { max-width: 0 } equal-column pattern.
|
|
935
|
+
inputs.sizing_mode = taffy::SizingMode::ContentSize;
|
|
936
|
+
}
|
|
937
|
+
self.doc.compute_child_layout(node_id, inputs)
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
impl taffy::LayoutGridContainer for TableTreeWrapper<'_, '_> {
|
|
942
|
+
type GridContainerStyle<'a>
|
|
943
|
+
= &'a taffy::Style<Atom>
|
|
944
|
+
where
|
|
945
|
+
Self: 'a;
|
|
946
|
+
|
|
947
|
+
type GridItemStyle<'a>
|
|
948
|
+
= &'a taffy::Style<Atom>
|
|
949
|
+
where
|
|
950
|
+
Self: 'a;
|
|
951
|
+
|
|
952
|
+
fn get_grid_container_style(&self, node_id: taffy::NodeId) -> Self::GridContainerStyle<'_> {
|
|
953
|
+
self.get_core_container_style(node_id)
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
fn get_grid_child_style(&self, child_node_id: taffy::NodeId) -> Self::GridItemStyle<'_> {
|
|
957
|
+
&self.ctx.cells[usize::from(child_node_id)].style
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
fn set_detailed_grid_info(
|
|
961
|
+
&mut self,
|
|
962
|
+
_node_id: taffy::NodeId,
|
|
963
|
+
detailed_grid_info: DetailedGridInfo<Atom>,
|
|
964
|
+
) {
|
|
965
|
+
*self.ctx.computed_grid_info.borrow_mut() = Some(detailed_grid_info);
|
|
966
|
+
}
|
|
967
|
+
}
|