use std::{ops::Range, sync::Arc};
use atomic_refcell::AtomicRefCell;
use markup5ever::local_name;
use style::properties::style_structs::Border;
use style::servo_arc::Arc as ServoArc;
use style::values::specified::box_::{DisplayInside, DisplayOutside};
use style::{
Atom, computed_values::border_collapse::T as BorderCollapse,
computed_values::table_layout::T as TableLayout,
};
use taffy::{
DetailedGridInfo, LayoutPartialTree as _, ResolveOrZero, TrackSizingFunction, style_helpers,
};
use crate::BaseDocument;
use super::damage::{CONSTRUCT_BOX, CONSTRUCT_DESCENDENT, CONSTRUCT_FC};
use super::resolve_calc_value;
pub struct TableTreeWrapper<'doc> {
pub(crate) doc: &'doc mut BaseDocument,
pub(crate) ctx: Arc<TableContext>,
}
#[derive(Debug, Clone)]
pub struct TableContext {
pub style: taffy::Style<Atom>,
pub cells: Vec<TableCell>,
pub rows: Vec<TableRow>,
pub computed_grid_info: AtomicRefCell<Option<DetailedGridInfo>>,
pub border_style: Option<ServoArc<Border>>,
pub border_collapse: BorderCollapse,
}
#[derive(Debug, Clone)]
pub struct TableCell {
node_id: usize,
style: taffy::Style<Atom>,
}
#[derive(Debug, Clone)]
pub struct TableRow {
pub node_id: usize,
pub height: f32,
}
pub(crate) fn build_table_context(
doc: &mut BaseDocument,
table_root_node_id: usize,
) -> (TableContext, Vec<usize>) {
let mut cells: Vec<TableCell> = Vec::new();
let mut rows: Vec<TableRow> = Vec::new();
let mut row = 0u16;
let mut col = 0u16;
let root_node = &mut doc.nodes[table_root_node_id];
let children = std::mem::take(&mut root_node.children);
let Some(stylo_styles) = root_node.primary_styles() else {
panic!("Ignoring table because it has no styles");
};
let mut style = stylo_taffy::to_taffy_style(&stylo_styles);
style.item_is_table = true;
style.grid_auto_flow = taffy::GridAutoFlow::RowDense;
style.grid_auto_columns = Vec::new();
style.grid_auto_rows = Vec::new();
let is_fixed = match stylo_styles.clone_table_layout() {
TableLayout::Fixed => true,
TableLayout::Auto => false,
};
let border_collapse = stylo_styles.clone_border_collapse();
let border_spacing = stylo_styles.clone_border_spacing().0;
drop(stylo_styles);
let mut column_sizes: Vec<taffy::TrackSizingFunction> = Vec::new();
let mut first_cell_border: Option<ServoArc<Border>> = None;
for child_id in children.iter().copied() {
collect_table_cells(
doc,
child_id,
is_fixed,
border_collapse,
&mut row,
&mut col,
&mut cells,
&mut rows,
&mut column_sizes,
&mut first_cell_border,
);
}
column_sizes.resize(col as usize, style_helpers::auto());
style.grid_template_columns = column_sizes.into_iter().map(|dim| dim.into()).collect();
style.grid_template_rows = vec![style_helpers::auto(); row as usize];
style.gap = match border_collapse {
BorderCollapse::Separate => taffy::Size {
width: style_helpers::length(border_spacing.width.px()),
height: style_helpers::length(border_spacing.height.px()),
},
BorderCollapse::Collapse => first_cell_border
.as_ref()
.map(|border| {
let x = border
.border_left_width
.0
.max(border.border_right_width.0)
.to_f32_px();
let y = border
.border_top_width
.0
.max(border.border_bottom_width.0)
.to_f32_px();
taffy::Size {
width: style_helpers::length(x),
height: style_helpers::length(y),
}
})
.unwrap_or(taffy::Size::ZERO.map(style_helpers::length)),
};
if border_collapse == BorderCollapse::Collapse {
style.border = taffy::Rect {
left: style.gap.width,
right: style.gap.width,
top: style.gap.height,
bottom: style.gap.height,
};
}
let layout_children = cells.iter().map(|cell| cell.node_id).collect();
let root_node = &mut doc.nodes[table_root_node_id];
root_node.children = children;
(
TableContext {
style,
cells,
rows,
computed_grid_info: AtomicRefCell::new(None),
border_collapse,
border_style: first_cell_border,
},
layout_children,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn collect_table_cells(
doc: &mut BaseDocument,
node_id: usize,
is_fixed: bool,
border_collapse: BorderCollapse,
row: &mut u16,
col: &mut u16,
cells: &mut Vec<TableCell>,
rows: &mut Vec<TableRow>,
columns: &mut Vec<TrackSizingFunction>,
first_cell_border: &mut Option<ServoArc<Border>>,
) {
let node = &mut doc.nodes[node_id];
if !node.is_element() {
return;
}
let Some(display) = node.primary_styles().map(|s| s.clone_display()) else {
#[cfg(feature = "tracing")]
tracing::info!("Ignoring table descendent because it has no styles");
return;
};
if display.outside() == DisplayOutside::None {
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
return;
}
match display.inside() {
DisplayInside::TableRowGroup
| DisplayInside::TableHeaderGroup
| DisplayInside::TableFooterGroup
| DisplayInside::Contents => {
let children = std::mem::take(&mut doc.nodes[node_id].children);
for child_id in children.iter().copied() {
doc.nodes[child_id]
.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
collect_table_cells(
doc,
child_id,
is_fixed,
border_collapse,
row,
col,
cells,
rows,
columns,
first_cell_border,
);
}
doc.nodes[node_id].children = children;
}
DisplayInside::TableRow => {
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
*row += 1;
*col = 0;
rows.push(TableRow {
node_id,
height: 0.0,
});
let children = std::mem::take(&mut doc.nodes[node_id].children);
for child_id in children.iter().copied() {
collect_table_cells(
doc,
child_id,
is_fixed,
border_collapse,
row,
col,
cells,
rows,
columns,
first_cell_border,
);
}
doc.nodes[node_id].children = children;
}
DisplayInside::TableCell => {
let stylo_style = &node.primary_styles().unwrap();
let colspan: u16 = node
.attr(local_name!("colspan"))
.and_then(|val| val.parse().ok())
.unwrap_or(1);
let rowspan: u16 = node
.attr(local_name!("rowspan"))
.and_then(|val| val.parse::<u16>().ok())
.map(|v| v.clamp(1, 65534))
.unwrap_or(1);
let mut style = stylo_taffy::to_taffy_style(stylo_style);
if first_cell_border.is_none() {
*first_cell_border = Some(stylo_style.clone_border());
}
if *row == 1 {
let column = match style.size.width.tag() {
taffy::CompactLength::LENGTH_TAG => {
let len = style.size.width.value();
let padding = style.padding.resolve_or_zero(None, resolve_calc_value);
style_helpers::length(len + padding.left + padding.right)
}
taffy::CompactLength::PERCENT_TAG => {
if is_fixed {
style_helpers::percent(style.size.width.value())
} else {
style_helpers::auto()
}
}
taffy::CompactLength::AUTO_TAG => style_helpers::auto(),
_ => unreachable!(),
};
columns.push(column);
}
if border_collapse == BorderCollapse::Collapse {
style.border = taffy::Rect::ZERO.map(style_helpers::length);
}
style.grid_column = taffy::Line {
start: style_helpers::auto(),
end: style_helpers::span(colspan),
};
style.grid_row = taffy::Line {
start: style_helpers::line(*row as i16),
end: style_helpers::span(rowspan),
};
style.size.width = style_helpers::auto();
cells.push(TableCell { node_id, style });
*col += colspan;
}
DisplayInside::Flow
| DisplayInside::FlowRoot
| DisplayInside::Flex
| DisplayInside::Grid => {
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
}
DisplayInside::TableColumnGroup | DisplayInside::TableColumn | DisplayInside::Table => {
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
}
DisplayInside::None => {
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
}
}
}
pub struct RangeIter(Range<usize>);
impl Iterator for RangeIter {
type Item = taffy::NodeId;
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(taffy::NodeId::from)
}
}
impl taffy::TraversePartialTree for TableTreeWrapper<'_> {
type ChildIter<'a>
= RangeIter
where
Self: 'a;
#[inline(always)]
fn child_ids(&self, _node_id: taffy::NodeId) -> Self::ChildIter<'_> {
RangeIter(0..self.ctx.cells.len())
}
#[inline(always)]
fn child_count(&self, _node_id: taffy::NodeId) -> usize {
self.ctx.cells.len()
}
#[inline(always)]
fn get_child_id(&self, _node_id: taffy::NodeId, index: usize) -> taffy::NodeId {
index.into()
}
}
impl taffy::TraverseTree for TableTreeWrapper<'_> {}
impl taffy::LayoutPartialTree for TableTreeWrapper<'_> {
type CoreContainerStyle<'a>
= &'a taffy::Style<Atom>
where
Self: 'a;
type CustomIdent = Atom;
fn get_core_container_style(&self, _node_id: taffy::NodeId) -> &taffy::Style<Atom> {
&self.ctx.style
}
fn resolve_calc_value(&self, calc_ptr: *const (), parent_size: f32) -> f32 {
resolve_calc_value(calc_ptr, parent_size)
}
fn set_unrounded_layout(&mut self, node_id: taffy::NodeId, layout: &taffy::Layout) {
let node_id = taffy::NodeId::from(self.ctx.cells[usize::from(node_id)].node_id);
self.doc.set_unrounded_layout(node_id, layout)
}
fn compute_child_layout(
&mut self,
node_id: taffy::NodeId,
inputs: taffy::tree::LayoutInput,
) -> taffy::LayoutOutput {
let cell = &self.ctx.cells[usize::from(node_id)];
let node_id = taffy::NodeId::from(cell.node_id);
self.doc.compute_child_layout(node_id, inputs)
}
}
impl taffy::LayoutGridContainer for TableTreeWrapper<'_> {
type GridContainerStyle<'a>
= &'a taffy::Style<Atom>
where
Self: 'a;
type GridItemStyle<'a>
= &'a taffy::Style<Atom>
where
Self: 'a;
fn get_grid_container_style(&self, node_id: taffy::NodeId) -> Self::GridContainerStyle<'_> {
self.get_core_container_style(node_id)
}
fn get_grid_child_style(&self, child_node_id: taffy::NodeId) -> Self::GridItemStyle<'_> {
&self.ctx.cells[usize::from(child_node_id)].style
}
fn set_detailed_grid_info(
&mut self,
_node_id: taffy::NodeId,
detailed_grid_info: DetailedGridInfo,
) {
*self.ctx.computed_grid_info.borrow_mut() = Some(detailed_grid_info);
}
}