🎉 Basic wasm support for svg attrs and svg defs

This commit is contained in:
Alejandro Alonso 2024-12-26 13:16:37 +01:00 committed by AzazelN28
parent 751df46dc9
commit 79df616108
12 changed files with 452 additions and 60 deletions

View file

@ -361,6 +361,21 @@ pub extern "C" fn clear_shape_fills() {
}
}
#[no_mangle]
pub extern "C" fn set_shape_svg_raw_content() {
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
if let Some(shape) = state.current_shape() {
let bytes = mem::bytes();
let svg_raw_content = String::from_utf8(bytes)
.unwrap()
.trim_end_matches('\0')
.to_string();
shape
.set_svg_raw_content(svg_raw_content)
.expect("Failed to set svg raw content");
}
}
#[no_mangle]
pub extern "C" fn set_shape_blend_mode(mode: i32) {
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
@ -509,6 +524,24 @@ pub extern "C" fn add_shape_stroke_stops(ptr: *mut shapes::RawStopData, n_stops:
}
}
// Extracts a string from the bytes slice until the next null byte (0) and returns the result as a `String`.
// Updates the `start` index to the end of the extracted string.
fn extract_string(start: &mut usize, bytes: &[u8]) -> String {
match bytes[*start..].iter().position(|&b| b == 0) {
Some(pos) => {
let end = *start + pos;
let slice = &bytes[*start..end];
*start = end + 1; // Move the `start` pointer past the null byte
// Call to unsafe function within an unsafe block
unsafe { String::from_utf8_unchecked(slice.to_vec()) }
}
None => {
*start = bytes.len(); // Move `start` to the end if no null byte is found
String::new()
}
}
}
#[no_mangle]
pub extern "C" fn add_shape_image_stroke(
a: u32,
@ -544,7 +577,22 @@ pub extern "C" fn clear_shape_strokes() {
pub extern "C" fn set_shape_corners(r1: f32, r2: f32, r3: f32, r4: f32) {
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
if let Some(shape) = state.current_shape() {
shape.set_corners((r1, r2, r3, r4))
shape.set_corners((r1, r2, r3, r4));
}
}
#[no_mangle]
pub extern "C" fn set_shape_path_attrs(num_attrs: u32) {
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
if let Some(shape) = state.current_shape() {
let bytes = mem::bytes();
let mut start = 0;
for _ in 0..num_attrs {
let name = extract_string(&mut start, &bytes);
let value = extract_string(&mut start, &bytes);
shape.set_path_attr(name, value);
}
}
}

View file

@ -1,7 +1,6 @@
use std::collections::HashMap;
use skia::Contains;
use skia_safe as skia;
use std::collections::HashMap;
use uuid::Uuid;
use crate::math;
@ -33,6 +32,7 @@ pub trait Renderable {
fn clip(&self) -> bool;
fn children_ids(&self) -> Vec<Uuid>;
fn image_filter(&self, scale: f32) -> Option<skia::ImageFilter>;
fn is_recursive(&self) -> bool;
}
pub(crate) struct CachedSurfaceImage {
@ -191,6 +191,7 @@ impl RenderState {
skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest),
Some(&skia::Paint::default()),
);
self.drawing_surface
.canvas()
.clear(skia::Color::TRANSPARENT);
@ -336,8 +337,8 @@ impl RenderState {
// Returns a boolean indicating if the viewbox contains the rendered shapes
fn render_shape_tree(&mut self, root_id: &Uuid, tree: &HashMap<Uuid, impl Renderable>) -> bool {
let element = tree.get(&root_id).unwrap();
let mut is_complete = self.viewbox.area.contains(element.bounds());
if let Some(element) = tree.get(&root_id) {
let mut is_complete = self.viewbox.area.contains(element.bounds());
if !root_id.is_nil() {
if !element.bounds().intersects(self.viewbox.area) || element.hidden() {
@ -348,40 +349,45 @@ impl RenderState {
} else {
self.render_debug_element(element, true);
}
}
let mut paint = skia::Paint::default();
paint.set_blend_mode(element.blend_mode().into());
paint.set_alpha_f(element.opacity());
let filter = element.image_filter(self.viewbox.zoom * self.options.dpr());
if let Some(image_filter) = filter {
paint.set_image_filter(image_filter);
}
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
// This is needed so the next non-children shape does not carry this shape's transform
self.final_surface.canvas().save_layer(&layer_rec);
self.drawing_surface.canvas().save();
if !root_id.is_nil() {
self.render_single_element(element);
if element.clip() {
self.drawing_surface.canvas().clip_rect(
element.bounds(),
skia::ClipOp::Intersect,
true,
);
let mut paint = skia::Paint::default();
paint.set_blend_mode(element.blend_mode().into());
paint.set_alpha_f(element.opacity());
let filter = element.image_filter(self.viewbox.zoom * self.options.dpr());
if let Some(image_filter) = filter {
paint.set_image_filter(image_filter);
}
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
// This is needed so the next non-children shape does not carry this shape's transform
self.final_surface.canvas().save_layer(&layer_rec);
self.drawing_surface.canvas().save();
if !root_id.is_nil() {
self.render_single_element(element);
if element.clip() {
self.drawing_surface.canvas().clip_rect(
element.bounds(),
skia::ClipOp::Intersect,
true,
);
}
}
// draw all the children shapes
if element.is_recursive() {
for id in element.children_ids() {
is_complete = self.render_shape_tree(&id, tree) && is_complete;
}
}
self.final_surface.canvas().restore();
self.drawing_surface.canvas().restore();
return is_complete;
} else {
eprintln!("Error: Element with root_id {root_id} not found in the tree.");
return false;
}
// draw all the children shapes
for id in element.children_ids() {
is_complete = self.render_shape_tree(&id, tree) && is_complete;
}
self.final_surface.canvas().restore();
self.drawing_surface.canvas().restore();
return is_complete;
}
}

View file

@ -1,5 +1,6 @@
use crate::math;
use skia_safe as skia;
use std::collections::HashMap;
use uuid::Uuid;
use crate::render::{BlendMode, Renderable};
@ -12,6 +13,7 @@ mod matrix;
mod paths;
mod renderable;
mod strokes;
mod svgraw;
pub use blurs::*;
pub use bools::*;
@ -20,6 +22,7 @@ pub use images::*;
use matrix::*;
pub use paths::*;
pub use strokes::*;
pub use svgraw::*;
pub type CornerRadius = skia::Point;
pub type Corners = [CornerRadius; 4];
@ -30,6 +33,7 @@ pub enum Kind {
Circle(math::Rect),
Path(Path),
Bool(BoolType, Path),
SVGRaw(SVGRaw),
}
pub type Color = skia::Color;
@ -50,6 +54,7 @@ pub struct Shape {
blur: Blur,
opacity: f32,
hidden: bool,
svg_attrs: HashMap<String, String>,
}
impl Shape {
@ -68,6 +73,7 @@ impl Shape {
opacity: 1.,
hidden: false,
blur: Blur::default(),
svg_attrs: HashMap::new(),
}
}
@ -196,6 +202,20 @@ impl Shape {
Ok(())
}
pub fn set_path_attr(&mut self, name: String, value: String) {
match &mut self.kind {
Kind::Path(_) => {
self.set_svg_attr(name, value);
}
Kind::Rect(_) | Kind::Circle(_) | Kind::SVGRaw(_) => todo!(),
};
}
pub fn set_svg_raw_content(&mut self, content: String) -> Result<(), String> {
self.kind = Kind::SVGRaw(SVGRaw::from_content(content));
Ok(())
}
pub fn set_blend_mode(&mut self, mode: BlendMode) {
self.blend_mode = mode;
}
@ -230,6 +250,10 @@ impl Shape {
self.kind = Kind::Rect(self.selrect, corners);
}
pub fn set_svg_attr(&mut self, name: String, value: String) {
self.svg_attrs.insert(name, value);
}
fn to_path_transform(&self) -> Option<skia::Matrix> {
match self.kind {
Kind::Path(_) | Kind::Bool(_, _) => {

View file

@ -1,4 +1,5 @@
use skia_safe::{self as skia, RRect};
use std::collections::HashMap;
use uuid::Uuid;
use super::{BlurType, Corners, Fill, Image, Kind, Path, Shape, Stroke, StrokeCap, StrokeKind};
@ -93,22 +94,68 @@ impl Renderable for Shape {
None
}
}
fn is_recursive(&self) -> bool {
!matches!(self.kind, Kind::SVGRaw(_))
}
}
fn render_fills_for_kind(
shape: &Shape,
canvas: &skia::Canvas,
images: &ImageStore,
path_transform: Option<&skia::Matrix>,
svg_attrs: &HashMap<String, String>,
) {
for fill in shape.fills().rev() {
render_fill(
canvas,
images,
fill,
shape.selrect,
&shape.kind,
path_transform,
svg_attrs,
);
}
//TODO: remove when strokes are implemented, this is just for testing paths with no fills
if shape.fills().len() == 0 {
if let Kind::Path(ref path) = shape.kind {
let mut p = skia::Paint::default();
p.set_style(skia_safe::PaintStyle::Stroke);
p.set_stroke_width(2.0);
p.set_anti_alias(true);
p.set_blend_mode(skia::BlendMode::SrcOver);
if let Some("round") = svg_attrs.get("stroke-linecap").map(String::as_str) {
p.set_stroke_cap(skia::paint::Cap::Round);
}
if let Some("round") = svg_attrs.get("stroke-linejoin").map(String::as_str) {
p.set_stroke_join(skia::paint::Join::Round);
}
let mut skia_path = &mut path.to_skia_path();
skia_path = skia_path.transform(path_transform.unwrap());
canvas.draw_path(&skia_path, &p);
}
}
}
fn render_fill(
surface: &mut skia::Surface,
canvas: &skia::Canvas,
images: &ImageStore,
fill: &Fill,
selrect: Rect,
kind: &Kind,
path_transform: Option<&skia::Matrix>,
svg_attrs: &HashMap<String, String>,
) {
match (fill, kind) {
(Fill::Image(image_fill), kind) => {
let image = images.get(&image_fill.id());
if let Some(image) = image {
draw_image_fill_in_container(
surface.canvas(),
canvas,
&image,
image_fill.size(),
kind,
@ -119,20 +166,26 @@ fn render_fill(
}
}
(_, Kind::Rect(rect, None)) => {
surface.canvas().draw_rect(rect, &fill.to_paint(&selrect));
canvas.draw_rect(rect, &fill.to_paint(&selrect));
}
(_, Kind::Rect(rect, Some(corners))) => {
let rrect = RRect::new_rect_radii(rect, corners);
surface.canvas().draw_rrect(rrect, &fill.to_paint(&selrect));
canvas.draw_rrect(rrect, &fill.to_paint(&selrect));
}
(_, Kind::Circle(rect)) => {
surface.canvas().draw_oval(rect, &fill.to_paint(&selrect));
canvas.draw_oval(rect, &fill.to_paint(&selrect));
}
(_, Kind::Path(path)) | (_, Kind::Bool(_, path)) => {
surface.canvas().draw_path(
&path.to_skia_path().transform(path_transform.unwrap()),
&fill.to_paint(&selrect),
);
let mut skia_path = &mut path.to_skia_path();
skia_path = skia_path.transform(path_transform.unwrap());
if let Some("evenodd") = svg_attrs.get("fill-rule").map(String::as_str) {
skia_path.set_fill_type(skia::PathFillType::EvenOdd);
}
canvas.draw_path(&skia_path, &fill.to_paint(&selrect));
}
(_, Kind::SVGRaw(_sr)) => {
// NOOP
}
}
}
@ -514,6 +567,9 @@ pub fn draw_image_fill_in_container(
true,
);
}
Kind::SVGRaw(_) => {
canvas.clip_rect(container, skia::ClipOp::Intersect, true);
}
}
canvas.draw_image_rect(image, None, dest_rect, &paint);

View file

@ -0,0 +1,10 @@
#[derive(Debug, Clone, PartialEq)]
pub struct SVGRaw {
pub content: String,
}
impl SVGRaw {
pub fn from_content(svg: String) -> SVGRaw {
SVGRaw { content: svg }
}
}