From beb9120b2b36c57cbd6e0ea6506b454624ef3897 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 27 Dec 2024 13:59:35 +0100 Subject: [PATCH] :tada: Basic strokes wasm support --- frontend/src/app/render_wasm/api.cljs | 70 ++++++- frontend/src/app/render_wasm/shape.cljs | 1 + render-wasm/src/main.rs | 127 ++++++++++++- render-wasm/src/shapes.rs | 40 +++- render-wasm/src/shapes/paths.rs | 16 ++ render-wasm/src/shapes/renderable.rs | 232 ++++++++++++++++++++---- render-wasm/src/shapes/strokes.rs | 129 +++++++++++++ 7 files changed, 577 insertions(+), 38 deletions(-) create mode 100644 render-wasm/src/shapes/strokes.rs diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 96e045d24..3882dc245 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -211,6 +211,70 @@ (store-image id)))))) fills)) +(defn set-shape-strokes + [strokes] + (h/call internal-module "_clear_shape_strokes") + (keep (fn [stroke] + (let [opacity (or (:stroke-opacity stroke) 1.0) + color (:stroke-color stroke) + gradient (:stroke-color-gradient stroke) + image (:stroke-image stroke) + width (:stroke-width stroke) + align (:stroke-alignment stroke)] + (case align + :inner (h/call internal-module "_add_shape_inner_stroke" width) + :outer (h/call internal-module "_add_shape_outer_stroke" width) + (h/call internal-module "_add_shape_center_stroke" width)) + + (cond + (some? gradient) + (let [stops (:stops gradient) + n-stops (count stops) + mem-size (* 5 n-stops) + stops-ptr (h/call internal-module "_alloc_bytes" mem-size) + heap (gobj/get ^js internal-module "HEAPU8") + mem (js/Uint8Array. (.-buffer heap) stops-ptr mem-size)] + (if (= (:type gradient) :linear) + (h/call internal-module "_add_shape_stroke_linear_fill" + (:start-x gradient) + (:start-y gradient) + (:end-x gradient) + (:end-y gradient) + opacity) + (h/call internal-module "_add_shape_stroke_radial_fill" + (:start-x gradient) + (:start-y gradient) + (:end-x gradient) + (:end-y gradient) + opacity + (:width gradient))) + (.set mem (js/Uint8Array. (clj->js (flatten (map (fn [stop] + (let [[r g b a] (rgba-bytes-from-hex (:color stop) (:opacity stop)) + offset (:offset stop)] + [r g b a (* 100 offset)])) + stops))))) + (h/call internal-module "_add_shape_stroke_stops" stops-ptr n-stops)) + + (some? image) + (let [id (dm/get-prop image :id) + buffer (uuid/get-u32 id) + cached-image? (h/call internal-module "_is_image_cached" (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))] + (h/call internal-module "_add_shape_image_stroke" + (aget buffer 0) + (aget buffer 1) + (aget buffer 2) + (aget buffer 3) + opacity + (dm/get-prop image :width) + (dm/get-prop image :height)) + (when (== cached-image? 0) + (store-image id))) + + (some? color) + (let [rgba (rgba-from-hex color opacity)] + (h/call internal-module "_add_shape_stroke_solid_fill" rgba))))) + strokes)) + (defn set-shape-path-content [content] (let [buffer (path/content->buffer content) @@ -280,6 +344,8 @@ transform (dm/get-prop shape :transform) fills (if (= type :group) [] (dm/get-prop shape :fills)) + strokes (if (= type :group) + [] (dm/get-prop shape :strokes)) children (dm/get-prop shape :shapes) blend-mode (dm/get-prop shape :blend-mode) opacity (dm/get-prop shape :opacity) @@ -297,8 +363,8 @@ (set-shape-opacity opacity) (set-shape-hidden hidden) (when (and (some? content) (= type :path)) (set-shape-path-content content)) - (let [pending-fills (doall (set-shape-fills fills))] - (recur (inc index) (into pending pending-fills)))) + (let [pending' (concat (set-shape-fills fills) (set-shape-strokes strokes))] + (recur (inc index) (into pending pending')))) pending))] (request-render) (when-let [pending (seq pending)] diff --git a/frontend/src/app/render_wasm/shape.cljs b/frontend/src/app/render_wasm/shape.cljs index 582c61103..9be274472 100644 --- a/frontend/src/app/render_wasm/shape.cljs +++ b/frontend/src/app/render_wasm/shape.cljs @@ -117,6 +117,7 @@ :rotation (api/set-shape-rotation v) :transform (api/set-shape-transform v) :fills (api/set-shape-fills v) + :strokes (api/set-shape-strokes v) :blend-mode (api/set-shape-blend-mode v) :opacity (api/set-shape-opacity v) :hidden (api/set-shape-hidden v) diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 32f516dd9..fbf52c665 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -245,7 +245,7 @@ pub extern "C" fn add_shape_fill_stops(ptr: *mut shapes::RawStopData, n_stops: u unsafe { let buffer = Vec::::from_raw_parts(ptr, len, len); shape - .add_gradient_stops(buffer) + .add_fill_gradient_stops(buffer) .expect("could not add gradient stops"); mem::free_bytes(); } @@ -346,6 +346,131 @@ pub extern "C" fn set_shape_path_content() { } } +#[no_mangle] +pub extern "C" fn add_shape_center_stroke(width: f32) { + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + shape.add_stroke(shapes::Stroke::new_center_stroke(width)) + } +} + +#[no_mangle] +pub extern "C" fn add_shape_inner_stroke(width: f32) { + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + shape.add_stroke(shapes::Stroke::new_inner_stroke(width)) + } +} + +#[no_mangle] +pub extern "C" fn add_shape_outer_stroke(width: f32) { + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + shape.add_stroke(shapes::Stroke::new_outer_stroke(width)) + } +} + +#[no_mangle] +pub extern "C" fn add_shape_stroke_solid_fill(raw_color: u32) { + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + let color = skia::Color::new(raw_color); + shape + .set_stroke_fill(shapes::Fill::Solid(color)) + .expect("could not add stroke solid fill"); + } +} + +#[no_mangle] +pub extern "C" fn add_shape_stroke_linear_fill( + start_x: f32, + start_y: f32, + end_x: f32, + end_y: f32, + opacity: f32, +) { + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + shape + .set_stroke_fill(shapes::Fill::new_linear_gradient( + (start_x, start_y), + (end_x, end_y), + opacity, + )) + .expect("could not add stroke linear fill"); + } +} + +#[no_mangle] +pub extern "C" fn add_shape_stroke_radial_fill( + start_x: f32, + start_y: f32, + end_x: f32, + end_y: f32, + opacity: f32, + width: f32, +) { + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + shape + .set_stroke_fill(shapes::Fill::new_radial_gradient( + (start_x, start_y), + (end_x, end_y), + opacity, + width, + )) + .expect("could not add stroke radial fill"); + } +} + +#[no_mangle] +pub extern "C" fn add_shape_stroke_stops(ptr: *mut shapes::RawStopData, n_stops: u32) { + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + + if let Some(shape) = state.current_shape() { + let len = n_stops as usize; + + unsafe { + let buffer = Vec::::from_raw_parts(ptr, len, len); + shape + .add_stroke_gradient_stops(buffer) + .expect("could not add gradient stops"); + mem::free_bytes(); + } + } +} + +#[no_mangle] +pub extern "C" fn add_shape_image_stroke( + a: u32, + b: u32, + c: u32, + d: u32, + alpha: f32, + width: i32, + height: i32, +) { + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + let id = uuid_from_u32_quartet(a, b, c, d); + if let Some(shape) = state.current_shape() { + shape + .set_stroke_fill(shapes::Fill::new_image_fill( + id, + (alpha * 0xff as f32).floor() as u8, + (width, height), + )) + .expect("could not add stroke image fill"); + } +} + +#[no_mangle] +pub extern "C" fn clear_shape_strokes() { + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + shape.clear_strokes(); + } +} + fn main() { init_gl(); } diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 1b0cb4529..4943d8e64 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -9,11 +9,13 @@ mod images; mod matrix; mod paths; mod renderable; +mod strokes; pub use fills::*; pub use images::*; use matrix::*; pub use paths::*; +pub use strokes::*; #[derive(Debug, Clone, PartialEq)] pub enum Kind { @@ -35,6 +37,7 @@ pub struct Shape { rotation: f32, clip_content: bool, fills: Vec, + strokes: Vec, blend_mode: BlendMode, opacity: f32, hidden: bool, @@ -51,6 +54,7 @@ impl Shape { rotation: 0., clip_content: true, fills: vec![], + strokes: vec![], blend_mode: BlendMode::default(), opacity: 1., hidden: false, @@ -114,7 +118,7 @@ impl Shape { self.fills.clear(); } - pub fn add_gradient_stops(&mut self, buffer: Vec) -> Result<(), String> { + pub fn add_fill_gradient_stops(&mut self, buffer: Vec) -> Result<(), String> { let fill = self.fills.last_mut().ok_or("Shape has no fills")?; let gradient = match fill { Fill::LinearGradient(g) => Ok(g), @@ -129,6 +133,40 @@ impl Shape { Ok(()) } + pub fn strokes(&self) -> std::slice::Iter { + self.strokes.iter() + } + + pub fn add_stroke(&mut self, s: Stroke) { + self.strokes.push(s) + } + + pub fn set_stroke_fill(&mut self, f: Fill) -> Result<(), String> { + let stroke = self.strokes.last_mut().ok_or("Shape has no strokes")?; + stroke.fill = f; + Ok(()) + } + + pub fn add_stroke_gradient_stops(&mut self, buffer: Vec) -> Result<(), String> { + let stroke = self.strokes.last_mut().ok_or("Shape has no strokes")?; + let fill = &mut stroke.fill; + let gradient = match fill { + Fill::LinearGradient(g) => Ok(g), + Fill::RadialGradient(g) => Ok(g), + _ => Err("Active stroke is not a gradient"), + }?; + + for stop in buffer.into_iter() { + gradient.add_stop(stop.color(), stop.offset()); + } + + Ok(()) + } + + pub fn clear_strokes(&mut self) { + self.strokes.clear(); + } + pub fn set_path_segments(&mut self, buffer: Vec) -> Result<(), String> { let p = Path::try_from(buffer)?; self.kind = Kind::Path(p); diff --git a/render-wasm/src/shapes/paths.rs b/render-wasm/src/shapes/paths.rs index ae2367fb7..f4528bc4a 100644 --- a/render-wasm/src/shapes/paths.rs +++ b/render-wasm/src/shapes/paths.rs @@ -75,6 +75,18 @@ pub struct Path { skia_path: skia::Path, } +fn starts_and_ends_at_same_point(path: &skia::Path) -> bool { + if path.count_points() < 2 { + return false; // A path with fewer than 2 points cannot be closed + } + + let start_point = path.get_point(0); // First point of the path + let end_point = path.get_point(path.count_points() - 1); // Last point of the path + + // Compare the start and end points + start_point == end_point +} + impl TryFrom> for Path { type Error = String; @@ -102,6 +114,10 @@ impl TryFrom> for Path { } } + if !skia_path.is_last_contour_closed() && starts_and_ends_at_same_point(&skia_path) { + skia_path.close(); + } + Ok(Path { segments, skia_path, diff --git a/render-wasm/src/shapes/renderable.rs b/render-wasm/src/shapes/renderable.rs index 907489593..5f4ad4fc8 100644 --- a/render-wasm/src/shapes/renderable.rs +++ b/render-wasm/src/shapes/renderable.rs @@ -1,7 +1,7 @@ use skia_safe as skia; use uuid::Uuid; -use super::{Fill, Image, Kind, Shape}; +use super::{Fill, Image, Kind, Path, Shape, Stroke, StrokeKind}; use crate::math::Rect; use crate::render::{ImageStore, Renderable}; @@ -29,9 +29,16 @@ impl Renderable for Shape { ); } - let mut paint = skia::Paint::default(); - paint.set_blend_mode(self.blend_mode.into()); - paint.set_alpha_f(self.opacity); + for stroke in self.strokes().rev() { + render_stroke( + surface, + images, + stroke, + self.selrect, + &self.kind, + self.to_path_transform().as_ref(), + ); + } Ok(()) } @@ -73,7 +80,7 @@ fn render_fill( (Fill::Image(image_fill), kind) => { let image = images.get(&image_fill.id()); if let Some(image) = image { - draw_image_in_container( + draw_image_fill_in_container( surface.canvas(), &image, image_fill.size(), @@ -99,7 +106,124 @@ fn render_fill( } } -pub fn draw_image_in_container( +fn render_stroke( + surface: &mut skia::Surface, + images: &ImageStore, + stroke: &Stroke, + selrect: Rect, + kind: &Kind, + path_transform: Option<&skia::Matrix>, +) { + if let Fill::Image(image_fill) = &stroke.fill { + if let Some(image) = images.get(&image_fill.id()) { + draw_image_stroke_in_container( + surface.canvas(), + &image, + stroke, + image_fill.size(), + kind, + &selrect, + path_transform, + ); + } + } else { + match kind { + Kind::Rect(rect) => draw_stroke_on_rect(surface.canvas(), stroke, rect, &selrect), + Kind::Circle(rect) => draw_stroke_on_circle(surface.canvas(), stroke, rect, &selrect), + Kind::Path(path) => { + draw_stroke_on_path(surface.canvas(), stroke, path, &selrect, path_transform) + } + } + } +} + +fn draw_stroke_on_rect(canvas: &skia::Canvas, stroke: &Stroke, rect: &Rect, selrect: &Rect) { + // Draw the different kind of strokes for a rect is perry straightforward, we just need apply a stroke to: + // - The same rect if it's a center stroke + // - A bigger rect if it's an outer stroke + // - A smaller rect if it's an outer stroke + let stroke_rect = stroke.outer_rect(rect); + canvas.draw_rect(&stroke_rect, &stroke.to_paint(selrect)); +} + +fn draw_stroke_on_circle(canvas: &skia::Canvas, stroke: &Stroke, rect: &Rect, selrect: &Rect) { + // Draw the different kind of strokes for an oval is perry straightforward, we just need apply a stroke to: + // - The same oval if it's a center stroke + // - A bigger oval if it's an outer stroke + // - A smaller oval if it's an outer stroke + let stroke_rect = stroke.outer_rect(rect); + canvas.draw_oval(&stroke_rect, &stroke.to_paint(selrect)); +} + +fn draw_stroke_on_path( + canvas: &skia::Canvas, + stroke: &Stroke, + path: &Path, + selrect: &Rect, + path_transform: Option<&skia::Matrix>, +) { + let mut skia_path = path.to_skia_path(); + skia_path.transform(path_transform.unwrap()); + + let paint_stroke = stroke.to_stroked_paint(selrect); + // Draw the different kind of strokes for a path requires different strategies: + match stroke.kind { + // For inner stroke we draw a center stroke (with double width) and clip to the original path (that way the extra outer stroke is removed) + StrokeKind::InnerStroke => { + canvas.clip_path(&skia_path, skia::ClipOp::Intersect, true); + canvas.draw_path(&skia_path, &paint_stroke); + } + // For center stroke we don't need to do anything extra + StrokeKind::CenterStroke => { + canvas.draw_path(&skia_path, &paint_stroke); + } + // For outer stroke we draw a center stroke (with double width) and use another path with blend mode clear to remove the inner stroke added + StrokeKind::OuterStroke => { + let mut paint = skia::Paint::default(); + paint.set_blend_mode(skia::BlendMode::SrcOver); + paint.set_anti_alias(true); + let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint); + canvas.save_layer(&layer_rec); + + canvas.draw_path(&skia_path, &paint_stroke); + + let mut clear_paint = skia::Paint::default(); + clear_paint.set_blend_mode(skia::BlendMode::Clear); + clear_paint.set_anti_alias(true); + canvas.draw_path(&skia_path, &clear_paint); + + canvas.restore(); + } + } +} + +fn calculate_scaled_rect(size: (i32, i32), container: &Rect, delta: f32) -> Rect { + let (width, height) = (size.0 as f32, size.1 as f32); + let image_aspect_ratio = width / height; + + // Container size + let container_width = container.width(); + let container_height = container.height(); + let container_aspect_ratio = container_width / container_height; + + let scale = if image_aspect_ratio > container_aspect_ratio { + container_height / height + } else { + container_width / width + }; + + let scaled_width = width * scale; + let scaled_height = height * scale; + + Rect::from_xywh( + container.left - delta - (scaled_width - container_width) / 2.0, + container.top - delta - (scaled_height - container_height) / 2.0, + scaled_width + (2. * delta) + (scaled_width - container_width), + scaled_height + (2. * delta) + (scaled_width - container_width), + ) +} + +pub fn draw_image_fill_in_container( canvas: &skia::Canvas, image: &Image, size: (i32, i32), @@ -108,34 +232,8 @@ pub fn draw_image_in_container( container: &Rect, path_transform: Option<&skia::Matrix>, ) { - let width = size.0 as f32; - let height = size.1 as f32; - let image_aspect_ratio = width / height; - - // Container size - let container_width = container.width(); - let container_height = container.height(); - let container_aspect_ratio = container_width / container_height; - - // Calculate scale to ensure the image covers the container - let scale = if image_aspect_ratio > container_aspect_ratio { - // Image is wider, scale based on height to cover container - container_height / height - } else { - // Image is taller, scale based on width to cover container - container_width / width - }; - - // Scaled size of the image - let scaled_width = width * scale; - let scaled_height = height * scale; - - let dest_rect = Rect::from_xywh( - container.left - (scaled_width - container_width) / 2.0, - container.top - (scaled_height - container_height) / 2.0, - scaled_width, - scaled_height, - ); + // Compute scaled rect + let dest_rect = calculate_scaled_rect(size, container, 0.); // Save the current canvas state canvas.save(); @@ -165,3 +263,69 @@ pub fn draw_image_in_container( // Restore the canvas to remove the clipping canvas.restore(); } + +pub fn draw_image_stroke_in_container( + canvas: &skia::Canvas, + image: &Image, + stroke: &Stroke, + size: (i32, i32), + kind: &Kind, + container: &Rect, + path_transform: Option<&skia::Matrix>, +) { + // Helper to handle drawing based on kind + fn draw_kind( + canvas: &skia::Canvas, + kind: &Kind, + stroke: &Stroke, + container: &Rect, + path_transform: Option<&skia::Matrix>, + ) { + let outer_rect = stroke.outer_rect(container); + match kind { + Kind::Rect(rect) => draw_stroke_on_rect(canvas, stroke, rect, &outer_rect), + Kind::Circle(rect) => draw_stroke_on_circle(canvas, stroke, rect, &outer_rect), + Kind::Path(p) => { + let mut path = p.to_skia_path(); + path.transform(path_transform.unwrap()); + if stroke.kind == StrokeKind::InnerStroke { + canvas.clip_path(&path, skia::ClipOp::Intersect, true); + } + let paint = stroke.to_stroked_paint(&outer_rect); + canvas.draw_path(&path, &paint); + } + } + } + + // Save canvas and layer state + let mut pb = skia::Paint::default(); + pb.set_blend_mode(skia::BlendMode::SrcOver); + pb.set_anti_alias(true); + let layer_rec = skia::canvas::SaveLayerRec::default().paint(&pb); + canvas.save_layer(&layer_rec); + + // Draw the stroke based on the kind, we are using this stroke as a "selector" of the area of the image we want to show. + draw_kind(canvas, kind, stroke, container, path_transform); + + // Draw the image. We are using now the SrcIn blend mode, so the rendered piece of image will the area of the stroke over the image. + let mut image_paint = skia::Paint::default(); + image_paint.set_blend_mode(skia::BlendMode::SrcIn); + image_paint.set_anti_alias(true); + // Compute scaled rect and clip to it + let dest_rect = calculate_scaled_rect(size, container, stroke.delta()); + canvas.clip_rect(dest_rect, skia::ClipOp::Intersect, true); + canvas.draw_image_rect(image, None, dest_rect, &image_paint); + + // Clear outer stroke for paths if necessary. When adding an outer stroke we need to empty the stroke added too in the inner area. + if let (Kind::Path(p), StrokeKind::OuterStroke) = (kind, &stroke.kind) { + let mut path = p.to_skia_path(); + path.transform(path_transform.unwrap()); + let mut clear_paint = skia::Paint::default(); + clear_paint.set_blend_mode(skia::BlendMode::Clear); + clear_paint.set_anti_alias(true); + canvas.draw_path(&path, &clear_paint); + } + + // Restore canvas state + canvas.restore(); +} diff --git a/render-wasm/src/shapes/strokes.rs b/render-wasm/src/shapes/strokes.rs new file mode 100644 index 000000000..a4bef5a72 --- /dev/null +++ b/render-wasm/src/shapes/strokes.rs @@ -0,0 +1,129 @@ +use crate::math; +use crate::shapes::fills::Fill; +use skia_safe as skia; + +#[derive(Debug, Clone, PartialEq)] +pub enum StrokeStyle { + Solid, + // Dotted, + // Dashed, + // Mixed, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum StrokeCap { + None, + // Line, + // Triangle, + // Circle, + // Diamond, + // Round, + // Square, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum StrokeKind { + InnerStroke, + OuterStroke, + CenterStroke, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Stroke { + pub fill: Fill, + pub width: f32, + pub style: StrokeStyle, + pub cap_end: StrokeCap, + pub cap_start: StrokeCap, + pub kind: StrokeKind, +} + +impl Stroke { + pub fn new_center_stroke(width: f32) -> Self { + let transparent = skia::Color::from_argb(0, 0, 0, 0); + Stroke { + fill: Fill::Solid(transparent), + width: width, + style: StrokeStyle::Solid, + cap_end: StrokeCap::None, + cap_start: StrokeCap::None, + kind: StrokeKind::CenterStroke, + } + } + + pub fn new_inner_stroke(width: f32) -> Self { + let transparent = skia::Color::from_argb(0, 0, 0, 0); + Stroke { + fill: Fill::Solid(transparent), + width: width, + style: StrokeStyle::Solid, + cap_end: StrokeCap::None, + cap_start: StrokeCap::None, + kind: StrokeKind::InnerStroke, + } + } + + pub fn new_outer_stroke(width: f32) -> Self { + let transparent = skia::Color::from_argb(0, 0, 0, 0); + Stroke { + fill: Fill::Solid(transparent), + width: width, + style: StrokeStyle::Solid, + cap_end: StrokeCap::None, + cap_start: StrokeCap::None, + kind: StrokeKind::OuterStroke, + } + } + + pub fn delta(&self) -> f32 { + match self.kind { + StrokeKind::InnerStroke => 0., + StrokeKind::CenterStroke => self.width / 2., + StrokeKind::OuterStroke => self.width, + } + } + + pub fn outer_rect(&self, rect: &math::Rect) -> math::Rect { + match self.kind { + StrokeKind::InnerStroke => math::Rect::from_xywh( + rect.left + (self.width / 2.), + rect.top + (self.width / 2.), + rect.width() - self.width, + rect.height() - self.width, + ), + StrokeKind::CenterStroke => { + math::Rect::from_xywh(rect.left, rect.top, rect.width(), rect.height()) + } + StrokeKind::OuterStroke => math::Rect::from_xywh( + rect.left - (self.width / 2.), + rect.top - (self.width / 2.), + rect.width() + self.width, + rect.height() + self.width, + ), + } + } + + pub fn to_paint(&self, rect: &math::Rect) -> skia::Paint { + let mut paint = self.fill.to_paint(rect); + paint.set_style(skia::PaintStyle::Stroke); + paint.set_stroke_width(self.width); + paint.set_anti_alias(true); + paint + } + + pub fn to_stroked_paint(&self, rect: &math::Rect) -> skia::Paint { + let mut paint = self.to_paint(rect); + match self.kind { + StrokeKind::InnerStroke => { + paint.set_stroke_width(2. * self.width); + paint + } + + StrokeKind::CenterStroke => paint, + StrokeKind::OuterStroke => { + paint.set_stroke_width(2. * self.width); + paint + } + } + } +}