mirror of
https://github.com/penpot/penpot.git
synced 2025-06-22 20:07:04 +02:00
✨ Improve paths deserialization (wasm) (#6501)
* ♻️ Refactor path wasm code to its own wasm submodule * ♻️ Use unified enum for RawSegmentData and transmute to deserialize * ♻️ Move set_shape_path_attrs to wasm::paths module * 💄 Unify repr declarations
This commit is contained in:
parent
eaaca5629e
commit
f9bbf2d524
6 changed files with 197 additions and 125 deletions
|
@ -345,38 +345,6 @@ pub extern "C" fn set_shape_blur(blur_type: u8, hidden: bool, value: f32) {
|
|||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_shape_path_content() {
|
||||
with_current_shape!(state, |shape: &mut Shape| {
|
||||
let bytes = mem::bytes();
|
||||
let raw_segments = bytes
|
||||
.chunks(size_of::<shapes::RawPathData>())
|
||||
.map(|data| shapes::RawPathData {
|
||||
data: data.try_into().unwrap(),
|
||||
})
|
||||
.collect();
|
||||
shape.set_path_segments(raw_segments).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
// 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_corners(r1: f32, r2: f32, r3: f32, r4: f32) {
|
||||
with_current_shape!(state, |shape: &mut Shape| {
|
||||
|
@ -384,19 +352,6 @@ pub extern "C" fn set_shape_corners(r1: f32, r2: f32, r3: f32, r4: f32) {
|
|||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_shape_path_attrs(num_attrs: u32) {
|
||||
with_current_shape!(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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn propagate_modifiers(pixel_precision: bool) -> *mut u8 {
|
||||
let bytes = mem::bytes();
|
||||
|
|
|
@ -469,8 +469,8 @@ impl Shape {
|
|||
self.strokes.clear();
|
||||
}
|
||||
|
||||
pub fn set_path_segments(&mut self, buffer: Vec<RawPathData>) -> Result<(), String> {
|
||||
let path = Path::try_from(buffer)?;
|
||||
pub fn set_path_segments(&mut self, segments: Vec<Segment>) {
|
||||
let path = Path::new(segments);
|
||||
|
||||
match &mut self.shape_type {
|
||||
Type::Bool(Bool { bool_type, .. }) => {
|
||||
|
@ -484,7 +484,6 @@ impl Shape {
|
|||
}
|
||||
_ => {}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_path_attr(&mut self, name: String, value: String) {
|
||||
|
|
|
@ -1,74 +1,15 @@
|
|||
use skia_safe::{self as skia, Matrix};
|
||||
use std::array::TryFromSliceError;
|
||||
|
||||
type Point = (f32, f32);
|
||||
|
||||
fn stringify_slice_err(_: TryFromSliceError) -> String {
|
||||
"Error deserializing path".to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RawPathData {
|
||||
pub data: [u8; 28],
|
||||
}
|
||||
|
||||
impl RawPathData {
|
||||
fn command(&self) -> Result<u16, String> {
|
||||
let cmd = u16::from_le_bytes(self.data[0..2].try_into().map_err(stringify_slice_err)?);
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
fn xy(&self) -> Result<Point, String> {
|
||||
let x = f32::from_le_bytes(self.data[20..24].try_into().map_err(stringify_slice_err)?);
|
||||
let y = f32::from_le_bytes(self.data[24..].try_into().map_err(stringify_slice_err)?);
|
||||
Ok((x, y))
|
||||
}
|
||||
|
||||
fn c1(&self) -> Result<Point, String> {
|
||||
let c1_x = f32::from_le_bytes(self.data[4..8].try_into().map_err(stringify_slice_err)?);
|
||||
let c1_y = f32::from_le_bytes(self.data[8..12].try_into().map_err(stringify_slice_err)?);
|
||||
|
||||
Ok((c1_x, c1_y))
|
||||
}
|
||||
|
||||
fn c2(&self) -> Result<Point, String> {
|
||||
let c2_x = f32::from_le_bytes(self.data[12..16].try_into().map_err(stringify_slice_err)?);
|
||||
let c2_y = f32::from_le_bytes(self.data[16..20].try_into().map_err(stringify_slice_err)?);
|
||||
|
||||
Ok((c2_x, c2_y))
|
||||
}
|
||||
}
|
||||
|
||||
const MOVE_TO: u16 = 1;
|
||||
const LINE_TO: u16 = 2;
|
||||
const CURVE_TO: u16 = 3;
|
||||
const CLOSE: u16 = 4;
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
enum Segment {
|
||||
pub enum Segment {
|
||||
MoveTo(Point),
|
||||
LineTo(Point),
|
||||
CurveTo((Point, Point, Point)),
|
||||
Close,
|
||||
}
|
||||
|
||||
impl TryFrom<RawPathData> for Segment {
|
||||
type Error = String;
|
||||
fn try_from(value: RawPathData) -> Result<Self, Self::Error> {
|
||||
let cmd = value.command()?;
|
||||
match cmd {
|
||||
MOVE_TO => Ok(Segment::MoveTo(value.xy()?)),
|
||||
LINE_TO => Ok(Segment::LineTo(value.xy()?)),
|
||||
CURVE_TO => Ok(Segment::CurveTo((value.c1()?, value.c2()?, value.xy()?))),
|
||||
CLOSE => Ok(Segment::Close),
|
||||
_ => Err(format!(
|
||||
"Error deserializing path. Unknown command/flags: {:#010x}",
|
||||
cmd
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Path {
|
||||
segments: Vec<Segment>,
|
||||
|
@ -78,20 +19,13 @@ pub struct Path {
|
|||
|
||||
impl Default for Path {
|
||||
fn default() -> Self {
|
||||
Path::try_from(Vec::new()).unwrap()
|
||||
Self::new(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<RawPathData>> for Path {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: Vec<RawPathData>) -> Result<Self, Self::Error> {
|
||||
impl Path {
|
||||
pub fn new(segments: Vec<Segment>) -> Self {
|
||||
let mut open = true;
|
||||
let segments = value
|
||||
.into_iter()
|
||||
.map(Segment::try_from)
|
||||
.collect::<Result<Vec<Segment>, String>>()?;
|
||||
|
||||
let mut skia_path = skia::Path::new();
|
||||
let mut start = None;
|
||||
|
||||
|
@ -123,15 +57,14 @@ impl TryFrom<Vec<RawPathData>> for Path {
|
|||
}
|
||||
}
|
||||
}
|
||||
Ok(Path {
|
||||
|
||||
Self {
|
||||
segments,
|
||||
skia_path,
|
||||
open,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Path {
|
||||
pub fn to_skia_path(&self) -> skia::Path {
|
||||
self.skia_path.snapshot()
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
pub mod fills;
|
||||
pub mod fonts;
|
||||
pub mod paths;
|
||||
pub mod strokes;
|
||||
pub mod text;
|
||||
|
|
|
@ -9,9 +9,7 @@ use crate::STATE;
|
|||
|
||||
const RAW_FILL_DATA_SIZE: usize = std::mem::size_of::<RawFillData>();
|
||||
|
||||
#[repr(C)]
|
||||
#[repr(align(4))]
|
||||
#[repr(u8)]
|
||||
#[repr(C, u8, align(4))]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
#[allow(dead_code)]
|
||||
pub enum RawFillData {
|
||||
|
|
186
render-wasm/src/wasm/paths.rs
Normal file
186
render-wasm/src/wasm/paths.rs
Normal file
|
@ -0,0 +1,186 @@
|
|||
use crate::shapes::{Path, Segment};
|
||||
use crate::{mem, with_current_shape, STATE};
|
||||
|
||||
const RAW_SEGMENT_DATA_SIZE: usize = size_of::<RawSegmentData>();
|
||||
|
||||
#[repr(C, u16, align(4))]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
#[allow(dead_code)]
|
||||
enum RawSegmentData {
|
||||
MoveTo(RawMoveCommand) = 0x01,
|
||||
LineTo(RawLineCommand) = 0x02,
|
||||
CurveTo(RawCurveCommand) = 0x03,
|
||||
Close = 0x04,
|
||||
}
|
||||
|
||||
impl From<[u8; size_of::<RawSegmentData>()]> for RawSegmentData {
|
||||
fn from(bytes: [u8; size_of::<RawSegmentData>()]) -> Self {
|
||||
unsafe { std::mem::transmute(bytes) }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for RawSegmentData {
|
||||
type Error = String;
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let data: [u8; RAW_SEGMENT_DATA_SIZE] = bytes
|
||||
.get(0..RAW_SEGMENT_DATA_SIZE)
|
||||
.and_then(|slice| slice.try_into().ok())
|
||||
.ok_or("Invalid path data".to_string())?;
|
||||
Ok(RawSegmentData::from(data))
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
struct RawMoveCommand {
|
||||
_padding: [u32; 4],
|
||||
x: f32,
|
||||
y: f32,
|
||||
}
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
struct RawLineCommand {
|
||||
_padding: [u32; 4],
|
||||
x: f32,
|
||||
y: f32,
|
||||
}
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
struct RawCurveCommand {
|
||||
c1_x: f32,
|
||||
c1_y: f32,
|
||||
c2_x: f32,
|
||||
c2_y: f32,
|
||||
x: f32,
|
||||
y: f32,
|
||||
}
|
||||
|
||||
impl From<RawSegmentData> for Segment {
|
||||
fn from(value: RawSegmentData) -> Self {
|
||||
match value {
|
||||
RawSegmentData::MoveTo(cmd) => Segment::MoveTo((cmd.x, cmd.y)),
|
||||
RawSegmentData::LineTo(cmd) => Segment::LineTo((cmd.x, cmd.y)),
|
||||
RawSegmentData::CurveTo(cmd) => {
|
||||
Segment::CurveTo(((cmd.c1_x, cmd.c1_y), (cmd.c2_x, cmd.c2_y), (cmd.x, cmd.y)))
|
||||
}
|
||||
RawSegmentData::Close => Segment::Close,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<RawSegmentData>> for Path {
|
||||
fn from(value: Vec<RawSegmentData>) -> Self {
|
||||
let segments = value.into_iter().map(Segment::from).collect();
|
||||
Path::new(segments)
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_shape_path_content() {
|
||||
with_current_shape!(state, |shape: &mut Shape| {
|
||||
let bytes = mem::bytes();
|
||||
|
||||
let segments = bytes
|
||||
.chunks(size_of::<RawSegmentData>())
|
||||
.map(|chunk| RawSegmentData::try_from(chunk).expect("Invalid path data"))
|
||||
.map(Segment::from)
|
||||
.collect();
|
||||
|
||||
shape.set_path_segments(segments);
|
||||
});
|
||||
}
|
||||
|
||||
// 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!(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::*;
|
||||
|
||||
#[test]
|
||||
fn test_move_command_deserialization() {
|
||||
let mut bytes = [0x00; size_of::<RawSegmentData>()];
|
||||
bytes[0..2].copy_from_slice(&0x01_u16.to_le_bytes());
|
||||
bytes[20..24].copy_from_slice(&1.0_f32.to_le_bytes());
|
||||
bytes[24..28].copy_from_slice(&2.0_f32.to_le_bytes());
|
||||
|
||||
let raw_segment = RawSegmentData::try_from(&bytes[..]).unwrap();
|
||||
let segment = Segment::from(raw_segment);
|
||||
|
||||
assert_eq!(segment, Segment::MoveTo((1.0, 2.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_line_command_deserialization() {
|
||||
let mut bytes = [0x00; size_of::<RawSegmentData>()];
|
||||
bytes[0..2].copy_from_slice(&0x02_u16.to_le_bytes());
|
||||
bytes[20..24].copy_from_slice(&3.0_f32.to_le_bytes());
|
||||
bytes[24..28].copy_from_slice(&4.0_f32.to_le_bytes());
|
||||
|
||||
let raw_segment = RawSegmentData::try_from(&bytes[..]).unwrap();
|
||||
let segment = Segment::from(raw_segment);
|
||||
|
||||
assert_eq!(segment, Segment::LineTo((3.0, 4.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_curve_command_deserialization() {
|
||||
let mut bytes = [0x00; size_of::<RawSegmentData>()];
|
||||
bytes[0..2].copy_from_slice(&0x03_u16.to_le_bytes());
|
||||
bytes[4..8].copy_from_slice(&1.0_f32.to_le_bytes());
|
||||
bytes[8..12].copy_from_slice(&2.0_f32.to_le_bytes());
|
||||
bytes[12..16].copy_from_slice(&3.0_f32.to_le_bytes());
|
||||
bytes[16..20].copy_from_slice(&4.0_f32.to_le_bytes());
|
||||
bytes[20..24].copy_from_slice(&5.0_f32.to_le_bytes());
|
||||
bytes[24..28].copy_from_slice(&6.0_f32.to_le_bytes());
|
||||
|
||||
let raw_segment = RawSegmentData::try_from(&bytes[..]).unwrap();
|
||||
let segment = Segment::from(raw_segment);
|
||||
|
||||
assert_eq!(
|
||||
segment,
|
||||
Segment::CurveTo(((1.0, 2.0), (3.0, 4.0), (5.0, 6.0)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_close_command_deserialization() {
|
||||
let mut bytes = [0x00; size_of::<RawSegmentData>()];
|
||||
bytes[0..2].copy_from_slice(&0x04_u16.to_le_bytes());
|
||||
|
||||
let raw_segment = RawSegmentData::try_from(&bytes[..]).unwrap();
|
||||
let segment = Segment::from(raw_segment);
|
||||
|
||||
assert_eq!(segment, Segment::Close);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue