Skip to main content

cssbox_core/
tree.rs

1//! Box tree representation for layout.
2//!
3//! The box tree is the input to the layout engine. It represents the CSS box model
4//! structure after display computation has determined which formatting contexts apply.
5
6use crate::style::{ComputedStyle, DisplayInner};
7
8/// A unique identifier for a node in the box tree.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct NodeId(pub usize);
11
12impl NodeId {
13    pub const ROOT: Self = NodeId(0);
14}
15
16/// Text content for inline layout.
17#[derive(Debug, Clone)]
18pub struct TextContent {
19    pub text: String,
20}
21
22/// The kind of content a box tree node holds.
23#[derive(Debug, Clone)]
24pub enum NodeKind {
25    /// An element node with children.
26    Element,
27    /// A text node (leaf).
28    Text(TextContent),
29    /// An anonymous block wrapper (generated by the engine).
30    AnonymousBlock,
31    /// An anonymous inline wrapper.
32    AnonymousInline,
33}
34
35/// A single node in the box tree.
36#[derive(Debug, Clone)]
37pub struct BoxTreeNode {
38    pub id: NodeId,
39    pub kind: NodeKind,
40    pub style: ComputedStyle,
41    pub children: Vec<NodeId>,
42    pub parent: Option<NodeId>,
43}
44
45impl BoxTreeNode {
46    pub fn new(id: NodeId, kind: NodeKind, style: ComputedStyle) -> Self {
47        Self {
48            id,
49            kind,
50            style,
51            children: Vec::new(),
52            parent: None,
53        }
54    }
55
56    pub fn is_text(&self) -> bool {
57        matches!(self.kind, NodeKind::Text(_))
58    }
59
60    pub fn text_content(&self) -> Option<&str> {
61        match &self.kind {
62            NodeKind::Text(tc) => Some(&tc.text),
63            _ => None,
64        }
65    }
66}
67
68/// The box tree — arena-allocated tree of layout boxes.
69#[derive(Debug, Clone)]
70pub struct BoxTree {
71    nodes: Vec<BoxTreeNode>,
72    root: NodeId,
73}
74
75impl BoxTree {
76    pub fn new() -> Self {
77        Self {
78            nodes: Vec::new(),
79            root: NodeId(0),
80        }
81    }
82
83    /// Add a new node to the tree. Returns the node's ID.
84    pub fn add_node(&mut self, kind: NodeKind, style: ComputedStyle) -> NodeId {
85        let id = NodeId(self.nodes.len());
86        self.nodes.push(BoxTreeNode::new(id, kind, style));
87        id
88    }
89
90    /// Set the root node.
91    pub fn set_root(&mut self, id: NodeId) {
92        self.root = id;
93    }
94
95    /// Append a child to a parent node.
96    pub fn append_child(&mut self, parent: NodeId, child: NodeId) {
97        self.nodes[child.0].parent = Some(parent);
98        self.nodes[parent.0].children.push(child);
99    }
100
101    /// Get a reference to a node.
102    pub fn node(&self, id: NodeId) -> &BoxTreeNode {
103        &self.nodes[id.0]
104    }
105
106    /// Get a mutable reference to a node.
107    pub fn node_mut(&mut self, id: NodeId) -> &mut BoxTreeNode {
108        &mut self.nodes[id.0]
109    }
110
111    /// Get the root node ID.
112    pub fn root(&self) -> NodeId {
113        self.root
114    }
115
116    /// Get the children of a node.
117    pub fn children(&self, id: NodeId) -> &[NodeId] {
118        &self.nodes[id.0].children
119    }
120
121    /// Get the style of a node.
122    pub fn style(&self, id: NodeId) -> &ComputedStyle {
123        &self.nodes[id.0].style
124    }
125
126    /// Get the parent of a node.
127    pub fn parent(&self, id: NodeId) -> Option<NodeId> {
128        self.nodes[id.0].parent
129    }
130
131    /// Total number of nodes.
132    pub fn len(&self) -> usize {
133        self.nodes.len()
134    }
135
136    pub fn is_empty(&self) -> bool {
137        self.nodes.is_empty()
138    }
139
140    /// Determine the formatting context for a node's children.
141    pub fn formatting_context(&self, id: NodeId) -> FormattingContextType {
142        let style = self.style(id);
143        match style.display.inner {
144            DisplayInner::Flex => FormattingContextType::Flex,
145            DisplayInner::Grid => FormattingContextType::Grid,
146            DisplayInner::Table => FormattingContextType::Table,
147            DisplayInner::Flow | DisplayInner::FlowRoot => {
148                // Determine if children are all block or all inline
149                let children = self.children(id);
150                if children.is_empty() {
151                    return FormattingContextType::Block;
152                }
153
154                let has_block = children.iter().any(|&c| {
155                    let cs = self.style(c);
156                    cs.display.is_block_level() && !cs.is_out_of_flow()
157                });
158
159                if has_block {
160                    FormattingContextType::Block
161                } else {
162                    FormattingContextType::Inline
163                }
164            }
165            _ => FormattingContextType::Block,
166        }
167    }
168}
169
170impl Default for BoxTree {
171    fn default() -> Self {
172        Self::new()
173    }
174}
175
176/// The type of formatting context established by a container.
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178pub enum FormattingContextType {
179    Block,
180    Inline,
181    Flex,
182    Grid,
183    Table,
184}
185
186/// Helper to build a box tree from a simple description.
187pub struct BoxTreeBuilder {
188    pub tree: BoxTree,
189}
190
191impl BoxTreeBuilder {
192    pub fn new() -> Self {
193        Self {
194            tree: BoxTree::new(),
195        }
196    }
197
198    /// Add the root element.
199    pub fn root(&mut self, style: ComputedStyle) -> NodeId {
200        let id = self.tree.add_node(NodeKind::Element, style);
201        self.tree.set_root(id);
202        id
203    }
204
205    /// Add an element child.
206    pub fn element(&mut self, parent: NodeId, style: ComputedStyle) -> NodeId {
207        let id = self.tree.add_node(NodeKind::Element, style);
208        self.tree.append_child(parent, id);
209        id
210    }
211
212    /// Add a text child.
213    pub fn text(&mut self, parent: NodeId, text: &str) -> NodeId {
214        let id = self.tree.add_node(
215            NodeKind::Text(TextContent {
216                text: text.to_string(),
217            }),
218            ComputedStyle::inline(),
219        );
220        self.tree.append_child(parent, id);
221        id
222    }
223
224    /// Finish building and return the tree.
225    pub fn build(self) -> BoxTree {
226        self.tree
227    }
228}
229
230impl Default for BoxTreeBuilder {
231    fn default() -> Self {
232        Self::new()
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239    use crate::style::ComputedStyle;
240
241    #[test]
242    fn test_build_simple_tree() {
243        let mut builder = BoxTreeBuilder::new();
244        let root = builder.root(ComputedStyle::block());
245        let child1 = builder.element(root, ComputedStyle::block());
246        let child2 = builder.element(root, ComputedStyle::block());
247        builder.text(child1, "Hello");
248        let tree = builder.build();
249
250        assert_eq!(tree.len(), 4);
251        assert_eq!(tree.children(root).len(), 2);
252        assert_eq!(tree.children(child1).len(), 1);
253        assert_eq!(tree.children(child2).len(), 0);
254        assert_eq!(tree.parent(child1), Some(root));
255    }
256
257    #[test]
258    fn test_formatting_context_detection() {
259        let mut builder = BoxTreeBuilder::new();
260        let root = builder.root(ComputedStyle::block());
261        builder.element(root, ComputedStyle::block());
262        builder.element(root, ComputedStyle::block());
263        let tree = builder.build();
264
265        assert_eq!(tree.formatting_context(root), FormattingContextType::Block);
266    }
267}