mirror of
https://github.com/penpot/penpot.git
synced 2026-04-07 20:02:30 +02:00
🔧 Batch blur and shadow effects into single WASM call
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
[app.render-wasm.helpers :as h]
|
||||
[app.render-wasm.mem :as mem]
|
||||
[app.render-wasm.serializers :as sr]
|
||||
[app.render-wasm.serializers.color :as sr-clr]
|
||||
[app.render-wasm.wasm :as wasm]))
|
||||
|
||||
;; Binary layout constants matching Rust implementation:
|
||||
@@ -76,7 +77,7 @@
|
||||
[0.0 0.0 0.0 0.0]))
|
||||
|
||||
(defn set-shape-base-props
|
||||
"Set all base shape properties in a single WASM call.
|
||||
"Set all base shape properties (and optionally children) in a single WASM call.
|
||||
|
||||
This replaces the following individual calls:
|
||||
- use-shape
|
||||
@@ -91,103 +92,206 @@
|
||||
- set-shape-selrect
|
||||
- set-shape-corners
|
||||
- set-shape-constraints (clear + h + v)
|
||||
- set-shape-children (when include-children? is true)
|
||||
|
||||
Returns nil."
|
||||
[shape]
|
||||
([shape] (set-shape-base-props shape false))
|
||||
([shape include-children?]
|
||||
(when wasm/context-initialized?
|
||||
(let [id (dm/get-prop shape :id)
|
||||
parent-id (get shape :parent-id)
|
||||
shape-type (dm/get-prop shape :type)
|
||||
|
||||
clip-content (if (= shape-type :frame)
|
||||
(not (get shape :show-content))
|
||||
false)
|
||||
hidden (get shape :hidden false)
|
||||
|
||||
flags (cond-> 0
|
||||
clip-content (bit-or FLAG-CLIP-CONTENT)
|
||||
hidden (bit-or FLAG-HIDDEN))
|
||||
|
||||
blend-mode (sr/translate-blend-mode (get shape :blend-mode))
|
||||
constraint-h (let [c (get shape :constraints-h)]
|
||||
(if (some? c)
|
||||
(sr/translate-constraint-h c)
|
||||
CONSTRAINT-NONE))
|
||||
constraint-v (let [c (get shape :constraints-v)]
|
||||
(if (some? c)
|
||||
(sr/translate-constraint-v c)
|
||||
CONSTRAINT-NONE))
|
||||
|
||||
opacity (d/nilv (get shape :opacity) 1.0)
|
||||
rotation (d/nilv (get shape :rotation) 0.0)
|
||||
|
||||
;; Transform matrix
|
||||
[ta tb tc td te tf] (serialize-transform (get shape :transform))
|
||||
|
||||
;; Selrect
|
||||
selrect (get shape :selrect)
|
||||
[sx1 sy1 sx2 sy2] (serialize-selrect selrect)
|
||||
|
||||
;; Corners
|
||||
r1 (d/nilv (get shape :r1) 0.0)
|
||||
r2 (d/nilv (get shape :r2) 0.0)
|
||||
r3 (d/nilv (get shape :r3) 0.0)
|
||||
r4 (d/nilv (get shape :r4) 0.0)
|
||||
|
||||
;; Children (when batched)
|
||||
children (when include-children?
|
||||
(into [] (filter uuid?) (get shape :shapes)))
|
||||
child-count (if include-children? (count children) 0)
|
||||
|
||||
;; Total buffer: 104 base + (4 child_count + 16*N child UUIDs) when batched
|
||||
total-size (if include-children?
|
||||
(+ BASE-PROPS-SIZE 4 (* child-count 16))
|
||||
BASE-PROPS-SIZE)
|
||||
|
||||
;; Allocate buffer and get DataView
|
||||
offset (mem/alloc total-size)
|
||||
heap (mem/get-heap-u8)
|
||||
dview (js/DataView. (.-buffer heap))]
|
||||
|
||||
;; Write id (offset 0, 16 bytes)
|
||||
(write-uuid-to-heap dview offset id)
|
||||
|
||||
;; Write parent_id (offset 16, 16 bytes)
|
||||
(write-uuid-to-heap dview (+ offset 16) (d/nilv parent-id uuid/zero))
|
||||
|
||||
;; Write shape_type (offset 32, 1 byte)
|
||||
(.setUint8 dview (+ offset 32) (sr/translate-shape-type shape-type))
|
||||
|
||||
;; Write flags (offset 33, 1 byte)
|
||||
(.setUint8 dview (+ offset 33) flags)
|
||||
|
||||
;; Write blend_mode (offset 34, 1 byte)
|
||||
(.setUint8 dview (+ offset 34) blend-mode)
|
||||
|
||||
;; Write constraint_h (offset 35, 1 byte)
|
||||
(.setUint8 dview (+ offset 35) constraint-h)
|
||||
|
||||
;; Write constraint_v (offset 36, 1 byte)
|
||||
(.setUint8 dview (+ offset 36) constraint-v)
|
||||
|
||||
;; Padding at offset 37-39 (already zero from alloc)
|
||||
|
||||
;; Write opacity (offset 40, f32)
|
||||
(.setFloat32 dview (+ offset 40) opacity true)
|
||||
|
||||
;; Write rotation (offset 44, f32)
|
||||
(.setFloat32 dview (+ offset 44) rotation true)
|
||||
|
||||
;; Write transform matrix (offset 48, 6 × f32)
|
||||
(.setFloat32 dview (+ offset 48) ta true)
|
||||
(.setFloat32 dview (+ offset 52) tb true)
|
||||
(.setFloat32 dview (+ offset 56) tc true)
|
||||
(.setFloat32 dview (+ offset 60) td true)
|
||||
(.setFloat32 dview (+ offset 64) te true)
|
||||
(.setFloat32 dview (+ offset 68) tf true)
|
||||
|
||||
;; Write selrect (offset 72, 4 × f32)
|
||||
(.setFloat32 dview (+ offset 72) sx1 true)
|
||||
(.setFloat32 dview (+ offset 76) sy1 true)
|
||||
(.setFloat32 dview (+ offset 80) sx2 true)
|
||||
(.setFloat32 dview (+ offset 84) sy2 true)
|
||||
|
||||
;; Write corners (offset 88, 4 × f32)
|
||||
(.setFloat32 dview (+ offset 88) r1 true)
|
||||
(.setFloat32 dview (+ offset 92) r2 true)
|
||||
(.setFloat32 dview (+ offset 96) r3 true)
|
||||
(.setFloat32 dview (+ offset 100) r4 true)
|
||||
|
||||
;; Write children (offset 104+) when batched
|
||||
(when include-children?
|
||||
(.setUint32 dview (+ offset 104) child-count true)
|
||||
(loop [i 0
|
||||
cs (seq children)]
|
||||
(when cs
|
||||
(write-uuid-to-heap dview (+ offset 108 (* i 16)) (first cs))
|
||||
(recur (inc i) (next cs)))))
|
||||
|
||||
(h/call wasm/internal-module "_set_shape_base_props")
|
||||
|
||||
nil))))
|
||||
|
||||
;; Binary layout for batched blur + shadows:
|
||||
;;
|
||||
;; Header (12 bytes):
|
||||
;; | Offset | Size | Field | Type |
|
||||
;; |--------|------|---------------|------------|
|
||||
;; | 0 | 1 | blur_present | u8 |
|
||||
;; | 1 | 1 | blur_type | u8 |
|
||||
;; | 2 | 1 | blur_hidden | u8 |
|
||||
;; | 3 | 1 | padding | - |
|
||||
;; | 4 | 4 | blur_value | f32 LE |
|
||||
;; | 8 | 4 | shadow_count | u32 LE |
|
||||
;;
|
||||
;; Per shadow (24 bytes each):
|
||||
;; | Offset | Size | Field | Type |
|
||||
;; |--------|------|----------|------------|
|
||||
;; | 0 | 4 | color | u32 LE |
|
||||
;; | 4 | 4 | blur | f32 LE |
|
||||
;; | 8 | 4 | spread | f32 LE |
|
||||
;; | 12 | 4 | offset_x | f32 LE |
|
||||
;; | 16 | 4 | offset_y | f32 LE |
|
||||
;; | 20 | 1 | style | u8 |
|
||||
;; | 21 | 1 | hidden | u8 |
|
||||
;; | 22 | 2 | padding | - |
|
||||
|
||||
(def ^:const EFFECTS-HEADER-SIZE 12)
|
||||
(def ^:const SHADOW-ENTRY-SIZE 24)
|
||||
|
||||
(defn set-shape-effects
|
||||
"Set blur and shadows in a single WASM call.
|
||||
|
||||
Replaces:
|
||||
- set-shape-blur / clear-shape-blur
|
||||
- clear-shape-shadows + N × add-shape-shadow
|
||||
|
||||
Returns nil."
|
||||
[blur shadows]
|
||||
(when wasm/context-initialized?
|
||||
(let [id (dm/get-prop shape :id)
|
||||
parent-id (get shape :parent-id)
|
||||
shape-type (dm/get-prop shape :type)
|
||||
(let [shadow-count (count shadows)
|
||||
total-size (+ EFFECTS-HEADER-SIZE (* shadow-count SHADOW-ENTRY-SIZE))
|
||||
offset (mem/alloc total-size)
|
||||
heap (mem/get-heap-u8)
|
||||
dview (js/DataView. (.-buffer heap))]
|
||||
|
||||
clip-content (if (= shape-type :frame)
|
||||
(not (get shape :show-content))
|
||||
false)
|
||||
hidden (get shape :hidden false)
|
||||
;; Write blur header
|
||||
(if (some? blur)
|
||||
(let [type (-> blur :type sr/translate-blur-type)
|
||||
hidden (if (:hidden blur) 1 0)
|
||||
value (:value blur)]
|
||||
(.setUint8 dview offset 1) ;; blur_present
|
||||
(.setUint8 dview (+ offset 1) type) ;; blur_type
|
||||
(.setUint8 dview (+ offset 2) hidden) ;; blur_hidden
|
||||
(.setFloat32 dview (+ offset 4) value true)) ;; blur_value
|
||||
(do
|
||||
(.setUint8 dview offset 0) ;; blur_present = 0
|
||||
(.setUint8 dview (+ offset 1) 0)
|
||||
(.setUint8 dview (+ offset 2) 0)
|
||||
(.setFloat32 dview (+ offset 4) 0.0 true)))
|
||||
|
||||
flags (cond-> 0
|
||||
clip-content (bit-or FLAG-CLIP-CONTENT)
|
||||
hidden (bit-or FLAG-HIDDEN))
|
||||
;; Write shadow count
|
||||
(.setUint32 dview (+ offset 8) shadow-count true)
|
||||
|
||||
blend-mode (sr/translate-blend-mode (get shape :blend-mode))
|
||||
constraint-h (let [c (get shape :constraints-h)]
|
||||
(if (some? c)
|
||||
(sr/translate-constraint-h c)
|
||||
CONSTRAINT-NONE))
|
||||
constraint-v (let [c (get shape :constraints-v)]
|
||||
(if (some? c)
|
||||
(sr/translate-constraint-v c)
|
||||
CONSTRAINT-NONE))
|
||||
|
||||
opacity (d/nilv (get shape :opacity) 1.0)
|
||||
rotation (d/nilv (get shape :rotation) 0.0)
|
||||
|
||||
;; Transform matrix
|
||||
[ta tb tc td te tf] (serialize-transform (get shape :transform))
|
||||
|
||||
;; Selrect
|
||||
selrect (get shape :selrect)
|
||||
[sx1 sy1 sx2 sy2] (serialize-selrect selrect)
|
||||
|
||||
;; Corners
|
||||
r1 (d/nilv (get shape :r1) 0.0)
|
||||
r2 (d/nilv (get shape :r2) 0.0)
|
||||
r3 (d/nilv (get shape :r3) 0.0)
|
||||
r4 (d/nilv (get shape :r4) 0.0)
|
||||
|
||||
;; Allocate buffer and get DataView
|
||||
offset (mem/alloc BASE-PROPS-SIZE)
|
||||
heap (mem/get-heap-u8)
|
||||
dview (js/DataView. (.-buffer heap))]
|
||||
|
||||
;; Write id (offset 0, 16 bytes)
|
||||
(write-uuid-to-heap dview offset id)
|
||||
|
||||
;; Write parent_id (offset 16, 16 bytes)
|
||||
(write-uuid-to-heap dview (+ offset 16) (d/nilv parent-id uuid/zero))
|
||||
|
||||
;; Write shape_type (offset 32, 1 byte)
|
||||
(.setUint8 dview (+ offset 32) (sr/translate-shape-type shape-type))
|
||||
|
||||
;; Write flags (offset 33, 1 byte)
|
||||
(.setUint8 dview (+ offset 33) flags)
|
||||
|
||||
;; Write blend_mode (offset 34, 1 byte)
|
||||
(.setUint8 dview (+ offset 34) blend-mode)
|
||||
|
||||
;; Write constraint_h (offset 35, 1 byte)
|
||||
(.setUint8 dview (+ offset 35) constraint-h)
|
||||
|
||||
;; Write constraint_v (offset 36, 1 byte)
|
||||
(.setUint8 dview (+ offset 36) constraint-v)
|
||||
|
||||
;; Padding at offset 37-39 (already zero from alloc)
|
||||
|
||||
;; Write opacity (offset 40, f32)
|
||||
(.setFloat32 dview (+ offset 40) opacity true)
|
||||
|
||||
;; Write rotation (offset 44, f32)
|
||||
(.setFloat32 dview (+ offset 44) rotation true)
|
||||
|
||||
;; Write transform matrix (offset 48, 6 × f32)
|
||||
(.setFloat32 dview (+ offset 48) ta true)
|
||||
(.setFloat32 dview (+ offset 52) tb true)
|
||||
(.setFloat32 dview (+ offset 56) tc true)
|
||||
(.setFloat32 dview (+ offset 60) td true)
|
||||
(.setFloat32 dview (+ offset 64) te true)
|
||||
(.setFloat32 dview (+ offset 68) tf true)
|
||||
|
||||
;; Write selrect (offset 72, 4 × f32)
|
||||
(.setFloat32 dview (+ offset 72) sx1 true)
|
||||
(.setFloat32 dview (+ offset 76) sy1 true)
|
||||
(.setFloat32 dview (+ offset 80) sx2 true)
|
||||
(.setFloat32 dview (+ offset 84) sy2 true)
|
||||
|
||||
;; Write corners (offset 88, 4 × f32)
|
||||
(.setFloat32 dview (+ offset 88) r1 true)
|
||||
(.setFloat32 dview (+ offset 92) r2 true)
|
||||
(.setFloat32 dview (+ offset 96) r3 true)
|
||||
(.setFloat32 dview (+ offset 100) r4 true)
|
||||
|
||||
(h/call wasm/internal-module "_set_shape_base_props")
|
||||
;; Write shadow entries
|
||||
(loop [i 0
|
||||
shadows-seq (seq shadows)]
|
||||
(when shadows-seq
|
||||
(let [shadow (first shadows-seq)
|
||||
entry-offset (+ offset EFFECTS-HEADER-SIZE (* i SHADOW-ENTRY-SIZE))
|
||||
color (get shadow :color)
|
||||
rgba (sr-clr/hex->u32argb (get color :color)
|
||||
(get color :opacity))]
|
||||
(.setUint32 dview entry-offset rgba true)
|
||||
(.setFloat32 dview (+ entry-offset 4) (get shadow :blur) true)
|
||||
(.setFloat32 dview (+ entry-offset 8) (get shadow :spread) true)
|
||||
(.setFloat32 dview (+ entry-offset 12) (get shadow :offset-x) true)
|
||||
(.setFloat32 dview (+ entry-offset 16) (get shadow :offset-y) true)
|
||||
(.setUint8 dview (+ entry-offset 20) (sr/translate-shadow-style (get shadow :style)))
|
||||
(.setUint8 dview (+ entry-offset 21) (if (get shadow :hidden) 1 0))
|
||||
(recur (inc i) (next shadows-seq)))))
|
||||
|
||||
(h/call wasm/internal-module "_set_shape_effects")
|
||||
nil)))
|
||||
|
||||
198
render-wasm/src/wasm/shapes/effect_props.rs
Normal file
198
render-wasm/src/wasm/shapes/effect_props.rs
Normal file
@@ -0,0 +1,198 @@
|
||||
use skia_safe as skia;
|
||||
|
||||
use crate::mem;
|
||||
use crate::shapes::{Blur, Shadow};
|
||||
use crate::wasm::blurs::RawBlurType;
|
||||
use crate::wasm::shadows::RawShadowStyle;
|
||||
use crate::{with_current_shape_mut, STATE};
|
||||
|
||||
const RAW_EFFECT_HEADER_SIZE: usize = std::mem::size_of::<RawEffectHeader>();
|
||||
const RAW_SHADOW_ENTRY_SIZE: usize = std::mem::size_of::<RawShadowEntry>();
|
||||
|
||||
/// Binary layout for the effect header (blur + shadow count).
|
||||
///
|
||||
/// The struct fields directly mirror the binary protocol — the layout
|
||||
/// documentation lives in the struct definition itself via `#[repr(C)]`.
|
||||
#[repr(C)]
|
||||
#[repr(align(4))]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RawEffectHeader {
|
||||
blur_present: u8,
|
||||
blur_type: u8,
|
||||
blur_hidden: u8,
|
||||
padding: u8,
|
||||
blur_value: f32,
|
||||
shadow_count: u32,
|
||||
}
|
||||
|
||||
impl RawEffectHeader {
|
||||
fn has_blur(&self) -> bool {
|
||||
self.blur_present != 0
|
||||
}
|
||||
|
||||
fn is_blur_hidden(&self) -> bool {
|
||||
self.blur_hidden != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; RAW_EFFECT_HEADER_SIZE]> for RawEffectHeader {
|
||||
fn from(bytes: [u8; RAW_EFFECT_HEADER_SIZE]) -> Self {
|
||||
unsafe { std::mem::transmute(bytes) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Binary layout for a single shadow entry.
|
||||
#[repr(C)]
|
||||
#[repr(align(4))]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RawShadowEntry {
|
||||
color: u32,
|
||||
blur: f32,
|
||||
spread: f32,
|
||||
offset_x: f32,
|
||||
offset_y: f32,
|
||||
style: u8,
|
||||
hidden: u8,
|
||||
padding: [u8; 2],
|
||||
}
|
||||
|
||||
impl RawShadowEntry {
|
||||
fn is_hidden(&self) -> bool {
|
||||
self.hidden != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; RAW_SHADOW_ENTRY_SIZE]> for RawShadowEntry {
|
||||
fn from(bytes: [u8; RAW_SHADOW_ENTRY_SIZE]) -> Self {
|
||||
unsafe { std::mem::transmute(bytes) }
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_shape_effects() {
|
||||
let bytes = mem::bytes();
|
||||
|
||||
if bytes.len() < RAW_EFFECT_HEADER_SIZE {
|
||||
return;
|
||||
}
|
||||
|
||||
let header_bytes: [u8; RAW_EFFECT_HEADER_SIZE] =
|
||||
bytes[..RAW_EFFECT_HEADER_SIZE].try_into().unwrap();
|
||||
let header = RawEffectHeader::from(header_bytes);
|
||||
|
||||
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||
// Parse blur
|
||||
if header.has_blur() {
|
||||
let blur_type = RawBlurType::from(header.blur_type);
|
||||
shape.set_blur(Some(Blur::new(
|
||||
blur_type.into(),
|
||||
header.is_blur_hidden(),
|
||||
header.blur_value,
|
||||
)));
|
||||
} else {
|
||||
shape.set_blur(None);
|
||||
}
|
||||
|
||||
// Parse shadows
|
||||
let shadow_count = header.shadow_count as usize;
|
||||
shape.clear_shadows();
|
||||
let shadows_data = &bytes[RAW_EFFECT_HEADER_SIZE..];
|
||||
for i in 0..shadow_count {
|
||||
let offset = i * RAW_SHADOW_ENTRY_SIZE;
|
||||
if offset + RAW_SHADOW_ENTRY_SIZE > shadows_data.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
let entry_bytes: [u8; RAW_SHADOW_ENTRY_SIZE] = shadows_data
|
||||
[offset..offset + RAW_SHADOW_ENTRY_SIZE]
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let entry = RawShadowEntry::from(entry_bytes);
|
||||
|
||||
let shadow = Shadow::new(
|
||||
skia::Color::new(entry.color),
|
||||
entry.blur,
|
||||
entry.spread,
|
||||
(entry.offset_x, entry.offset_y),
|
||||
RawShadowStyle::from(entry.style).into(),
|
||||
entry.is_hidden(),
|
||||
);
|
||||
shape.add_shadow(shadow);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_raw_effect_header_layout() {
|
||||
assert_eq!(RAW_EFFECT_HEADER_SIZE, 12);
|
||||
assert_eq!(std::mem::align_of::<RawEffectHeader>(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_raw_shadow_entry_layout() {
|
||||
assert_eq!(RAW_SHADOW_ENTRY_SIZE, 24);
|
||||
assert_eq!(std::mem::align_of::<RawShadowEntry>(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_field_offsets() {
|
||||
assert_eq!(std::mem::offset_of!(RawEffectHeader, blur_present), 0);
|
||||
assert_eq!(std::mem::offset_of!(RawEffectHeader, blur_type), 1);
|
||||
assert_eq!(std::mem::offset_of!(RawEffectHeader, blur_hidden), 2);
|
||||
assert_eq!(std::mem::offset_of!(RawEffectHeader, blur_value), 4);
|
||||
assert_eq!(std::mem::offset_of!(RawEffectHeader, shadow_count), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shadow_entry_field_offsets() {
|
||||
assert_eq!(std::mem::offset_of!(RawShadowEntry, color), 0);
|
||||
assert_eq!(std::mem::offset_of!(RawShadowEntry, blur), 4);
|
||||
assert_eq!(std::mem::offset_of!(RawShadowEntry, spread), 8);
|
||||
assert_eq!(std::mem::offset_of!(RawShadowEntry, offset_x), 12);
|
||||
assert_eq!(std::mem::offset_of!(RawShadowEntry, offset_y), 16);
|
||||
assert_eq!(std::mem::offset_of!(RawShadowEntry, style), 20);
|
||||
assert_eq!(std::mem::offset_of!(RawShadowEntry, hidden), 21);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_deserialization() {
|
||||
let mut bytes = [0u8; RAW_EFFECT_HEADER_SIZE];
|
||||
bytes[0] = 1; // blur_present
|
||||
bytes[1] = 1; // blur_type = LayerBlur
|
||||
bytes[2] = 0; // blur_hidden = false
|
||||
bytes[4..8].copy_from_slice(&5.0_f32.to_le_bytes()); // blur_value
|
||||
bytes[8..12].copy_from_slice(&3_u32.to_le_bytes()); // shadow_count
|
||||
|
||||
let header = RawEffectHeader::from(bytes);
|
||||
assert!(header.has_blur());
|
||||
assert!(!header.is_blur_hidden());
|
||||
assert_eq!(header.blur_type, 1);
|
||||
assert_eq!(header.blur_value, 5.0);
|
||||
assert_eq!(header.shadow_count, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shadow_entry_deserialization() {
|
||||
let mut bytes = [0u8; RAW_SHADOW_ENTRY_SIZE];
|
||||
bytes[0..4].copy_from_slice(&0xFF0000FF_u32.to_le_bytes()); // color
|
||||
bytes[4..8].copy_from_slice(&4.0_f32.to_le_bytes()); // blur
|
||||
bytes[8..12].copy_from_slice(&2.0_f32.to_le_bytes()); // spread
|
||||
bytes[12..16].copy_from_slice(&10.0_f32.to_le_bytes()); // offset_x
|
||||
bytes[16..20].copy_from_slice(&20.0_f32.to_le_bytes()); // offset_y
|
||||
bytes[20] = 0; // style = DropShadow
|
||||
bytes[21] = 1; // hidden = true
|
||||
|
||||
let entry = RawShadowEntry::from(bytes);
|
||||
assert_eq!(entry.color, 0xFF0000FF);
|
||||
assert_eq!(entry.blur, 4.0);
|
||||
assert_eq!(entry.spread, 2.0);
|
||||
assert_eq!(entry.offset_x, 10.0);
|
||||
assert_eq!(entry.offset_y, 20.0);
|
||||
assert_eq!(entry.style, 0);
|
||||
assert!(entry.is_hidden());
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
mod base_props;
|
||||
mod effect_props;
|
||||
|
||||
use macros::ToJs;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user