1use blitz_traits::node_id::NodeId;
2use std::{ops::Range, sync::Arc};
3
4use atomic_refcell::AtomicRefCell;
5use markup5ever::local_name;
6use style::properties::style_structs::Border;
7use style::servo_arc::Arc as ServoArc;
8use style::values::specified::box_::{DisplayInside, DisplayOutside};
9use style::{
10 Atom, computed_values::border_collapse::T as BorderCollapse,
11 computed_values::table_layout::T as TableLayout,
12};
13use taffy::{
14 DetailedGridInfo, LayoutPartialTree as _, ResolveOrZero, TrackSizingFunction, style_helpers,
15};
16
17use crate::BaseDocument;
18
19use super::damage::{CONSTRUCT_BOX, CONSTRUCT_DESCENDENT, CONSTRUCT_FC};
20use super::resolve_calc_value;
21
22pub struct TableTreeWrapper<'doc> {
23 pub(crate) doc: &'doc mut BaseDocument,
24 pub(crate) ctx: Arc<TableContext>,
25}
26
27#[derive(Debug, Clone)]
28pub struct TableContext {
29 pub style: taffy::Style<Atom>,
30 pub cells: Vec<TableCell>,
31 pub rows: Vec<TableRow>,
32 pub row_groups: Vec<TableRowGroup>,
33 pub computed_grid_info: AtomicRefCell<Option<DetailedGridInfo>>,
34 pub border_style: Option<ServoArc<Border>>,
35 pub border_collapse: BorderCollapse,
36}
37
38impl TableContext {
39 pub(crate) fn references_removed_nodes<S: std::hash::BuildHasher>(
40 &self,
41 removed: &std::collections::HashSet<NodeId, S>,
42 ) -> bool {
43 self.cells
44 .iter()
45 .any(|cell| removed.contains(&cell.node_id))
46 || self.rows.iter().any(|row| removed.contains(&row.node_id))
47 || self
48 .row_groups
49 .iter()
50 .any(|group| removed.contains(&group.node_id))
51 }
52}
53
54#[derive(Debug, Clone)]
61pub struct TableCell {
62 node_id: NodeId,
64 style: taffy::Style<Atom>,
65}
66
67#[derive(Debug, Clone)]
68pub struct TableRow {
69 pub node_id: NodeId,
71 pub height: f32,
72 pub cells: Range<usize>,
79}
80
81#[derive(Debug, Clone)]
83pub struct TableRowGroup {
84 pub node_id: NodeId,
85 pub rows: Range<usize>,
87}
88
89pub(crate) fn build_table_context(
90 doc: &mut BaseDocument,
91 table_root_node_id: NodeId,
92) -> (TableContext, Vec<NodeId>) {
93 let mut cells: Vec<TableCell> = Vec::new();
94 let mut rows: Vec<TableRow> = Vec::new();
95 let mut row_groups: Vec<TableRowGroup> = Vec::new();
96 let mut row = 0u16;
97 let mut col = 0u16;
98
99 let root_node = &mut doc.nodes[table_root_node_id];
100
101 let children = std::mem::take(&mut root_node.children);
102
103 let Some(stylo_styles) = root_node.primary_styles() else {
104 panic!("Ignoring table because it has no styles");
105 };
106
107 let mut style = stylo_taffy::to_taffy_style(&stylo_styles);
108 style.item_is_table = true;
109 style.grid_auto_flow = taffy::GridAutoFlow::RowDense;
114 style.grid_auto_columns = Vec::new();
115 style.grid_auto_rows = Vec::new();
116
117 let is_fixed = match stylo_styles.clone_table_layout() {
118 TableLayout::Fixed => true,
119 TableLayout::Auto => false,
120 };
121
122 let border_collapse = stylo_styles.clone_border_collapse();
123 let border_spacing = stylo_styles.clone_border_spacing().0;
124
125 drop(stylo_styles);
126
127 let mut column_sizes: Vec<taffy::TrackSizingFunction> = Vec::new();
128 let mut first_cell_border: Option<ServoArc<Border>> = None;
129 for child_id in children.iter().copied() {
130 collect_table_cells(
131 doc,
132 child_id,
133 is_fixed,
134 border_collapse,
135 &mut row,
136 &mut col,
137 &mut cells,
138 &mut rows,
139 &mut row_groups,
140 &mut column_sizes,
141 &mut first_cell_border,
142 );
143 }
144 column_sizes.resize(col as usize, style_helpers::auto());
145
146 style.grid_template_columns = column_sizes.into_iter().map(|dim| dim.into()).collect();
147 style.grid_template_rows = vec![style_helpers::auto(); row as usize];
148
149 style.gap = match border_collapse {
150 BorderCollapse::Separate => {
151 let spacing_x = border_spacing.width.px();
154 let spacing_y = border_spacing.height.px();
155 let padding = style.padding.resolve_or_zero(None, resolve_calc_value);
156 style.padding = taffy::Rect {
157 left: style_helpers::length(padding.left + spacing_x),
158 right: style_helpers::length(padding.right + spacing_x),
159 top: style_helpers::length(padding.top + spacing_y),
160 bottom: style_helpers::length(padding.bottom + spacing_y),
161 };
162 taffy::Size {
163 width: style_helpers::length(spacing_x),
164 height: style_helpers::length(spacing_y),
165 }
166 }
167 BorderCollapse::Collapse => first_cell_border
168 .as_ref()
169 .map(|border| {
170 let x = border
171 .border_left_width
172 .0
173 .max(border.border_right_width.0)
174 .to_f32_px();
175 let y = border
176 .border_top_width
177 .0
178 .max(border.border_bottom_width.0)
179 .to_f32_px();
180 taffy::Size {
181 width: style_helpers::length(x),
182 height: style_helpers::length(y),
183 }
184 })
185 .unwrap_or(taffy::Size::ZERO.map(style_helpers::length)),
186 };
187
188 if border_collapse == BorderCollapse::Collapse {
189 style.border = taffy::Rect {
190 left: style.gap.width,
191 right: style.gap.width,
192 top: style.gap.height,
193 bottom: style.gap.height,
194 };
195 }
196
197 let layout_children = cells.iter().map(|cell| cell.node_id).collect();
198 let root_node = &mut doc.nodes[table_root_node_id];
199 root_node.children = children;
200
201 (
202 TableContext {
203 style,
204 cells,
205 rows,
206 row_groups,
207 computed_grid_info: AtomicRefCell::new(None),
208 border_collapse,
209 border_style: first_cell_border,
210 },
211 layout_children,
212 )
213}
214
215#[allow(clippy::too_many_arguments)]
216pub(crate) fn collect_table_cells(
217 doc: &mut BaseDocument,
218 node_id: NodeId,
219 is_fixed: bool,
220 border_collapse: BorderCollapse,
221 row: &mut u16,
222 col: &mut u16,
223 cells: &mut Vec<TableCell>,
224 rows: &mut Vec<TableRow>,
225 row_groups: &mut Vec<TableRowGroup>,
226 columns: &mut Vec<TrackSizingFunction>,
227 first_cell_border: &mut Option<ServoArc<Border>>,
228) {
229 let node = &mut doc.nodes[node_id];
230
231 if !node.is_element() {
232 return;
233 }
234
235 let Some(display) = node.primary_styles().map(|s| s.clone_display()) else {
236 #[cfg(feature = "tracing")]
237 tracing::info!("Ignoring table descendent because it has no styles");
238 return;
239 };
240
241 if display.outside() == DisplayOutside::None {
242 node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
243 return;
244 }
245
246 match display.inside() {
247 DisplayInside::TableRowGroup
248 | DisplayInside::TableHeaderGroup
249 | DisplayInside::TableFooterGroup
250 | DisplayInside::Contents => {
251 let is_row_group = !matches!(display.inside(), DisplayInside::Contents);
252 let first_row = rows.len();
253 let children = std::mem::take(&mut doc.nodes[node_id].children);
254 for child_id in children.iter().copied() {
255 doc.nodes[child_id]
256 .remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
257 collect_table_cells(
258 doc,
259 child_id,
260 is_fixed,
261 border_collapse,
262 row,
263 col,
264 cells,
265 rows,
266 row_groups,
267 columns,
268 first_cell_border,
269 );
270 }
271 doc.nodes[node_id].children = children;
272 if is_row_group {
273 row_groups.push(TableRowGroup {
274 node_id,
275 rows: first_row..rows.len(),
276 });
277 }
278 }
279 DisplayInside::TableRow => {
280 node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
281 *row += 1;
282 *col = 0;
283
284 let row_index = rows.len();
285 let first_cell = cells.len();
286 rows.push(TableRow {
287 node_id,
288 height: 0.0,
289 cells: first_cell..first_cell,
290 });
291
292 let children = std::mem::take(&mut doc.nodes[node_id].children);
293 for child_id in children.iter().copied() {
294 collect_table_cells(
295 doc,
296 child_id,
297 is_fixed,
298 border_collapse,
299 row,
300 col,
301 cells,
302 rows,
303 row_groups,
304 columns,
305 first_cell_border,
306 );
307 }
308 doc.nodes[node_id].children = children;
309 rows[row_index].cells = first_cell..cells.len();
310 }
311 DisplayInside::TableCell => {
312 let stylo_style = &node.primary_styles().unwrap();
314 let colspan: u16 = node
315 .attr(local_name!("colspan"))
316 .and_then(|val| val.parse().ok())
317 .unwrap_or(1);
318 let rowspan: u16 = node
319 .attr(local_name!("rowspan"))
320 .and_then(|val| val.parse::<u16>().ok())
321 .map(|v| v.clamp(1, 65534))
322 .unwrap_or(1);
323 let mut style = stylo_taffy::to_taffy_style(stylo_style);
324
325 if first_cell_border.is_none() {
326 *first_cell_border = Some(stylo_style.clone_border());
327 }
328
329 if *row == 1 {
330 let column = match style.size.width.tag() {
331 taffy::CompactLength::LENGTH_TAG => {
332 let len = style.size.width.value();
333 let padding = style.padding.resolve_or_zero(None, resolve_calc_value);
334 let border = style.border.resolve_or_zero(None, resolve_calc_value);
335 match style.box_sizing {
336 taffy::BoxSizing::ContentBox => style_helpers::length(
337 len + padding.left + padding.right + border.left + border.right,
338 ),
339 taffy::BoxSizing::BorderBox => style_helpers::length(len),
340 }
341 }
342 taffy::CompactLength::PERCENT_TAG => {
343 if is_fixed {
344 style_helpers::percent(style.size.width.value())
345 } else {
346 style_helpers::auto()
347 }
348 }
349 taffy::CompactLength::AUTO_TAG => style_helpers::auto(),
350 _ => style.size.width.into(),
354 };
355 columns.push(column);
356 }
357
358 if border_collapse == BorderCollapse::Collapse {
361 style.border = taffy::Rect::ZERO.map(style_helpers::length);
362 }
363
364 style.margin = taffy::Rect::ZERO.map(style_helpers::length);
366
367 style.grid_column = taffy::Line {
373 start: style_helpers::auto(),
374 end: style_helpers::span(colspan),
375 };
376 style.grid_row = taffy::Line {
377 start: style_helpers::line(*row as i16),
378 end: style_helpers::span(rowspan),
379 };
380 style.size.width = style_helpers::auto();
381 cells.push(TableCell { node_id, style });
382
383 *col += colspan;
384 }
385 DisplayInside::Flow
386 | DisplayInside::FlowRoot
387 | DisplayInside::Flex
388 | DisplayInside::Grid => {
389 node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
390 }
396 DisplayInside::TableColumnGroup | DisplayInside::TableColumn | DisplayInside::Table => {
397 node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
398 }
400 DisplayInside::None => {
401 node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
402 }
404 }
405}
406
407impl BaseDocument {
420 pub(crate) fn assign_table_row_layouts(&mut self) {
421 let contexts: Vec<Arc<TableContext>> = self
422 .nodes
423 .iter()
424 .filter(|(_, node)| node.flags.is_table_root())
425 .filter_map(
426 |(_, node)| match &node.data.downcast_element()?.special_data {
427 crate::node::SpecialElementData::TableRoot(context) => {
428 Some(Arc::clone(context))
429 }
430 _ => None,
431 },
432 )
433 .collect();
434 for context in contexts {
435 assign_row_layouts(self, &context);
436 }
437 }
438}
439
440pub(crate) fn assign_row_layouts(doc: &mut BaseDocument, ctx: &TableContext) {
441 if ctx.rows.is_empty() {
442 return;
443 }
444
445 let mut left = f32::MAX;
449 let mut right = f32::MIN;
450 for cell in &ctx.cells {
451 let Some(node) = doc.nodes.get(cell.node_id) else {
452 continue;
453 };
454 let layout = node.final_layout();
455 left = left.min(layout.location.x);
456 right = right.max(layout.location.x + layout.size.width);
457 }
458 if left > right {
459 return;
460 }
461
462 let mut row_extents: Vec<Option<(f32, f32)>> = Vec::with_capacity(ctx.rows.len());
463 for row in &ctx.rows {
464 let mut top = f32::MAX;
465 let mut bottom = f32::MIN;
466 for cell in &ctx.cells[row.cells.clone()] {
467 let Some(node) = doc.nodes.get(cell.node_id) else {
468 continue;
469 };
470 let layout = node.final_layout();
471 top = top.min(layout.location.y);
472 bottom = bottom.max(layout.location.y + layout.size.height);
473 }
474 if top > bottom {
475 row_extents.push(None);
476 continue;
477 }
478 row_extents.push(Some((top, bottom)));
479 write_box(doc, row.node_id, left, top, right - left, bottom - top);
480 }
481
482 for group in &ctx.row_groups {
483 let mut top = f32::MAX;
484 let mut bottom = f32::MIN;
485 for extent in row_extents[group.rows.clone()].iter().flatten() {
486 top = top.min(extent.0);
487 bottom = bottom.max(extent.1);
488 }
489 if top > bottom {
490 continue;
491 }
492 write_box(doc, group.node_id, left, top, right - left, bottom - top);
493 }
494}
495
496fn write_box(doc: &mut BaseDocument, node_id: NodeId, x: f32, y: f32, width: f32, height: f32) {
497 let Some(node) = doc.nodes.get_mut(node_id) else {
498 return;
499 };
500 let mut layout = taffy::Layout::with_order(node.final_layout().order);
501 layout.location = taffy::Point { x, y };
502 layout.size = taffy::Size { width, height };
503 layout.content_size = layout.size;
504 *node.unrounded_layout_mut() = layout;
505 *node.final_layout_mut() = layout;
506}
507
508pub struct RangeIter(Range<usize>);
509
510impl Iterator for RangeIter {
511 type Item = taffy::NodeId;
512
513 fn next(&mut self) -> Option<Self::Item> {
514 self.0.next().map(taffy::NodeId::from)
515 }
516}
517
518impl taffy::TraversePartialTree for TableTreeWrapper<'_> {
519 type ChildIter<'a>
520 = RangeIter
521 where
522 Self: 'a;
523
524 #[inline(always)]
525 fn child_ids(&self, _node_id: taffy::NodeId) -> Self::ChildIter<'_> {
526 RangeIter(0..self.ctx.cells.len())
527 }
528
529 #[inline(always)]
530 fn child_count(&self, _node_id: taffy::NodeId) -> usize {
531 self.ctx.cells.len()
532 }
533
534 #[inline(always)]
535 fn get_child_id(&self, _node_id: taffy::NodeId, index: usize) -> taffy::NodeId {
536 index.into()
537 }
538}
539impl taffy::TraverseTree for TableTreeWrapper<'_> {}
540
541impl taffy::LayoutPartialTree for TableTreeWrapper<'_> {
542 type CoreContainerStyle<'a>
543 = &'a taffy::Style<Atom>
544 where
545 Self: 'a;
546
547 type CustomIdent = Atom;
548
549 fn get_core_container_style(&self, _node_id: taffy::NodeId) -> &taffy::Style<Atom> {
550 &self.ctx.style
551 }
552
553 fn resolve_calc_value(&self, calc_ptr: *const (), parent_size: f32) -> f32 {
554 resolve_calc_value(calc_ptr, parent_size)
555 }
556
557 fn set_unrounded_layout(&mut self, node_id: taffy::NodeId, layout: &taffy::Layout) {
558 let node_id = crate::taffy_node_id(self.ctx.cells[usize::from(node_id)].node_id);
559 self.doc.set_unrounded_layout(node_id, layout)
560 }
561
562 fn compute_child_layout(
563 &mut self,
564 node_id: taffy::NodeId,
565 inputs: taffy::tree::LayoutInput,
566 ) -> taffy::LayoutOutput {
567 let cell = &self.ctx.cells[usize::from(node_id)];
568 let node_id = crate::taffy_node_id(cell.node_id);
569 self.doc.compute_child_layout(node_id, inputs)
570 }
571}
572
573impl taffy::LayoutGridContainer for TableTreeWrapper<'_> {
574 type GridContainerStyle<'a>
575 = &'a taffy::Style<Atom>
576 where
577 Self: 'a;
578
579 type GridItemStyle<'a>
580 = &'a taffy::Style<Atom>
581 where
582 Self: 'a;
583
584 fn get_grid_container_style(&self, node_id: taffy::NodeId) -> Self::GridContainerStyle<'_> {
585 self.get_core_container_style(node_id)
586 }
587
588 fn get_grid_child_style(&self, child_node_id: taffy::NodeId) -> Self::GridItemStyle<'_> {
589 &self.ctx.cells[usize::from(child_node_id)].style
590 }
591
592 fn set_detailed_grid_info(
593 &mut self,
594 _node_id: taffy::NodeId,
595 detailed_grid_info: DetailedGridInfo,
596 ) {
597 *self.ctx.computed_grid_info.borrow_mut() = Some(detailed_grid_info);
598 }
599}