diff --git a/crates/haku/src/render.rs b/crates/haku/src/render.rs index 6232863..6a43e43 100644 --- a/crates/haku/src/render.rs +++ b/crates/haku/src/render.rs @@ -1,11 +1,11 @@ use alloc::vec::Vec; use tiny_skia::{ - BlendMode, Color, LineCap, Paint, PathBuilder, Pixmap, Rect, Shader, Stroke as SStroke, - Transform, + BlendMode, Color, FillRule, LineCap, Paint, Path, PathBuilder, Pixmap, Rect, Shader, + Stroke as SStroke, Transform, }; use crate::{ - value::{Ref, Rgba, Scribble, Shape, Stroke, Value}, + value::{Fill, Ref, Rgba, Scribble, Shape, Stroke, Value}, vm::{Exception, Vm}, }; @@ -81,6 +81,7 @@ impl<'a> Renderer<'a> { } Ref::Scribble(scribble) => match scribble { Scribble::Stroke(stroke) => self.render_stroke(vm, value, stroke)?, + Scribble::Fill(fill) => self.render_fill(vm, value, fill)?, }, _ => return Err(Self::create_exception(vm, value, NOT_A_SCRIBBLE))?, } @@ -88,92 +89,64 @@ impl<'a> Renderer<'a> { Ok(()) } + fn shape_to_path(shape: &Shape) -> Path { + let mut pb = PathBuilder::new(); + match shape { + Shape::Point(vec) => { + pb.move_to(vec.x, vec.y); + pb.line_to(vec.x, vec.y); + } + Shape::Line(start, end) => { + pb.move_to(start.x, start.y); + pb.line_to(end.x, end.y); + } + Shape::Rect(position, size) => { + if let Some(rect) = + tiny_skia::Rect::from_xywh(position.x, position.y, size.x, size.y) + { + pb.push_rect(rect); + } + } + Shape::Circle(position, radius) => { + pb.push_circle(position.x, position.y, *radius); + } + } + pb.finish().unwrap() + } + fn render_stroke(&mut self, _vm: &Vm, _value: Value, stroke: &Stroke) -> Result<(), Exception> { let paint = Paint { shader: Shader::SolidColor(tiny_skia_color(stroke.color)), ..default_paint() }; let transform = self.transform(); + let path = Self::shape_to_path(&stroke.shape); - match stroke.shape { - Shape::Point(vec) => { - let mut pb = PathBuilder::new(); - pb.move_to(vec.x, vec.y); - pb.line_to(vec.x, vec.y); - let path = pb.finish().unwrap(); + self.pixmap_mut().stroke_path( + &path, + &paint, + &SStroke { + width: stroke.thickness, + line_cap: LineCap::Square, + ..Default::default() + }, + transform, + None, + ); - self.pixmap_mut().stroke_path( - &path, - &paint, - &SStroke { - width: stroke.thickness, - line_cap: LineCap::Square, - ..Default::default() - }, - transform, - None, - ); - } + Ok(()) + } - Shape::Line(start, end) => { - let mut pb = PathBuilder::new(); - pb.move_to(start.x, start.y); - pb.line_to(end.x, end.y); - let path = pb.finish().unwrap(); + fn render_fill(&mut self, _vm: &Vm, _value: Value, fill: &Fill) -> Result<(), Exception> { + let paint = Paint { + shader: Shader::SolidColor(tiny_skia_color(fill.color)), + ..default_paint() + }; + let transform = self.transform(); + let path = Self::shape_to_path(&fill.shape); - self.pixmap_mut().stroke_path( - &path, - &paint, - &SStroke { - width: stroke.thickness, - line_cap: LineCap::Square, - ..Default::default() - }, - transform, - None, - ); - } - - Shape::Rect(position, size) => { - let mut pb = PathBuilder::new(); - if let Some(rect) = - tiny_skia::Rect::from_xywh(position.x, position.y, size.x, size.y) - { - pb.push_rect(rect); - } - let path = pb.finish().unwrap(); - - self.pixmap_mut().stroke_path( - &path, - &paint, - &SStroke { - width: stroke.thickness, - line_cap: LineCap::Square, - ..Default::default() - }, - transform, - None, - ); - } - - Shape::Circle(position, radius) => { - let mut pb = PathBuilder::new(); - pb.push_circle(position.x, position.y, radius); - let path = pb.finish().unwrap(); - - self.pixmap_mut().stroke_path( - &path, - &paint, - &SStroke { - width: stroke.thickness, - line_cap: LineCap::Square, - ..Default::default() - }, - transform, - None, - ); - } - } + self.pixmap_mut() + .fill_path(&path, &paint, FillRule::EvenOdd, transform, None); Ok(()) } diff --git a/crates/haku/src/system.rs b/crates/haku/src/system.rs index 159ead0..ef5686e 100644 --- a/crates/haku/src/system.rs +++ b/crates/haku/src/system.rs @@ -102,7 +102,7 @@ pub mod fns { use alloc::vec::Vec; use crate::{ - value::{List, Ref, Rgba, Scribble, Shape, Stroke, Value, Vec2, Vec4}, + value::{Fill, List, Ref, Rgba, Scribble, Shape, Stroke, Value, Vec2, Vec4}, vm::{Exception, FnArgs, Vm}, }; @@ -142,6 +142,7 @@ pub mod fns { 0xc2 "rect" => rect, 0xc3 "circle" => circle, 0xe0 "stroke" => stroke, + 0xe1 "fill" => fill, } } @@ -509,4 +510,18 @@ pub mod fns { Ok(Value::Nil) } } + + pub fn fill(vm: &mut Vm, args: FnArgs) -> Result { + if args.num() != 2 { + return Err(vm.create_exception("(fill) expects 2 arguments (fill color shape)")); + } + + let color = args.get_rgba(vm, 0, "1st argument to (fill) must be a color (rgba)")?; + if let Some(shape) = to_shape(args.get(vm, 1), vm) { + let id = vm.create_ref(Ref::Scribble(Scribble::Fill(Fill { color, shape })))?; + Ok(Value::Ref(id)) + } else { + Ok(Value::Nil) + } + } } diff --git a/crates/haku/src/value.rs b/crates/haku/src/value.rs index 36761ee..97b8113 100644 --- a/crates/haku/src/value.rs +++ b/crates/haku/src/value.rs @@ -179,7 +179,14 @@ pub struct Stroke { pub shape: Shape, } +#[derive(Debug, Clone)] +pub struct Fill { + pub color: Rgba, + pub shape: Shape, +} + #[derive(Debug, Clone)] pub enum Scribble { Stroke(Stroke), + Fill(Fill), }