avx_browser/rendering/
mod.rs1use std::collections::BTreeMap;
4
5#[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 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 if html.starts_with('<') {
34 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 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 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 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 pub fn extract_title(&self) -> Option<String> {
103 self.find_element("title")
104 }
105
106 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#[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 let mut rules = Vec::new();
158
159 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#[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 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 (self.viewport_width, 20)
212 }
213 NodeType::Text(text) => {
214 (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
243pub 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