use std::collections::HashMap;
use crate::dom::{Dom, NodeType};
use crate::style::ComputedStyle;
use super::box_model::Dimensions;
const INLINE_TAGS: &[&str] = &[
"span", "a", "b", "i", "em", "strong", "code", "small", "label", "abbr",
];
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BoxType {
Block(usize), Anonymous,
}
#[derive(Debug, Clone)]
pub struct LayoutBox {
pub dimensions: Dimensions,
pub box_type: BoxType,
pub children: Vec<LayoutBox>,
pub text_content: Option<String>,
}
impl LayoutBox {
pub fn new(box_type: BoxType) -> Self {
LayoutBox {
dimensions: Dimensions::default(),
box_type,
children: vec![],
text_content: None,
}
}
pub fn new_block(node_idx: usize) -> Self {
LayoutBox::new(BoxType::Block(node_idx))
}
pub fn new_anonymous_text(text: String) -> Self {
LayoutBox {
dimensions: Dimensions::default(),
box_type: BoxType::Anonymous,
children: vec![],
text_content: Some(text),
}
}
}
fn is_block_level(dom: &Dom, node_idx: usize, styles: &HashMap<usize, ComputedStyle>) -> bool {
if let Some(style) = styles.get(&node_idx) {
if let Some(display) = style.get("display") {
return display != "inline" && display != "none";
}
}
match dom.element_at(node_idx) {
Some(el) => !INLINE_TAGS.contains(&el.tag.as_str()),
None => true,
}
}
fn is_display_none(node_idx: usize, styles: &HashMap<usize, ComputedStyle>) -> bool {
styles
.get(&node_idx)
.and_then(|s| s.get("display"))
.map(|d| d == "none")
.unwrap_or(false)
}
fn collect_inline_text(dom: &Dom, node_idx: usize, buf: &mut String) {
match &dom.nodes[node_idx].node_type {
NodeType::Text(t) => buf.push_str(t),
NodeType::Element(_) => {
for &child in &dom.nodes[node_idx].children {
collect_inline_text(dom, child, buf);
}
}
_ => {}
}
}
pub fn build_layout_tree(
dom: &Dom,
styles: &HashMap<usize, ComputedStyle>,
node_idx: usize,
) -> Option<LayoutBox> {
let is_container = matches!(
dom.nodes[node_idx].node_type,
NodeType::Element(_) | NodeType::Document
);
if !is_container {
return None;
}
if is_display_none(node_idx, styles) {
return None;
}
let mut layout_box = LayoutBox::new_block(node_idx);
let mut pending_inline = String::new();
for &child_idx in &dom.nodes[node_idx].children {
match &dom.nodes[child_idx].node_type {
NodeType::Text(t) => {
if !t.trim().is_empty() {
pending_inline.push_str(t);
}
}
NodeType::Element(_) => {
if is_display_none(child_idx, styles) {
continue;
}
if is_block_level(dom, child_idx, styles) {
flush_inline_run(&mut pending_inline, &mut layout_box);
if let Some(child_box) = build_layout_tree(dom, styles, child_idx) {
layout_box.children.push(child_box);
}
} else {
collect_inline_text(dom, child_idx, &mut pending_inline);
}
}
_ => {}
}
}
flush_inline_run(&mut pending_inline, &mut layout_box);
Some(layout_box)
}
fn flush_inline_run(pending_inline: &mut String, layout_box: &mut LayoutBox) {
if !pending_inline.trim().is_empty() {
layout_box
.children
.push(LayoutBox::new_anonymous_text(std::mem::take(pending_inline)));
} else {
pending_inline.clear();
}
}