use crate::{
html::css_parse::CssDisplay,
layout::{FormattingContext, OuterDisplay},
tdom::node::{NodeArena, NodeId, NodeType, document_base_href},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GeneratedBoxKind {
Normal,
Text,
Anonymous,
Replaced,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GeneratedBox {
pub node_id: NodeId,
pub kind: GeneratedBoxKind,
pub outer_display: OuterDisplay,
pub inner_formatting_context: FormattingContext,
pub payload: GeneratedPayload,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum GeneratedPayload {
#[default]
None,
Image { src: String },
Input,
Select,
Textarea,
Button,
Transparent,
}
pub fn generate_box_tree(arena: &NodeArena, root_id: NodeId) -> Vec<GeneratedBox> {
arena[root_id]
.children
.iter()
.filter_map(|&child_id| generate_node(arena, child_id))
.collect()
}
pub fn generate_node(arena: &NodeArena, node_id: NodeId) -> Option<GeneratedBox> {
let node = &arena[node_id];
if is_non_rendered(&node.node_type) {
return None;
}
if matches!(node.node_type, NodeType::Text) {
return Some(GeneratedBox {
node_id,
kind: GeneratedBoxKind::Text,
outer_display: OuterDisplay::Inline { block: false },
inner_formatting_context: FormattingContext::Flow,
payload: GeneratedPayload::None,
});
}
let display = node
.css_attr
.display()
.unwrap_or_else(|| node.node_type.ua_default_display());
if display.is_dropped() {
return None;
}
if node.node_type == NodeType::Dialog
&& !node.attributes.contains_key("open")
&& !node.css_attr.is_set("display")
{
return None;
}
if node.attributes.contains_key("hidden") && !node.css_attr.is_set("display") {
return None;
}
if display.is_transparent() {
return Some(GeneratedBox {
node_id,
kind: GeneratedBoxKind::Anonymous,
outer_display: OuterDisplay::Inline { block: false },
inner_formatting_context: FormattingContext::Flow,
payload: GeneratedPayload::Transparent,
});
}
let (kind, payload) = if matches!(node.node_type, NodeType::Img) {
let raw_src = node.get_src().cloned().unwrap_or_default();
let src = if raw_src.is_empty() {
raw_src
} else {
let base = document_base_href(arena, node_id);
crate::resources::resolve_url(base.as_deref(), &raw_src)
};
(GeneratedBoxKind::Replaced, GeneratedPayload::Image { src })
} else if matches!(node.node_type, NodeType::Input) {
if node
.get_type()
.is_some_and(|t| t.eq_ignore_ascii_case("hidden"))
{
return None;
}
(GeneratedBoxKind::Replaced, GeneratedPayload::Input)
} else if matches!(node.node_type, NodeType::Textarea) {
(GeneratedBoxKind::Replaced, GeneratedPayload::Textarea)
} else if matches!(node.node_type, NodeType::Button) {
(GeneratedBoxKind::Replaced, GeneratedPayload::Button)
} else if matches!(node.node_type, NodeType::Select) {
(GeneratedBoxKind::Replaced, GeneratedPayload::Select)
} else {
(GeneratedBoxKind::Normal, GeneratedPayload::None)
};
let outer_display = OuterDisplay::from_css_display(display);
let inner_formatting_context = if display.is_table_family() {
FormattingContext::Table
} else if display.is_flex() {
FormattingContext::Flex
} else if matches!(display, CssDisplay::Grid | CssDisplay::InlineGrid) {
FormattingContext::Grid
} else {
FormattingContext::Flow
};
Some(GeneratedBox {
node_id,
kind,
outer_display,
inner_formatting_context,
payload,
})
}
pub fn visible_children(arena: &NodeArena, parent_id: NodeId) -> std::borrow::Cow<'_, [NodeId]> {
let parent = &arena[parent_id];
if parent.node_type != NodeType::Details || parent.attributes.contains_key("open") {
return std::borrow::Cow::Borrowed(&parent.children);
}
match parent
.children
.iter()
.position(|&id| arena[id].node_type == NodeType::Summary)
{
Some(pos) => std::borrow::Cow::Owned(vec![parent.children[pos]]),
None => std::borrow::Cow::Owned(Vec::new()),
}
}
pub fn flatten_contents_children(arena: &NodeArena, children: &[NodeId]) -> Vec<NodeId> {
let mut out = Vec::with_capacity(children.len());
for &id in children {
let is_contents = arena
.get(id)
.and_then(|node| node.css_attr.display())
.is_some_and(|d| d.is_transparent());
if is_contents {
out.extend(flatten_contents_children(
arena,
&visible_children(arena, id),
));
} else {
out.push(id);
}
}
out
}
fn is_non_rendered(node_type: &NodeType) -> bool {
matches!(
node_type,
NodeType::Head
| NodeType::Style
| NodeType::Link
| NodeType::Meta
| NodeType::Base
| NodeType::Noscript
| NodeType::Template
| NodeType::Datalist
| NodeType::Comment
| NodeType::DocumentType
| NodeType::DocumentFragment
)
}