H
horizon-browser
↓ 0 downloadsHorizon: 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-browserCode / 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.
}
}