Improve setting svg attrs in wasm

This commit is contained in:
Alejandro Alonso
2025-10-23 14:22:22 +02:00
committed by Alonso Torres
parent dba718b850
commit 479ce99b32
14 changed files with 1554 additions and 73 deletions

View File

@@ -160,6 +160,38 @@ Stroke styles are serialized as `u8`:
| 3 | Mixed |
| \_ | Solid |
## Fill rules
Fill rules are serialized as `u8`
| Value | Field |
| ----- | ------ |
| 0 | Nonzero |
| 1 | Evenodd |
| \_ | Nonzero |
## Stroke linecaps
Stroke linecaps are serialized as `u8`
| Value | Field |
| ----- | ------ |
| 0 | Butt |
| 1 | Round |
| 2 | Square |
| \_ | Butt |
## Stroke linejoins
Stroke linejoins are serialized as `u8`
| Value | Field |
| ----- | ------ |
| 0 | Miter |
| 1 | Round |
| 2 | Bevel |
| \_ | Miter |
## Bool Operations
Bool operations (`bool-type`) are serialized as `u8`:

View File

@@ -764,14 +764,9 @@ impl RenderState {
&shape
};
let has_fill_none = matches!(
shape.svg_attrs.get("fill").map(String::as_str),
Some("none")
);
if shape.fills.is_empty()
&& !matches!(shape.shape_type, Type::Group(_))
&& !has_fill_none
&& !shape.svg_attrs.fill_none
{
if let Some(fills_to_render) = self.nested_fills.last() {
let fills_to_render = fills_to_render.clone();

View File

@@ -1,8 +1,8 @@
use std::collections::HashMap;
use crate::math::{Matrix, Point, Rect};
use crate::shapes::{Corners, Fill, ImageFill, Path, Shape, Stroke, StrokeCap, StrokeKind, Type};
use crate::shapes::{
Corners, Fill, ImageFill, Path, Shape, Stroke, StrokeCap, StrokeKind, SvgAttrs, Type,
};
use skia_safe::{self as skia, ImageFilter, RRect};
use super::{RenderState, SurfaceId};
@@ -17,7 +17,7 @@ fn draw_stroke_on_rect(
rect: &Rect,
selrect: &Rect,
corners: &Option<Corners>,
svg_attrs: &HashMap<String, String>,
svg_attrs: &SvgAttrs,
scale: f32,
shadow: Option<&ImageFilter>,
blur: Option<&ImageFilter>,
@@ -53,7 +53,7 @@ fn draw_stroke_on_circle(
stroke: &Stroke,
rect: &Rect,
selrect: &Rect,
svg_attrs: &HashMap<String, String>,
svg_attrs: &SvgAttrs,
scale: f32,
shadow: Option<&ImageFilter>,
blur: Option<&ImageFilter>,
@@ -130,7 +130,7 @@ pub fn draw_stroke_on_path(
path: &Path,
selrect: &Rect,
path_transform: Option<&Matrix>,
svg_attrs: &HashMap<String, String>,
svg_attrs: &SvgAttrs,
scale: f32,
shadow: Option<&ImageFilter>,
blur: Option<&ImageFilter>,
@@ -217,7 +217,7 @@ fn handle_stroke_caps(
selrect: &Rect,
canvas: &skia::Canvas,
is_open: bool,
svg_attrs: &HashMap<String, String>,
svg_attrs: &SvgAttrs,
scale: f32,
blur: Option<&ImageFilter>,
antialias: bool,

View File

@@ -21,6 +21,7 @@ mod rects;
mod shadows;
mod shape_to_path;
mod strokes;
mod svg_attrs;
mod svgraw;
mod text;
pub mod text_paths;
@@ -41,6 +42,7 @@ pub use rects::*;
pub use shadows::*;
pub use shape_to_path::*;
pub use strokes::*;
pub use svg_attrs::*;
pub use svgraw::*;
pub use text::*;
pub use transform::*;
@@ -174,7 +176,7 @@ pub struct Shape {
pub opacity: f32,
pub hidden: bool,
pub svg: Option<skia::svg::Dom>,
pub svg_attrs: HashMap<String, String>,
pub svg_attrs: SvgAttrs,
pub shadows: Vec<Shadow>,
pub layout_item: Option<LayoutItem>,
pub extrect: OnceCell<math::Rect>,
@@ -201,7 +203,7 @@ impl Shape {
hidden: false,
blur: None,
svg: None,
svg_attrs: HashMap::new(),
svg_attrs: SvgAttrs::default(),
shadows: Vec::with_capacity(1),
layout_item: None,
extrect: OnceCell::new(),
@@ -566,15 +568,6 @@ impl Shape {
};
}
pub fn set_path_attr(&mut self, name: String, value: String) {
match self.shape_type {
Type::Path(_) | Type::Bool(_) => {
self.set_svg_attr(name, value);
}
_ => unreachable!("This shape should have path attrs"),
};
}
pub fn set_svg_raw_content(&mut self, content: String) -> Result<(), String> {
self.shape_type = Type::SVGRaw(SVGRaw::from_content(content));
Ok(())
@@ -607,10 +600,6 @@ impl Shape {
self.svg = Some(svg);
}
pub fn set_svg_attr(&mut self, name: String, value: String) {
self.svg_attrs.insert(name, value);
}
pub fn blend_mode(&self) -> BlendMode {
self.blend_mode
}
@@ -1104,7 +1093,7 @@ impl Shape {
if let Some(path_transform) = self.to_path_transform() {
skia_path.transform(&path_transform);
}
if let Some("evenodd") = self.svg_attrs.get("fill-rule").map(String::as_str) {
if self.svg_attrs.fill_rule == FillRule::Evenodd {
skia_path.set_fill_type(skia::PathFillType::EvenOdd);
}
Some(skia_path)

View File

@@ -1,8 +1,10 @@
use crate::shapes::fills::{Fill, SolidColor};
use skia_safe::{self as skia, Rect};
use std::collections::HashMap;
use super::Corners;
use super::StrokeLineCap;
use super::StrokeLineJoin;
use super::SvgAttrs;
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum StrokeStyle {
@@ -159,7 +161,7 @@ impl Stroke {
pub fn to_paint(
&self,
rect: &Rect,
svg_attrs: &HashMap<String, String>,
svg_attrs: &SvgAttrs,
scale: f32,
antialias: bool,
) -> skia::Paint {
@@ -175,11 +177,11 @@ impl Stroke {
paint.set_stroke_width(width);
paint.set_anti_alias(antialias);
if let Some("round") = svg_attrs.get("stroke-linecap").map(String::as_str) {
if svg_attrs.stroke_linecap == StrokeLineCap::Round {
paint.set_stroke_cap(skia::paint::Cap::Round);
}
if let Some("round") = svg_attrs.get("stroke-linejoin").map(String::as_str) {
if svg_attrs.stroke_linejoin == StrokeLineJoin::Round {
paint.set_stroke_join(skia::paint::Join::Round);
}
@@ -225,7 +227,7 @@ impl Stroke {
&self,
is_open: bool,
rect: &Rect,
svg_attrs: &HashMap<String, String>,
svg_attrs: &SvgAttrs,
scale: f32,
antialias: bool,
) -> skia::Paint {
@@ -249,7 +251,7 @@ impl Stroke {
&self,
is_open: bool,
rect: &Rect,
svg_attrs: &HashMap<String, String>,
svg_attrs: &SvgAttrs,
scale: f32,
antialias: bool,
) -> skia::Paint {

View File

@@ -0,0 +1,49 @@
#[derive(Debug, Clone, PartialEq, Copy, Default)]
pub enum FillRule {
#[default]
Nonzero,
Evenodd,
}
#[derive(Debug, Clone, PartialEq, Copy, Default)]
pub enum StrokeLineCap {
#[default]
Butt,
Round,
Square,
}
#[derive(Debug, Clone, PartialEq, Copy, Default)]
pub enum StrokeLineJoin {
#[default]
Miter,
Round,
Bevel,
}
#[derive(Debug, Clone, PartialEq, Copy)]
pub struct SvgAttrs {
pub fill_rule: FillRule,
pub stroke_linecap: StrokeLineCap,
pub stroke_linejoin: StrokeLineJoin,
/// Indicates that this shape has an explicit `fill="none"` attribute.
///
/// In SVG, the `fill` attribute is inheritable from container elements like `<g>`.
/// However, when a shape explicitly sets `fill="none"`, it breaks the color
/// inheritance chain - the shape will not inherit fill colors from parent containers.
///
/// This is different from having an empty fills array, as it explicitly signals
/// the intention to have no fill, preventing inheritance.
pub fill_none: bool,
}
impl Default for SvgAttrs {
fn default() -> Self {
Self {
fill_rule: FillRule::Nonzero,
stroke_linecap: StrokeLineCap::Butt,
stroke_linejoin: StrokeLineJoin::Miter,
fill_none: false,
}
}
}

View File

@@ -7,4 +7,5 @@ pub mod paths;
pub mod shadows;
pub mod shapes;
pub mod strokes;
pub mod svg_attrs;
pub mod text;

View File

@@ -225,37 +225,6 @@ pub extern "C" fn current_to_path() -> *mut u8 {
mem::write_vec(result)
}
// 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 set_shape_path_attrs(num_attrs: u32) {
with_current_shape_mut!(state, |shape: &mut 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);
}
});
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -0,0 +1,95 @@
use macros::ToJs;
use crate::shapes::{FillRule, StrokeLineCap, StrokeLineJoin};
use crate::{with_current_shape_mut, STATE};
#[derive(PartialEq, ToJs)]
#[repr(u8)]
#[allow(dead_code)]
pub enum RawFillRule {
Nonzero = 0,
Evenodd = 1,
}
impl From<u8> for RawFillRule {
fn from(value: u8) -> Self {
unsafe { std::mem::transmute(value) }
}
}
impl From<RawFillRule> for FillRule {
fn from(value: RawFillRule) -> Self {
match value {
RawFillRule::Nonzero => FillRule::Nonzero,
RawFillRule::Evenodd => FillRule::Evenodd,
}
}
}
#[derive(PartialEq, ToJs)]
#[repr(u8)]
#[allow(dead_code)]
pub enum RawStrokeLineCap {
Butt = 0,
Round = 1,
Square = 2,
}
impl From<u8> for RawStrokeLineCap {
fn from(value: u8) -> Self {
unsafe { std::mem::transmute(value) }
}
}
impl From<RawStrokeLineCap> for StrokeLineCap {
fn from(value: RawStrokeLineCap) -> Self {
match value {
RawStrokeLineCap::Butt => StrokeLineCap::Butt,
RawStrokeLineCap::Round => StrokeLineCap::Round,
RawStrokeLineCap::Square => StrokeLineCap::Square,
}
}
}
#[derive(PartialEq, ToJs)]
#[repr(u8)]
#[allow(dead_code)]
pub enum RawStrokeLineJoin {
Miter = 0,
Round = 1,
Bevel = 2,
}
impl From<u8> for RawStrokeLineJoin {
fn from(value: u8) -> Self {
unsafe { std::mem::transmute(value) }
}
}
impl From<RawStrokeLineJoin> for StrokeLineJoin {
fn from(value: RawStrokeLineJoin) -> Self {
match value {
RawStrokeLineJoin::Miter => StrokeLineJoin::Miter,
RawStrokeLineJoin::Round => StrokeLineJoin::Round,
RawStrokeLineJoin::Bevel => StrokeLineJoin::Bevel,
}
}
}
#[no_mangle]
pub extern "C" fn set_shape_svg_attrs(
fill_rule: u8,
stroke_linecap: u8,
stroke_linejoin: u8,
fill_none: bool,
) {
with_current_shape_mut!(state, |shape: &mut Shape| {
let fill_rule = RawFillRule::from(fill_rule);
shape.svg_attrs.fill_rule = fill_rule.into();
let stroke_linecap = RawStrokeLineCap::from(stroke_linecap);
shape.svg_attrs.stroke_linecap = stroke_linecap.into();
let stroke_linejoin = RawStrokeLineJoin::from(stroke_linejoin);
shape.svg_attrs.stroke_linejoin = stroke_linejoin.into();
shape.svg_attrs.fill_none = fill_none;
});
}