Skip to main content

cssbox_core/
fragment.rs

1//! Fragment tree — the output of layout.
2//!
3//! Each fragment represents a positioned, sized piece of the layout output,
4//! corresponding to a box tree node.
5
6use crate::geometry::{Edges, Point, Rect, Size};
7use crate::tree::NodeId;
8
9/// A fragment produced by layout.
10#[derive(Debug, Clone)]
11pub struct Fragment {
12    /// The box tree node this fragment corresponds to.
13    pub node: NodeId,
14    /// Position relative to parent fragment.
15    pub position: Point,
16    /// Content box size.
17    pub size: Size,
18    /// Resolved padding.
19    pub padding: Edges,
20    /// Resolved border widths.
21    pub border: Edges,
22    /// Resolved margin.
23    pub margin: Edges,
24    /// Child fragments.
25    pub children: Vec<Fragment>,
26    /// Fragment kind.
27    pub kind: FragmentKind,
28}
29
30/// The kind of fragment.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum FragmentKind {
33    /// A block-level box.
34    Box,
35    /// An anonymous block box.
36    AnonymousBox,
37    /// A line box (produced by inline formatting context).
38    LineBox,
39    /// A text run within a line box.
40    TextRun,
41}
42
43impl Fragment {
44    pub fn new(node: NodeId, kind: FragmentKind) -> Self {
45        Self {
46            node,
47            position: Point::ZERO,
48            size: Size::ZERO,
49            padding: Edges::ZERO,
50            border: Edges::ZERO,
51            margin: Edges::ZERO,
52            children: Vec::new(),
53            kind,
54        }
55    }
56
57    /// Border box rect (position + padding + border + content, relative to parent).
58    pub fn border_box(&self) -> Rect {
59        Rect::new(
60            self.position.x,
61            self.position.y,
62            self.size.width + self.padding.horizontal() + self.border.horizontal(),
63            self.size.height + self.padding.vertical() + self.border.vertical(),
64        )
65    }
66
67    /// Margin box rect (position + margin + border + padding + content, relative to parent).
68    pub fn margin_box(&self) -> Rect {
69        Rect::new(
70            self.position.x - self.margin.left,
71            self.position.y - self.margin.top,
72            self.size.width
73                + self.padding.horizontal()
74                + self.border.horizontal()
75                + self.margin.horizontal(),
76            self.size.height
77                + self.padding.vertical()
78                + self.border.vertical()
79                + self.margin.vertical(),
80        )
81    }
82
83    /// Content box rect (position offset by border + padding).
84    pub fn content_box(&self) -> Rect {
85        Rect::new(
86            self.position.x + self.border.left + self.padding.left,
87            self.position.y + self.border.top + self.padding.top,
88            self.size.width,
89            self.size.height,
90        )
91    }
92
93    /// Compute absolute position by walking up the tree.
94    /// The position is the top-left of the border box in viewport coordinates.
95    pub fn absolute_position(&self, ancestors: &[&Fragment]) -> Point {
96        let mut x = self.position.x;
97        let mut y = self.position.y;
98        for ancestor in ancestors.iter().rev() {
99            x += ancestor.position.x + ancestor.border.left + ancestor.padding.left;
100            y += ancestor.position.y + ancestor.border.top + ancestor.padding.top;
101        }
102        Point::new(x, y)
103    }
104}
105
106/// The result of a layout computation.
107#[derive(Debug, Clone)]
108pub struct LayoutResult {
109    /// The root fragment.
110    pub root: Fragment,
111}
112
113impl LayoutResult {
114    /// Find a fragment by node ID (depth-first search).
115    pub fn find_fragment(&self, node: NodeId) -> Option<&Fragment> {
116        find_in_fragment(&self.root, node)
117    }
118
119    /// Get the layout output for a node (the bounding client rect equivalent).
120    pub fn get_layout(&self, node: NodeId) -> Option<LayoutOutput> {
121        // We need the absolute position, which requires ancestors
122        let mut path = Vec::new();
123        if find_path_to_node(&self.root, node, &mut path) {
124            let fragment = path.last().unwrap();
125            let ancestors: Vec<&Fragment> = path[..path.len() - 1].to_vec();
126            let abs_pos = fragment.absolute_position(&ancestors);
127            Some(LayoutOutput {
128                position: abs_pos,
129                size: fragment.size,
130                padding: fragment.padding,
131                border: fragment.border,
132                margin: fragment.margin,
133            })
134        } else {
135            None
136        }
137    }
138
139    /// Get the bounding client rect for a node.
140    pub fn bounding_rect(&self, node: NodeId) -> Option<Rect> {
141        self.get_layout(node).map(|l| {
142            Rect::new(
143                l.position.x,
144                l.position.y,
145                l.size.width + l.padding.horizontal() + l.border.horizontal(),
146                l.size.height + l.padding.vertical() + l.border.vertical(),
147            )
148        })
149    }
150}
151
152/// Layout output for a single node.
153#[derive(Debug, Clone, Copy, PartialEq)]
154pub struct LayoutOutput {
155    /// Absolute position of the border box (x, y).
156    pub position: Point,
157    /// Content box size.
158    pub size: Size,
159    /// Resolved padding.
160    pub padding: Edges,
161    /// Resolved border widths.
162    pub border: Edges,
163    /// Resolved margin.
164    pub margin: Edges,
165}
166
167fn find_in_fragment(fragment: &Fragment, node: NodeId) -> Option<&Fragment> {
168    if fragment.node == node {
169        return Some(fragment);
170    }
171    for child in &fragment.children {
172        if let Some(found) = find_in_fragment(child, node) {
173            return Some(found);
174        }
175    }
176    None
177}
178
179fn find_path_to_node<'a>(
180    fragment: &'a Fragment,
181    node: NodeId,
182    path: &mut Vec<&'a Fragment>,
183) -> bool {
184    path.push(fragment);
185    if fragment.node == node {
186        return true;
187    }
188    for child in &fragment.children {
189        if find_path_to_node(child, node, path) {
190            return true;
191        }
192    }
193    path.pop();
194    false
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200
201    #[test]
202    fn test_border_box() {
203        let mut f = Fragment::new(NodeId(0), FragmentKind::Box);
204        f.position = Point::new(10.0, 20.0);
205        f.size = Size::new(100.0, 50.0);
206        f.padding = Edges::all(5.0);
207        f.border = Edges::all(1.0);
208
209        let bb = f.border_box();
210        assert_eq!(bb.width, 112.0); // 100 + 5*2 + 1*2
211        assert_eq!(bb.height, 62.0); // 50 + 5*2 + 1*2
212    }
213}