灵符lingfu
H

horizon-browser

↓ 0 downloads

Horizon: a real box-model HTML/CSS layout engine with a Canvas 2D surface, no_std + alloc, zero dependencies -- LingOS's next-generation browser engine, sibling to bring-browser

Keywords browser, html, css, layout, canvas, no_std, lingos
lingfu install horizon-browser
LicenseMIT
AuthorsLing Lin <taellinglin@gmail.com>
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (9)

src/canvas.rs
//! `<canvas>`'s real 2D pixel surface. This is the actual "Canvas support"
//! the engine provides today: an addressable `Vec<u32>` framebuffer per
//! canvas element with a small drawing API. Nothing drives it from a page's
//! own `<script>` yet -- there is no JS engine (see the crate README) -- so
//! it's meant to be painted by native Rust code that already holds a
//! `&mut Page` (an embedder animating a game/chart/etc each frame), the same
//! way a `<canvas>` is painted by script in a real browser.
//!
//! Colors are `0x00RRGGBB`, matching `bring-browser` and LingOS's
//! `framebuffer` driver so an embedder can blit these pixels with the same
//! color convention it already uses.

use alloc::vec;
use alloc::vec::Vec;

pub struct CanvasSurface {
    pub w: u32,
    pub h: u32,
    pub pixels: Vec<u32>,
}

impl CanvasSurface {
    pub fn new(w: u32, h: u32) -> CanvasSurface {
        let w = w.max(1);
        let h = h.max(1);
        CanvasSurface { w, h, pixels: vec![0; (w * h) as usize] }
    }

    #[inline]
    fn idx(&self, x: u32, y: u32) -> Option<usize> {
        if x < self.w && y < self.h {
            Some((y * self.w + x) as usize)
        } else {
            None
        }
    }

    pub fn set_pixel(&mut self, x: u32, y: u32, color: u32) {
        if let Some(i) = self.idx(x, y) {
            self.pixels[i] = color;
        }
    }

    pub fn get_pixel(&self, x: u32, y: u32) -> Option<u32> {
        self.idx(x, y).map(|i| self.pixels[i])
    }

    pub fn clear(&mut self, color: u32) {
        self.pixels.fill(color);
    }

    pub fn fill_rect(&mut self, x: u32, y: u32, w: u32, h: u32, color: u32) {
        let x1 = (x + w).min(self.w);
        let y1 = (y + h).min(self.h);
        for py in y.min(self.h)..y1 {
            for px in x.min(self.w)..x1 {
                self.set_pixel(px, py, color);
            }
        }
    }

    pub fn stroke_rect(&mut self, x: u32, y: u32, w: u32, h: u32, color: u32) {
        if w == 0 || h == 0 {
            return;
        }
        self.fill_rect(x, y, w, 1, color);
        self.fill_rect(x, y + h - 1, w, 1, color);
        self.fill_rect(x, y, 1, h, color);
        self.fill_rect(x + w - 1, y, 1, h, color);
    }

    /// Bresenham line from (x0,y0) to (x1,y1).
    pub fn line(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: u32) {
        let (mut x0, mut y0) = (x0, y0);
        let dx = (x1 - x0).abs();
        let dy = -(y1 - y0).abs();
        let sx = if x0 < x1 { 1 } else { -1 };
        let sy = if y0 < y1 { 1 } else { -1 };
        let mut err = dx + dy;
        loop {
            if x0 >= 0 && y0 >= 0 {
                self.set_pixel(x0 as u32, y0 as u32, color);
            }
            if x0 == x1 && y0 == y1 {
                break;
            }
            let e2 = 2 * err;
            if e2 >= dy {
                err += dy;
                x0 += sx;
            }
            if e2 <= dx {
                err += dx;
                y0 += sy;
            }
        }
    }

    /// Filled circle via a midpoint scan, same approach as LingOS's
    /// `framebuffer::back_fill_circle` so behavior matches what an embedder
    /// already expects from that primitive.
    pub fn fill_circle(&mut self, cx: i32, cy: i32, radius: u32, color: u32) {
        let r = radius as i32;
        let mut x = r;
        let mut y = 0;
        let mut err = 0i32;
        while x >= y {
            self.fill_row(cx - x, cx + x, cy + y, color);
            self.fill_row(cx - x, cx + x, cy - y, color);
            self.fill_row(cx - y, cx + y, cy + x, color);
            self.fill_row(cx - y, cx + y, cy - x, color);
            y += 1;
            err += 1 + 2 * y;
            if 2 * (err - x) + 1 > 0 {
                x -= 1;
                err += 1 - 2 * x;
            }
        }
    }

    fn fill_row(&mut self, x0: i32, x1: i32, y: i32, color: u32) {
        if y < 0 || y as u32 >= self.h {
            return;
        }
        let x0 = x0.max(0) as u32;
        let x1 = (x1.max(0) as u32).min(self.w.saturating_sub(1));
        if x0 > x1 {
            return;
        }
        self.fill_rect(x0, y as u32, x1 - x0 + 1, 1, color);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn fill_rect_writes_exact_pixels() {
        let mut c = CanvasSurface::new(10, 10);
        c.fill_rect(2, 2, 3, 3, 0xff0000);
        assert_eq!(c.get_pixel(2, 2), Some(0xff0000));
        assert_eq!(c.get_pixel(4, 4), Some(0xff0000));
        assert_eq!(c.get_pixel(5, 5), Some(0));
        assert_eq!(c.get_pixel(1, 1), Some(0));
    }

    #[test]
    fn fill_rect_clips_to_bounds() {
        let mut c = CanvasSurface::new(4, 4);
        c.fill_rect(2, 2, 10, 10, 0x00ff00);
        assert_eq!(c.get_pixel(3, 3), Some(0x00ff00));
        assert_eq!(c.pixels.len(), 16);
    }

    #[test]
    fn stroke_rect_only_touches_border() {
        let mut c = CanvasSurface::new(5, 5);
        c.stroke_rect(1, 1, 3, 3, 0x123456);
        assert_eq!(c.get_pixel(2, 2), Some(0)); // center untouched
        assert_eq!(c.get_pixel(1, 1), Some(0x123456));
        assert_eq!(c.get_pixel(3, 3), Some(0x123456));
    }

    #[test]
    fn out_of_bounds_writes_are_ignored_not_panicking() {
        let mut c = CanvasSurface::new(2, 2);
        c.set_pixel(50, 50, 0xffffff);
        c.fill_circle(0, 0, 100, 0xffffff);
        c.line(-5, -5, 100, 100, 0xffffff);
        // Reaching here without panicking is the assertion.
    }
}

← Back to index