⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/browser/
integration.rs

1//! Browser Integration
2//!
3//! End-to-end convenience functions and conformance tests that exercise
4//! the full pipeline: HTML tokenization, DOM construction, CSS parsing,
5//! style resolution, layout computation, and pixel painting.
6
7#![allow(dead_code)]
8
9use alloc::vec::Vec;
10
11use super::{
12    css_parser::CssParser,
13    dom::Document,
14    layout::{build_layout_tree, LayoutBox},
15    paint::{build_display_list, DisplayList, Painter, PixelRect},
16    style::StyleResolver,
17    tree_builder::TreeBuilder,
18};
19
20/// Render an HTML string to a pixel buffer
21///
22/// Convenience function that runs the full pipeline:
23/// tokenize -> build DOM -> parse CSS -> resolve styles -> layout -> paint
24pub fn render_html(html: &str, width: i32, height: i32) -> Vec<u32> {
25    render_html_with_css(html, "", width, height)
26}
27
28/// Render HTML with an external CSS string to a pixel buffer
29pub fn render_html_with_css(html: &str, css: &str, width: i32, height: i32) -> Vec<u32> {
30    let doc = TreeBuilder::build(html);
31    let stylesheet = CssParser::parse(css);
32
33    let mut resolver = StyleResolver::new();
34    resolver.add_stylesheet(stylesheet);
35
36    let layout = build_layout_tree(&doc, &resolver, width, height);
37    let display_list = build_display_list(&layout, 0);
38
39    let mut painter = Painter::new(width as usize, height as usize);
40    painter.paint(&display_list);
41
42    painter.pixels
43}
44
45/// Render HTML and return the layout tree (for inspection)
46pub fn render_to_layout(html: &str, css: &str, width: i32, height: i32) -> LayoutBox {
47    let doc = TreeBuilder::build(html);
48    let stylesheet = CssParser::parse(css);
49
50    let mut resolver = StyleResolver::new();
51    resolver.add_stylesheet(stylesheet);
52
53    build_layout_tree(&doc, &resolver, width, height)
54}
55
56/// Render HTML and return (layout, display_list) for inspection
57pub fn render_to_display_list(
58    html: &str,
59    css: &str,
60    width: i32,
61    height: i32,
62) -> (LayoutBox, DisplayList) {
63    let doc = TreeBuilder::build(html);
64    let stylesheet = CssParser::parse(css);
65
66    let mut resolver = StyleResolver::new();
67    resolver.add_stylesheet(stylesheet);
68
69    let layout = build_layout_tree(&doc, &resolver, width, height);
70    let display_list = build_display_list(&layout, 0);
71
72    (layout, display_list)
73}
74
75/// Parse HTML and return the Document for inspection
76pub fn parse_html(html: &str) -> Document {
77    TreeBuilder::build(html)
78}
79
80/// Check if a pixel region contains a specific color
81pub fn region_contains_color(pixels: &[u32], width: usize, rect: &PixelRect, color: u32) -> bool {
82    for y in rect.y..(rect.y + rect.height) {
83        for x in rect.x..(rect.x + rect.width) {
84            if x >= 0 && y >= 0 {
85                let idx = y as usize * width + x as usize;
86                if idx < pixels.len() && pixels[idx] == color {
87                    return true;
88                }
89            }
90        }
91    }
92    false
93}
94
95/// Check if a pixel region is entirely one color
96pub fn region_is_solid_color(pixels: &[u32], width: usize, rect: &PixelRect, color: u32) -> bool {
97    for y in rect.y..(rect.y + rect.height) {
98        for x in rect.x..(rect.x + rect.width) {
99            if x >= 0 && y >= 0 {
100                let idx = y as usize * width + x as usize;
101                if idx < pixels.len() && pixels[idx] != color {
102                    return false;
103                }
104            }
105        }
106    }
107    true
108}
109
110/// Count the number of unique colors in a pixel region
111pub fn count_unique_colors(pixels: &[u32], width: usize, rect: &PixelRect) -> usize {
112    let mut colors: Vec<u32> = Vec::new();
113    for y in rect.y..(rect.y + rect.height) {
114        for x in rect.x..(rect.x + rect.width) {
115            if x >= 0 && y >= 0 {
116                let idx = y as usize * width + x as usize;
117                if idx < pixels.len() && !colors.contains(&pixels[idx]) {
118                    colors.push(pixels[idx]);
119                }
120            }
121        }
122    }
123    colors.len()
124}
125
126/// Find a layout box with a specific tag (for test inspection)
127pub fn find_layout_box_by_tag<'a>(
128    layout: &'a LayoutBox,
129    doc: &Document,
130    tag: &str,
131) -> Option<&'a LayoutBox> {
132    if let Some(node_id) = layout.node_id {
133        if doc.tag_name(node_id) == Some(tag) {
134            return Some(layout);
135        }
136    }
137    for child in &layout.children {
138        if let Some(found) = find_layout_box_by_tag(child, doc, tag) {
139            return Some(found);
140        }
141    }
142    None
143}
144
145/// Count layout boxes in the tree
146pub fn count_layout_boxes(layout: &LayoutBox) -> usize {
147    let mut count = 1;
148    for child in &layout.children {
149        count += count_layout_boxes(child);
150    }
151    count
152}
153
154#[cfg(test)]
155mod tests {
156    #[allow(unused_imports)]
157    use alloc::{string::String, vec};
158
159    use super::*;
160
161    #[test]
162    fn test_render_empty() {
163        let pixels = render_html("", 100, 100);
164        assert_eq!(pixels.len(), 10000);
165    }
166
167    #[test]
168    fn test_render_simple_paragraph() {
169        let pixels = render_html("<p>Hello</p>", 200, 200);
170        assert_eq!(pixels.len(), 40000);
171    }
172
173    #[test]
174    fn test_render_with_css() {
175        let pixels = render_html_with_css(
176            "<div>Hello</div>",
177            "div { background-color: #ff0000; width: 100px; height: 50px; }",
178            200,
179            200,
180        );
181        assert_eq!(pixels.len(), 40000);
182        // Should contain some red pixels
183        let has_red = pixels.iter().any(|&p| p == 0xFFFF0000);
184        assert!(has_red, "Expected red pixels from background-color");
185    }
186
187    #[test]
188    fn test_render_headings() {
189        let pixels = render_html("<h1>Title</h1><h2>Subtitle</h2><p>Text</p>", 400, 300);
190        assert_eq!(pixels.len(), 120000);
191    }
192
193    #[test]
194    fn test_render_colored_text() {
195        let pixels = render_html_with_css("<p>Red text</p>", "p { color: #ff0000; }", 200, 200);
196        assert_eq!(pixels.len(), 40000);
197    }
198
199    #[test]
200    fn test_render_nested_elements() {
201        let pixels = render_html("<div><p>Nested <b>bold</b></p></div>", 200, 200);
202        assert_eq!(pixels.len(), 40000);
203    }
204
205    #[test]
206    fn test_render_links() {
207        let doc = parse_html("<a href=\"/page\">Click me</a>");
208        let anchors = doc.get_elements_by_tag_name("a");
209        assert_eq!(anchors.len(), 1);
210        assert_eq!(doc.inner_text(anchors[0]), "Click me");
211    }
212
213    #[test]
214    fn test_render_to_layout() {
215        let layout = render_to_layout("<div><p>Hello</p></div>", "", 800, 600);
216        assert!(layout.dimensions.content.width > 0);
217    }
218
219    #[test]
220    fn test_render_to_display_list() {
221        let (layout, display_list) = render_to_display_list(
222            "<div>Hello</div>",
223            "div { background-color: red; height: 50px; }",
224            800,
225            600,
226        );
227        assert!(layout.dimensions.content.width > 0);
228        assert!(!display_list.commands.is_empty());
229    }
230
231    #[test]
232    fn test_parse_html_returns_document() {
233        let doc = parse_html("<html><body><p>Test</p></body></html>");
234        assert!(doc.node_count() > 1);
235    }
236
237    #[test]
238    fn test_region_contains_color() {
239        let mut pixels = alloc::vec![0xFFFFFFFF; 100];
240        pixels[55] = 0xFFFF0000; // Set one pixel red
241        let rect = PixelRect::new(0, 0, 10, 10);
242        assert!(region_contains_color(&pixels, 10, &rect, 0xFFFF0000));
243    }
244
245    #[test]
246    fn test_region_solid_color() {
247        let pixels = alloc::vec![0xFFFFFFFF; 100];
248        let rect = PixelRect::new(0, 0, 10, 10);
249        assert!(region_is_solid_color(&pixels, 10, &rect, 0xFFFFFFFF));
250    }
251
252    #[test]
253    fn test_region_not_solid() {
254        let mut pixels = alloc::vec![0xFFFFFFFF; 100];
255        pixels[0] = 0xFF000000;
256        let rect = PixelRect::new(0, 0, 10, 10);
257        assert!(!region_is_solid_color(&pixels, 10, &rect, 0xFFFFFFFF));
258    }
259
260    #[test]
261    fn test_count_unique_colors() {
262        let mut pixels = alloc::vec![0xFFFFFFFF; 100];
263        pixels[0] = 0xFF000000;
264        pixels[1] = 0xFFFF0000;
265        let rect = PixelRect::new(0, 0, 10, 10);
266        let count = count_unique_colors(&pixels, 10, &rect);
267        assert_eq!(count, 3);
268    }
269
270    #[test]
271    fn test_count_layout_boxes() {
272        let layout = render_to_layout("<div><p>A</p><p>B</p></div>", "", 800, 600);
273        let count = count_layout_boxes(&layout);
274        assert!(count >= 3, "Expected at least 3 boxes, got {}", count);
275    }
276
277    #[test]
278    fn test_render_background_visible() {
279        let pixels = render_html_with_css(
280            "<div>Content</div>",
281            "div { background-color: #0000ff; width: 200px; height: 100px; }",
282            400,
283            300,
284        );
285        let has_blue = pixels.iter().any(|&p| p == 0xFF0000FF);
286        assert!(has_blue, "Expected blue background pixels");
287    }
288
289    #[test]
290    fn test_render_multiple_backgrounds() {
291        let pixels = render_html_with_css(
292            "<div id='a'>A</div><div id='b'>B</div>",
293            "#a { background-color: #ff0000; height: 50px; } #b { background-color: #0000ff; \
294             height: 50px; }",
295            200,
296            200,
297        );
298        let has_red = pixels.iter().any(|&p| p == 0xFFFF0000);
299        let has_blue = pixels.iter().any(|&p| p == 0xFF0000FF);
300        assert!(has_red, "Expected red background");
301        assert!(has_blue, "Expected blue background");
302    }
303
304    #[test]
305    fn test_render_large_document() {
306        let mut html = String::from("<html><body>");
307        for i in 0..20 {
308            html.push_str(&alloc::format!("<p>Paragraph {}</p>", i));
309        }
310        html.push_str("</body></html>");
311        let pixels = render_html(&html, 800, 600);
312        assert_eq!(pixels.len(), 480000);
313    }
314
315    #[test]
316    fn test_full_pipeline_with_styles() {
317        let html = "<!DOCTYPE html><html><head><style>body { background-color: #eee; } h1 { \
318                    color: #333; } p { margin: 10px; \
319                    }</style></head><body><h1>Welcome</h1><p>This is a test \
320                    page.</p></body></html>";
321        let pixels = render_html(html, 800, 600);
322        assert_eq!(pixels.len(), 480000);
323    }
324}