avx_browser/rendering/
mod.rs

1//! HTML/CSS rendering engine with DOM tree construction and layout computation
2
3use std::collections::BTreeMap;
4
5/// DOM (Document Object Model)
6#[derive(Debug, Clone)]
7pub struct Dom {
8    pub root: DomNode,
9}
10
11#[derive(Debug, Clone)]
12pub struct DomNode {
13    pub node_type: NodeType,
14    pub children: Vec<DomNode>,
15    pub attributes: BTreeMap<String, String>,
16}
17
18#[derive(Debug, Clone, PartialEq)]
19pub enum NodeType {
20    Element { tag: String, inner_text: String },
21    Text(String),
22}
23
24impl Dom {
25    /// Parse HTML into abstract syntax tree representation
26    pub fn parse(html: &str) -> Self {
27        let root = Self::parse_node(html);
28        Self { root }
29    }
30
31    fn parse_node(html: &str) -> DomNode {
32        // Recursive descent HTML parser
33        if html.starts_with('<') {
34            // Element node
35            if let Some(tag_end) = html.find('>') {
36                let tag_content = &html[1..tag_end];
37                let tag = tag_content.split_whitespace().next()
38                    .unwrap_or("div")
39                    .to_string();
40
41                // Extract inner text content - find matching closing tag
42                let close_tag = format!("</{}>", tag);
43                let inner_content = if let Some(close_start) = html.find(&close_tag) {
44                    html[tag_end + 1..close_start].to_string()
45                } else {
46                    String::new()
47                };
48
49                // Parse children recursively
50                let mut children = Vec::new();
51                let mut remaining = inner_content.as_str();
52                while !remaining.is_empty() {
53                    if remaining.starts_with('<') && !remaining.starts_with("</") {
54                        if let Some(child_tag_end) = remaining.find('>') {
55                            let child_tag_content = &remaining[1..child_tag_end];
56                            let child_tag = child_tag_content.split_whitespace().next()
57                                .unwrap_or("div");
58                            let child_close_tag = format!("</{}>", child_tag);
59                            if let Some(child_close_start) = remaining.find(&child_close_tag) {
60                                let child_close_end = child_close_start + child_close_tag.len();
61                                let child_html = &remaining[..child_close_end];
62                                children.push(Self::parse_node(child_html));
63                                remaining = &remaining[child_close_end..];
64                            } else {
65                                break;
66                            }
67                        } else {
68                            break;
69                        }
70                    } else {
71                        // Skip non-element content
72                        if let Some(next_tag) = remaining.find('<') {
73                            remaining = &remaining[next_tag..];
74                        } else {
75                            break;
76                        }
77                    }
78                }
79
80                DomNode {
81                    node_type: NodeType::Element { tag, inner_text: inner_content },
82                    children,
83                    attributes: BTreeMap::new(),
84                }
85            } else {
86                Self::text_node(html)
87            }
88        } else {
89            Self::text_node(html)
90        }
91    }
92
93    fn text_node(text: &str) -> DomNode {
94        DomNode {
95            node_type: NodeType::Text(text.to_string()),
96            children: Vec::new(),
97            attributes: BTreeMap::new(),
98        }
99    }
100
101    /// Extract document title from DOM tree
102    pub fn extract_title(&self) -> Option<String> {
103        self.find_element("title")
104    }
105
106    /// Query DOM tree for element by tag name
107    pub fn find_element(&self, tag: &str) -> Option<String> {
108        self.find_in_node(&self.root, tag)
109    }
110
111    fn find_in_node(&self, node: &DomNode, tag: &str) -> Option<String> {
112        match &node.node_type {
113            NodeType::Element { tag: node_tag, inner_text } => {
114                if node_tag == tag {
115                    return Some(inner_text.clone());
116                }
117            }
118            _ => {}
119        }
120
121        for child in &node.children {
122            if let Some(result) = self.find_in_node(child, tag) {
123                return Some(result);
124            }
125        }
126
127        None
128    }
129}
130
131/// CSS parser implementing selector matching and cascade resolution
132#[derive(Debug)]
133pub struct CssParser {
134    pub stylesheets: Vec<Stylesheet>,
135}
136
137#[derive(Debug)]
138pub struct Stylesheet {
139    pub rules: Vec<CssRule>,
140}
141
142#[derive(Debug)]
143pub struct CssRule {
144    pub selector: String,
145    pub declarations: BTreeMap<String, String>,
146}
147
148impl CssParser {
149    pub fn new() -> Self {
150        Self {
151            stylesheets: Vec::new(),
152        }
153    }
154
155    pub fn parse(&mut self, css: &str) {
156        // Tokenization-based CSS parser
157        let mut rules = Vec::new();
158
159        // Tokenize by rule terminator
160        for rule_text in css.split('}') {
161            if let Some(open_brace) = rule_text.find('{') {
162                let selector = rule_text[..open_brace].trim().to_string();
163                let declarations_text = &rule_text[open_brace + 1..];
164
165                let mut declarations = BTreeMap::new();
166                for decl in declarations_text.split(';') {
167                    if let Some(colon) = decl.find(':') {
168                        let property = decl[..colon].trim().to_string();
169                        let value = decl[colon + 1..].trim().to_string();
170                        declarations.insert(property, value);
171                    }
172                }
173
174                rules.push(CssRule {
175                    selector,
176                    declarations,
177                });
178            }
179        }
180
181        self.stylesheets.push(Stylesheet { rules });
182    }
183}
184
185/// Layout engine implementing box model and flow layout algorithm
186#[derive(Debug)]
187pub struct LayoutEngine {
188    pub viewport_width: u32,
189    pub viewport_height: u32,
190}
191
192impl LayoutEngine {
193    pub fn new(width: u32, height: u32) -> Self {
194        Self {
195            viewport_width: width,
196            viewport_height: height,
197        }
198    }
199
200    /// Compute layout tree with positioned boxes
201    pub fn layout(&self, dom: &Dom) -> LayoutTree {
202        LayoutTree {
203            root: self.layout_node(&dom.root, 0, 0),
204        }
205    }
206
207    fn layout_node(&self, node: &DomNode, x: u32, y: u32) -> LayoutNode {
208        let (width, height) = match &node.node_type {
209            NodeType::Element { .. } => {
210                // Block-level box: full viewport width
211                (self.viewport_width, 20)
212            }
213            NodeType::Text(text) => {
214                // Inline text: character-based width estimation
215                (text.len() as u32 * 8, 16)
216            }
217        };
218
219        LayoutNode {
220            x,
221            y,
222            width,
223            height,
224            children: Vec::new(),
225        }
226    }
227}
228
229#[derive(Debug)]
230pub struct LayoutTree {
231    pub root: LayoutNode,
232}
233
234#[derive(Debug)]
235pub struct LayoutNode {
236    pub x: u32,
237    pub y: u32,
238    pub width: u32,
239    pub height: u32,
240    pub children: Vec<LayoutNode>,
241}
242
243/// Render layout tree to terminal using ASCII art representation
244pub fn render_to_terminal(_layout: &LayoutTree, dom: &Dom) -> String {
245    let mut output = String::new();
246
247    output.push_str("╔════════════════════════════════════════════════════════════════╗\n");
248    output.push_str("║                    avx BROWSER                               ║\n");
249    output.push_str("╠════════════════════════════════════════════════════════════════╣\n");
250
251    render_node(&dom.root, &mut output, 0);
252
253    output.push_str("╚════════════════════════════════════════════════════════════════╝\n");
254    output
255}
256
257fn render_node(node: &DomNode, output: &mut String, indent: usize) {
258    let padding = "  ".repeat(indent);
259
260    match &node.node_type {
261        NodeType::Element { tag, inner_text } => {
262            output.push_str(&format!("║ {}<{}> {}\n", padding, tag, inner_text));
263        }
264        NodeType::Text(text) => {
265            if !text.trim().is_empty() {
266                output.push_str(&format!("║ {}{}\n", padding, text.trim()));
267            }
268        }
269    }
270
271    for child in &node.children {
272        render_node(child, output, indent + 1);
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279
280    #[test]
281    fn test_dom_parsing() {
282        let html = "<html><title>Test</title><body>Content</body></html>";
283        let dom = Dom::parse(html);
284
285        let title = dom.extract_title();
286        assert_eq!(title, Some("Test".to_string()));
287    }
288
289    #[test]
290    fn test_css_parsing() {
291        let mut parser = CssParser::new();
292        let css = "body { color: red; font-size: 14px; }";
293
294        parser.parse(css);
295
296        assert_eq!(parser.stylesheets.len(), 1);
297        assert_eq!(parser.stylesheets[0].rules.len(), 1);
298    }
299
300    #[test]
301    fn test_layout_engine() {
302        let engine = LayoutEngine::new(800, 600);
303        assert_eq!(engine.viewport_width, 800);
304    }
305}
306
307
308
309
310