use crate::{
graphics::{FontStyle, block_style},
html::box_edges::BoxEdges,
layout::{
BoxConstraint, Cursor, InlineRun, LayoutBox, LayoutContext, PaintStyle, Rect,
inner_formatting_context::flow::layout_children,
layoutbox::{collect_runs, wrap_runs},
},
tdom::node::{NodeArena, NodeId, NodeType, effective_font_size_cells},
};
#[derive(Clone, Copy)]
pub struct CellInfo {
pub node_id: NodeId,
pub is_header: bool,
}
pub fn measure_cell(
arena: &NodeArena,
cell_id: NodeId,
col_width: usize,
is_header: bool,
context: LayoutContext,
) -> (usize, Vec<Vec<InlineRun>>) {
let node = &arena[cell_id];
let own_font_size_cells =
effective_font_size_cells(arena, cell_id, context.font, context.viewport);
let child_context = context.child(own_font_size_cells);
let cell_edges: BoxEdges =
node.css_attr
.get_box_edges(col_width, child_context.font, child_context.viewport);
let content_width = col_width
.saturating_sub(cell_edges.inner_horizontal())
.max(1);
let block_fg = node.css_attr.get_color();
let mut block_font_style = block_style(&node.css_attr);
if is_header {
for &(prop, value) in node.node_type.inline_ua_declarations() {
if prop == "font-weight" && !node.css_attr.is_set(prop) && value == "bold" {
block_font_style |= FontStyle::BOLD;
}
}
}
let spacing = (
node.css_attr
.get_letter_spacing(child_context.font, child_context.viewport),
node.css_attr
.get_word_spacing(child_context.font, child_context.viewport),
);
let body_runs = collect_runs(
arena,
cell_id,
node,
block_fg,
None,
block_font_style,
spacing,
);
let wrapped_lines = if body_runs.is_empty() {
Vec::new()
} else {
wrap_runs(
&body_runs,
content_width,
own_font_size_cells,
node.css_attr.get_white_space(&node.node_type),
node.css_attr.get_word_break(),
node.css_attr.get_overflow_wrap(),
0,
)
};
let inline_rows = wrapped_lines
.len()
.max(1)
.saturating_mul(own_font_size_cells.max(1));
let block_extent = {
let mut scratch = Cursor {
y: 0,
prev_bottom_margin: 0,
};
let children = layout_children(
arena,
&node.children,
BoxConstraint {
x: 0,
width: content_width,
},
&mut scratch,
child_context,
);
children
.iter()
.filter(|b| {
!matches!(
b.positioning.position,
crate::html::css_parse::Position::Absolute
| crate::html::css_parse::Position::Fixed
)
})
.map(|b| b.rect.y.saturating_add(b.rect.height))
.max()
.unwrap_or(0)
};
let height = inline_rows
.max(block_extent)
.saturating_add(cell_edges.border.top.thickness())
.saturating_add(cell_edges.padding.top)
.saturating_add(cell_edges.border.bottom.thickness())
.saturating_add(cell_edges.padding.bottom)
.max(1);
(height, wrapped_lines)
}
pub fn layout_cell(
arena: &NodeArena,
cell_id: NodeId,
rect: Rect,
is_header: bool,
context: LayoutContext,
wrapped_lines: Vec<Vec<InlineRun>>,
hide_if_empty: bool,
) -> LayoutBox {
let node = &arena[cell_id];
let own_font_size_cells =
effective_font_size_cells(arena, cell_id, context.font, context.viewport);
let child_context = context.child(own_font_size_cells);
let cell_is_empty = wrapped_lines.iter().all(|l| l.is_empty())
&& node.children.iter().all(|&c| {
matches!(arena.get(c).map(|n| n.node_type), Some(NodeType::Text))
&& arena
.get(c)
.and_then(|n| n.data.as_deref())
.is_none_or(str::is_empty)
});
let suppress_chrome = hide_if_empty && cell_is_empty;
let cell_edges: BoxEdges = if suppress_chrome {
BoxEdges::default()
} else {
node.css_attr
.get_box_edges(rect.width, child_context.font, child_context.viewport)
};
let block_bg = if suppress_chrome {
None
} else {
node.css_attr.get_bg_color()
};
let mut text_align = node.css_attr.get_text_align();
if is_header {
for &(prop, value) in node.node_type.inline_ua_declarations() {
if prop == "text-align" && !node.css_attr.is_set(prop) && value == "center" {
text_align = crate::html::css_parse::TextAlign::Center;
}
}
}
let fill_bg = block_bg.is_some();
let inner_content_x = rect.x + cell_edges.border.left.thickness() + cell_edges.padding.left;
let inner_content_y = rect.y + cell_edges.border.top.thickness() + cell_edges.padding.top;
let inner_content_width = rect
.width
.saturating_sub(cell_edges.inner_horizontal())
.max(1);
let mut child_cursor = Cursor {
y: inner_content_y,
prev_bottom_margin: 0,
};
let child_containing = BoxConstraint {
x: inner_content_x,
width: inner_content_width,
};
let children = layout_children(
arena,
&node.children,
child_containing,
&mut child_cursor,
child_context,
);
let paint_style = PaintStyle {
box_edges: cell_edges,
runs: if wrapped_lines.is_empty() || wrapped_lines.iter().all(|l| l.is_empty()) {
None
} else {
Some(wrapped_lines)
},
column_layout: None,
font_size_cells: own_font_size_cells,
line_height_cells: own_font_size_cells.max(1),
border_radius: crate::html::css_parse::CornerRadii::default(),
fill_bg,
spacing_after: 0,
text_align,
text_indent: 0,
scrollbar: None,
};
LayoutBox::new_normal(
cell_id,
rect,
paint_style,
node.css_attr.outer_display(&node.node_type),
children,
)
}