mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
✨ Improve setting svg attrs in wasm
This commit is contained in:
committed by
Alonso Torres
parent
dba718b850
commit
479ce99b32
@@ -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`:
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
49
render-wasm/src/shapes/svg_attrs.rs
Normal file
49
render-wasm/src/shapes/svg_attrs.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,4 +7,5 @@ pub mod paths;
|
||||
pub mod shadows;
|
||||
pub mod shapes;
|
||||
pub mod strokes;
|
||||
pub mod svg_attrs;
|
||||
pub mod text;
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
95
render-wasm/src/wasm/svg_attrs.rs
Normal file
95
render-wasm/src/wasm/svg_attrs.rs
Normal 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;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user