use std::collections::HashMap;
use crate::{
html::css_parse::{FontContext, Viewport, WhiteSpace},
layout::{
BoxConstraint, Cursor, DEFAULT_LAYOUT, LayoutBox, LayoutContext, OuterDisplay, Rect,
layoutbox::{
GeneratedBox, GeneratedBoxKind, flatten_contents_children, generate_node,
layout_generated_box, layout_generated_tree, resolve_positioned_rects,
},
},
tdom::node::{Node, NodeArena, NodeId, NodeType, is_collapsible_whitespace},
};
pub fn layout_tree(
arena: &NodeArena,
root_id: NodeId,
containing: BoxConstraint,
cursor: &mut Cursor,
viewport_cols: usize,
viewport_rows: usize,
) -> Vec<LayoutBox> {
let scroll_offsets = HashMap::new();
layout_tree_with_scroll_offsets(
arena,
root_id,
containing,
cursor,
viewport_cols,
viewport_rows,
&scroll_offsets,
&NO_X,
)
}
static NO_X: std::sync::LazyLock<HashMap<NodeId, u32>> = std::sync::LazyLock::new(HashMap::new);
pub fn layout_tree_with_scroll_offsets(
arena: &NodeArena,
root_id: NodeId,
containing: BoxConstraint,
cursor: &mut Cursor,
viewport_cols: usize,
viewport_rows: usize,
scroll_offsets: &HashMap<NodeId, u32>,
scroll_x_offsets: &HashMap<NodeId, u32>,
) -> Vec<LayoutBox> {
let generated: Vec<GeneratedBox> = flatten_contents_children(arena, &arena[root_id].children)
.iter()
.filter_map(|&id| generate_node(arena, id))
.collect();
let mut boxes = layout_generated_tree(
arena,
&generated,
containing,
cursor,
LayoutContext {
font: FontContext::root(),
viewport: Viewport {
cols: viewport_cols,
rows: viewport_rows,
},
scroll_offsets,
scroll_x_offsets,
},
);
resolve_positioned_rects(&mut boxes, Rect::new(0, 0, viewport_cols, viewport_rows));
boxes
}
#[derive(Debug, Clone, Copy)]
struct InlineRowItem {
id: NodeId,
leading_space: usize,
}
fn collapsed_interitem_space(node: &Node, parent_white_space: WhiteSpace) -> usize {
if node.node_type != NodeType::Text {
return 0;
}
let Some(s) = node.data.as_deref() else {
return 0;
};
if s.is_empty() || !s.chars().all(is_collapsible_whitespace) {
return 0;
}
if parent_white_space.preserves_whitespace() {
s.chars().count()
} else {
1
}
}
pub fn layout_children(
arena: &NodeArena,
dom_children: &[NodeId],
containing: BoxConstraint,
cursor: &mut Cursor,
context: LayoutContext,
) -> Vec<LayoutBox> {
let parent_white_space = dom_children
.first()
.and_then(|&first| arena.get(first))
.and_then(|first| first.parent)
.map(|pid| arena[pid].css_attr.get_white_space(&arena[pid].node_type))
.unwrap_or(WhiteSpace::Normal);
let children = flatten_contents_children(arena, dom_children);
let children = &children;
let mut boxes = Vec::new();
let mut inline_row: Vec<InlineRowItem> = Vec::new();
let mut pending_space: usize = 0;
for &child_id in children {
let Some(generated) = generate_node(arena, child_id) else {
continue;
};
match generated.outer_display {
OuterDisplay::Inline { block: false } if generated.kind == GeneratedBoxKind::Normal => {
if !inline_row.is_empty() {
boxes.extend(layout_inline_row(
arena,
&inline_row,
containing,
cursor,
context,
));
inline_row.clear();
}
pending_space = 0;
boxes.extend(layout_inline_container_descendants(
arena, child_id, containing, cursor, context,
));
}
OuterDisplay::Inline { block: true } if generated.kind == GeneratedBoxKind::Normal => {
inline_row.push(InlineRowItem {
id: child_id,
leading_space: pending_space,
});
pending_space = 0;
}
_ => {
if generated.kind == GeneratedBoxKind::Text
|| generated.kind == GeneratedBoxKind::Anonymous
{
pending_space = pending_space.max(collapsed_interitem_space(
&arena[child_id],
parent_white_space,
));
continue;
}
if !inline_row.is_empty() {
boxes.extend(layout_inline_row(
arena,
&inline_row,
containing,
cursor,
context,
));
inline_row.clear();
}
pending_space = 0;
if let Some(b) =
layout_generated_box(arena, &generated, containing, cursor, context, None)
{
boxes.push(b);
}
}
}
}
if !inline_row.is_empty() {
boxes.extend(layout_inline_row(
arena,
&inline_row,
containing,
cursor,
context,
));
}
boxes
}
fn layout_inline_row(
arena: &NodeArena,
items: &[InlineRowItem],
containing: BoxConstraint,
cursor: &mut Cursor,
context: LayoutContext,
) -> Vec<LayoutBox> {
struct Measured {
width: usize,
height: usize,
margin_before: usize,
margin_after: usize,
leading_space: usize,
}
let mut measured: Vec<Option<Measured>> = items.iter().map(|_| None).collect();
for (idx, item) in items.iter().enumerate() {
let Some(generated) = generate_node(arena, item.id) else {
continue;
};
let mut scratch = Cursor {
y: 0,
prev_bottom_margin: 0,
};
if let Some(b) =
layout_generated_box(arena, &generated, containing, &mut scratch, context, None)
{
let edges = &b.paint_style.box_edges;
measured[idx] = Some(Measured {
width: b.rect.width,
height: b.rect.height,
margin_before: edges.margin.left.max(0) as usize,
margin_after: edges.margin.right.max(0) as usize,
leading_space: item.leading_space,
});
}
}
let max_h = measured
.iter()
.filter_map(|m| m.as_ref().map(|m| m.height))
.max()
.unwrap_or(1);
let max_x = containing.x.saturating_add(containing.width);
let mut x = containing.x;
let mut y = cursor.y;
let mut out: Vec<LayoutBox> = Vec::new();
for (idx, item) in items.iter().enumerate() {
let Some(Some(m)) = measured.get(idx) else {
continue;
};
let space = if x > containing.x { m.leading_space } else { 0 };
let start = x.saturating_add(space).saturating_add(m.margin_before);
if start > containing.x && start.saturating_add(m.width) > max_x {
x = containing.x;
y = y.saturating_add(max_h);
let start = x.saturating_add(m.margin_before);
let rect = Rect::new(start, y, m.width.max(1), m.height.max(1));
let Some(generated) = generate_node(arena, item.id) else {
continue;
};
if let Some(b) = layout_generated_box(
arena,
&generated,
containing,
&mut Cursor {
y,
prev_bottom_margin: 0,
},
context,
Some(rect),
) {
out.push(b);
}
x = start.saturating_add(m.width).saturating_add(m.margin_after);
continue;
}
let rect = Rect::new(start, y, m.width.max(1), m.height.max(1));
let Some(generated) = generate_node(arena, item.id) else {
continue;
};
if let Some(b) = layout_generated_box(
arena,
&generated,
containing,
&mut Cursor {
y,
prev_bottom_margin: 0,
},
context,
Some(rect),
) {
out.push(b);
}
x = start.saturating_add(m.width).saturating_add(m.margin_after);
}
cursor.y = y.saturating_add(max_h);
out
}
fn layout_inline_container_descendants(
arena: &NodeArena,
container_id: NodeId,
containing: BoxConstraint,
cursor: &mut Cursor,
context: LayoutContext,
) -> Vec<LayoutBox> {
let mut boxes = Vec::new();
let mut stack: Vec<NodeId> = arena[container_id].children.iter().rev().copied().collect();
while let Some(id) = stack.pop() {
let Some(generated) = generate_node(arena, id) else {
continue;
};
if generated.kind == GeneratedBoxKind::Text {
continue;
}
if let Some(b) = layout_generated_box(arena, &generated, containing, cursor, context, None)
{
boxes.push(b);
} else {
stack.extend(arena[id].children.iter().rev().copied());
}
}
boxes
}
pub fn dispatch(
arena: &NodeArena,
node_id: NodeId,
containing: BoxConstraint,
cursor: &mut Cursor,
context: LayoutContext,
pre_allocated_rect: Option<Rect>,
) -> Option<LayoutBox> {
let generated = generate_node(arena, node_id)?;
layout_generated_box(
arena,
&generated,
containing,
cursor,
context,
pre_allocated_rect,
)
}
pub(crate) fn dispatch_flow_geometry(
arena: &NodeArena,
node_id: NodeId,
containing: BoxConstraint,
cursor: &mut Cursor,
context: LayoutContext,
pre_allocated_rect: Option<Rect>,
) -> Option<LayoutBox> {
for layout_fn in DEFAULT_LAYOUT {
if let Some(b) = layout_fn(
arena,
node_id,
containing,
cursor,
context,
pre_allocated_rect,
) {
return Some(b);
}
}
log::debug!("No layout handler for {:?}", arena[node_id].node_type);
None
}