diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index c094c0d5ee..5b04e616dd 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -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::()) - .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(); diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index ea328266d2..c505cd8517 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -469,8 +469,8 @@ impl Shape { self.strokes.clear(); } - pub fn set_path_segments(&mut self, buffer: Vec) -> Result<(), String> { - let path = Path::try_from(buffer)?; + pub fn set_path_segments(&mut self, segments: Vec) { + 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) { diff --git a/render-wasm/src/shapes/paths.rs b/render-wasm/src/shapes/paths.rs index ce815c1fe9..29a91bff82 100644 --- a/render-wasm/src/shapes/paths.rs +++ b/render-wasm/src/shapes/paths.rs @@ -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 { - let cmd = u16::from_le_bytes(self.data[0..2].try_into().map_err(stringify_slice_err)?); - Ok(cmd) - } - - fn xy(&self) -> Result { - 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 { - 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 { - 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 for Segment { - type Error = String; - fn try_from(value: RawPathData) -> Result { - 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, @@ -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> for Path { - type Error = String; - - fn try_from(value: Vec) -> Result { +impl Path { + pub fn new(segments: Vec) -> Self { let mut open = true; - let segments = value - .into_iter() - .map(Segment::try_from) - .collect::, String>>()?; - let mut skia_path = skia::Path::new(); let mut start = None; @@ -123,15 +57,14 @@ impl TryFrom> for Path { } } } - Ok(Path { + + Self { segments, skia_path, open, - }) + } } -} -impl Path { pub fn to_skia_path(&self) -> skia::Path { self.skia_path.snapshot() } diff --git a/render-wasm/src/wasm.rs b/render-wasm/src/wasm.rs index 7d45923d91..737023935c 100644 --- a/render-wasm/src/wasm.rs +++ b/render-wasm/src/wasm.rs @@ -1,4 +1,5 @@ pub mod fills; pub mod fonts; +pub mod paths; pub mod strokes; pub mod text; diff --git a/render-wasm/src/wasm/fills.rs b/render-wasm/src/wasm/fills.rs index f1cef8eb10..6978b23609 100644 --- a/render-wasm/src/wasm/fills.rs +++ b/render-wasm/src/wasm/fills.rs @@ -9,9 +9,7 @@ use crate::STATE; const RAW_FILL_DATA_SIZE: usize = std::mem::size_of::(); -#[repr(C)] -#[repr(align(4))] -#[repr(u8)] +#[repr(C, u8, align(4))] #[derive(Debug, PartialEq, Clone, Copy)] #[allow(dead_code)] pub enum RawFillData { diff --git a/render-wasm/src/wasm/paths.rs b/render-wasm/src/wasm/paths.rs new file mode 100644 index 0000000000..6f8b4c724c --- /dev/null +++ b/render-wasm/src/wasm/paths.rs @@ -0,0 +1,186 @@ +use crate::shapes::{Path, Segment}; +use crate::{mem, with_current_shape, STATE}; + +const RAW_SEGMENT_DATA_SIZE: usize = size_of::(); + +#[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::()]> for RawSegmentData { + fn from(bytes: [u8; size_of::()]) -> Self { + unsafe { std::mem::transmute(bytes) } + } +} + +impl TryFrom<&[u8]> for RawSegmentData { + type Error = String; + fn try_from(bytes: &[u8]) -> Result { + 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 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> for Path { + fn from(value: Vec) -> 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::()) + .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::()]; + 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::()]; + 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::()]; + 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::()]; + 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); + } +}