use std::{ops::Range, sync::Arc};
use markup5ever::local_name;
use style::values::specified::box_::{DisplayInside, DisplayOutside};
use style::{Atom, computed_values::table_layout::T as TableLayout};
use taffy::{
Dimension, LayoutPartialTree as _, ResolveOrZero, TrackSizingFunction, compute_leaf_layout,
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 {
style: taffy::Style<Atom>,
items: Vec<TableItem>,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum TableItemKind {
Row,
Cell,
}
#[derive(Debug, Clone)]
pub struct TableItem {
kind: TableItemKind,
node_id: usize,
style: taffy::Style<Atom>,
}
pub(crate) fn build_table_context(
doc: &mut BaseDocument,
table_root_node_id: usize,
) -> (TableContext, Vec<usize>) {
let mut items: Vec<TableItem> = 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.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,
};
drop(stylo_styles);
let mut column_sizes: Vec<taffy::Dimension> = Vec::new();
for child_id in children.iter().copied() {
collect_table_cells(
doc,
child_id,
is_fixed,
&mut row,
&mut col,
&mut items,
&mut column_sizes,
);
}
column_sizes.resize(col as usize, style_helpers::auto());
style.grid_template_columns = column_sizes
.into_iter()
.map(|dim| TrackSizingFunction::from(dim).into())
.collect();
style.grid_template_rows = vec![style_helpers::auto(); row as usize];
let layout_children = items
.iter()
.filter(|item| item.kind == TableItemKind::Cell)
.map(|cell| cell.node_id)
.collect();
let root_node = &mut doc.nodes[table_root_node_id];
root_node.children = children;
(TableContext { style, items }, layout_children)
}
pub(crate) fn collect_table_cells(
doc: &mut BaseDocument,
node_id: usize,
is_fixed: bool,
row: &mut u16,
col: &mut u16,
cells: &mut Vec<TableItem>,
columns: &mut Vec<Dimension>,
) {
let node = &doc.nodes[node_id];
if !node.is_element() {
return;
}
let Some(display) = node.primary_styles().map(|s| s.clone_display()) else {
println!("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, row, col, cells, columns);
}
doc.nodes[node_id].children = children;
}
DisplayInside::TableRow => {
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
*row += 1;
*col = 0;
{
let stylo_style = &node.primary_styles().unwrap();
let mut style = stylo_taffy::to_taffy_style(stylo_style);
style.grid_column = taffy::Line {
start: style_helpers::line(0),
end: style_helpers::line(-1),
};
style.grid_row = taffy::Line {
start: style_helpers::line(*row as i16),
end: style_helpers::span(1),
};
cells.push(TableItem {
kind: TableItemKind::Row,
node_id,
style,
});
}
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, row, col, cells, columns);
}
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 mut style = stylo_taffy::to_taffy_style(stylo_style);
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 => {
style_helpers::percent(style.size.width.value())
}
taffy::CompactLength::AUTO_TAG => style_helpers::auto(),
_ => unreachable!(),
};
columns.push(column);
} else if !is_fixed
&& (*col as usize) < columns.len()
&& taffy::CompactLength::LENGTH_TAG == style.size.width.tag()
{
let new_len = style.size.width.value();
let tag = columns[*col as usize].tag();
let value = columns[*col as usize].value();
columns[*col as usize] = match tag {
taffy::CompactLength::LENGTH_TAG => style_helpers::length(value.max(new_len)),
taffy::CompactLength::AUTO_TAG => style_helpers::length(new_len),
taffy::CompactLength::PERCENT_TAG => style_helpers::percent(value),
_ => unreachable!(),
}
}
style.grid_column = taffy::Line {
start: style_helpers::line((*col + 1) as i16),
end: style_helpers::span(colspan),
};
style.grid_row = taffy::Line {
start: style_helpers::line(*row as i16),
end: style_helpers::span(1),
};
style.size.width = style_helpers::auto();
cells.push(TableItem {
kind: TableItemKind::Cell,
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.items.len())
}
#[inline(always)]
fn child_count(&self, node_id: taffy::NodeId) -> usize {
self.doc.child_count(node_id)
}
#[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.items[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.items[usize::from(node_id)];
match cell.kind {
TableItemKind::Row => {
compute_leaf_layout(inputs, &cell.style, resolve_calc_value, |_, _| {
taffy::Size::ZERO
})
}
TableItemKind::Cell => {
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.items[usize::from(child_node_id)].style
}
}