mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 11:18:36 +00:00
🔧 Text Editor Serialization WIP
This commit is contained in:
parent
1975e3b0e3
commit
1992dc5678
@ -154,6 +154,229 @@
|
||||
(mem/free)
|
||||
text)))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Binary layout constants for TextEditorStyles (must match text_editor.rs)
|
||||
;; ---------------------------------------------------------------------------
|
||||
(def ^:const STYLES-HEADER-SIZE 120)
|
||||
|
||||
;; RAW_FILL_DATA_SIZE = 4 (tag+padding) + RawGradientData (largest variant)
|
||||
;; RawGradientData = 28 header + 16 stops × 8 bytes = 156
|
||||
(def ^:const RAW-FILL-DATA-SIZE 160)
|
||||
|
||||
;; MultipleState enum values
|
||||
(def ^:const MULTIPLE-UNDEFINED 0)
|
||||
(def ^:const MULTIPLE-SINGLE 1)
|
||||
(def ^:const MULTIPLE-MULTIPLE 2)
|
||||
|
||||
(def ^:private vertical-align-map
|
||||
{0 :top 1 :center 2 :bottom})
|
||||
|
||||
(def ^:private text-align-map
|
||||
{0 :left 1 :center 2 :right 3 :justify})
|
||||
|
||||
(def ^:private text-direction-map
|
||||
{0 :ltr 1 :rtl})
|
||||
|
||||
(def ^:private text-decoration-map
|
||||
{0 nil 1 :underline 2 :line-through 3 :overline})
|
||||
|
||||
(def ^:private text-transform-map
|
||||
{0 nil 1 :uppercase 2 :lowercase 3 :capitalize})
|
||||
|
||||
(def ^:private font-style-map
|
||||
{0 :normal 1 :italic})
|
||||
|
||||
(defn- read-multiple
|
||||
"Read a Multiple<T> field from a DataView. Returns :mixed when Multiple,
|
||||
nil when Undefined, or calls value-fn to read the value when Single."
|
||||
[dview state-offset value-fn]
|
||||
(let [state (.getUint32 dview state-offset true)]
|
||||
(case state
|
||||
1 (value-fn) ;; Single
|
||||
2 :mixed ;; Multiple
|
||||
nil))) ;; Undefined
|
||||
|
||||
(defn- u32-argb->hex
|
||||
"Convert u32 ARGB to #RRGGBB hex string."
|
||||
[argb]
|
||||
(let [r (bit-and (unsigned-bit-shift-right argb 16) 0xFF)
|
||||
g (bit-and (unsigned-bit-shift-right argb 8) 0xFF)
|
||||
b (bit-and argb 0xFF)]
|
||||
(str "#"
|
||||
(.padStart (.toString r 16) 2 "0")
|
||||
(.padStart (.toString g 16) 2 "0")
|
||||
(.padStart (.toString b 16) 2 "0"))))
|
||||
|
||||
(defn- u32-argb->opacity
|
||||
"Extract normalized opacity (0.0-1.0) from u32 ARGB."
|
||||
[argb]
|
||||
(/ (bit-and (unsigned-bit-shift-right argb 24) 0xFF) 255.0))
|
||||
|
||||
(defn- read-gradient-stops
|
||||
"Read gradient stops from DataView at the given byte offset."
|
||||
[dview base-offset stop-count]
|
||||
(let [stops-offset (+ base-offset 28)] ;; stops start at byte 28 within gradient data
|
||||
(into []
|
||||
(map (fn [i]
|
||||
(let [stop-off (+ stops-offset (* i 8))
|
||||
color (.getUint32 dview stop-off true)
|
||||
offset (.getFloat32 dview (+ stop-off 4) true)]
|
||||
{:color (u32-argb->hex color)
|
||||
:opacity (u32-argb->opacity color)
|
||||
:offset offset})))
|
||||
(range stop-count))))
|
||||
|
||||
(defn- read-fill-from-dview
|
||||
"Read a single RawFillData entry from DataView at the given byte offset.
|
||||
Returns a fill map in the standard Penpot text content format."
|
||||
[dview fill-offset]
|
||||
(let [tag (.getUint8 dview fill-offset)]
|
||||
(case tag
|
||||
;; Solid fill
|
||||
0 (let [color (.getUint32 dview (+ fill-offset 4) true)]
|
||||
{:fill-color (u32-argb->hex color)
|
||||
:fill-opacity (u32-argb->opacity color)})
|
||||
|
||||
;; Linear gradient
|
||||
1 (let [base (+ fill-offset 4)
|
||||
stop-count (.getUint8 dview (+ base 24))]
|
||||
{:fill-color-gradient
|
||||
{:type :linear
|
||||
:start-x (.getFloat32 dview base true)
|
||||
:start-y (.getFloat32 dview (+ base 4) true)
|
||||
:end-x (.getFloat32 dview (+ base 8) true)
|
||||
:end-y (.getFloat32 dview (+ base 12) true)
|
||||
:width (.getFloat32 dview (+ base 20) true)
|
||||
:stops (read-gradient-stops dview base stop-count)}
|
||||
:fill-opacity (/ (.getUint8 dview (+ base 16)) 255.0)})
|
||||
|
||||
;; Radial gradient
|
||||
2 (let [base (+ fill-offset 4)
|
||||
stop-count (.getUint8 dview (+ base 24))]
|
||||
{:fill-color-gradient
|
||||
{:type :radial
|
||||
:start-x (.getFloat32 dview base true)
|
||||
:start-y (.getFloat32 dview (+ base 4) true)
|
||||
:end-x (.getFloat32 dview (+ base 8) true)
|
||||
:end-y (.getFloat32 dview (+ base 12) true)
|
||||
:width (.getFloat32 dview (+ base 20) true)
|
||||
:stops (read-gradient-stops dview base stop-count)}
|
||||
:fill-opacity (/ (.getUint8 dview (+ base 16)) 255.0)})
|
||||
|
||||
;; Image fill
|
||||
3 (let [base (+ fill-offset 4)
|
||||
a (.getUint32 dview base true)
|
||||
b (.getUint32 dview (+ base 4) true)
|
||||
c (.getUint32 dview (+ base 8) true)
|
||||
d (.getUint32 dview (+ base 12) true)
|
||||
opacity (.getUint8 dview (+ base 16))
|
||||
flags (.getUint8 dview (+ base 17))
|
||||
width (.getInt32 dview (+ base 20) true)
|
||||
height (.getInt32 dview (+ base 24) true)]
|
||||
{:fill-image
|
||||
{:id (uuid/from-unsigned-parts a b c d)
|
||||
:width width
|
||||
:height height
|
||||
:keep-aspect-ratio (not (zero? (bit-and flags 1)))}
|
||||
:fill-opacity (/ opacity 255.0)})
|
||||
|
||||
;; Unknown tag
|
||||
nil)))
|
||||
|
||||
(defn text-editor-get-current-styles
|
||||
"Read the current text editor styles from WASM. Returns a map with the
|
||||
style values for the current selection, or nil if no selection/focus.
|
||||
Multiple<T> fields return :mixed when spans have different values."
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(let [ptr (h/call wasm/internal-module "_text_editor_get_current_styles")]
|
||||
(when (and ptr (not (zero? ptr)))
|
||||
(let [heap-u8 (mem/get-heap-u8)
|
||||
dview (js/DataView. (.-buffer heap-u8) (.-byteOffset heap-u8))
|
||||
|
||||
;; Read fills count first to know total size
|
||||
fills-count (.getUint32 dview (+ ptr 116) true)
|
||||
|
||||
;; Read scalar Multiple<T> fields
|
||||
vertical-align (get vertical-align-map
|
||||
(.getUint32 dview ptr true)
|
||||
:top)
|
||||
|
||||
text-align
|
||||
(read-multiple dview (+ ptr 4)
|
||||
#(get text-align-map (.getUint32 dview (+ ptr 8) true)))
|
||||
|
||||
text-direction
|
||||
(read-multiple dview (+ ptr 12)
|
||||
#(get text-direction-map (.getUint32 dview (+ ptr 16) true)))
|
||||
|
||||
text-decoration
|
||||
(read-multiple dview (+ ptr 20)
|
||||
#(get text-decoration-map (.getUint32 dview (+ ptr 24) true)))
|
||||
|
||||
text-transform
|
||||
(read-multiple dview (+ ptr 28)
|
||||
#(get text-transform-map (.getUint32 dview (+ ptr 32) true)))
|
||||
|
||||
font-size
|
||||
(read-multiple dview (+ ptr 36)
|
||||
#(.getFloat32 dview (+ ptr 40) true))
|
||||
|
||||
font-weight
|
||||
(read-multiple dview (+ ptr 44)
|
||||
#(.getInt32 dview (+ ptr 48) true))
|
||||
|
||||
line-height
|
||||
(read-multiple dview (+ ptr 52)
|
||||
#(.getFloat32 dview (+ ptr 56) true))
|
||||
|
||||
letter-spacing
|
||||
(read-multiple dview (+ ptr 60)
|
||||
#(.getFloat32 dview (+ ptr 64) true))
|
||||
|
||||
font-family
|
||||
(read-multiple dview (+ ptr 68)
|
||||
(fn []
|
||||
(let [a (.getUint32 dview (+ ptr 72) true)
|
||||
b (.getUint32 dview (+ ptr 76) true)
|
||||
c (.getUint32 dview (+ ptr 80) true)
|
||||
d (.getUint32 dview (+ ptr 84) true)]
|
||||
{:id (uuid/from-unsigned-parts a b c d)
|
||||
:style (get font-style-map (.getUint32 dview (+ ptr 88) true))
|
||||
:weight (.getUint32 dview (+ ptr 92) true)})))
|
||||
|
||||
font-variant-id
|
||||
(read-multiple dview (+ ptr 96)
|
||||
(fn []
|
||||
(let [a (.getUint32 dview (+ ptr 100) true)
|
||||
b (.getUint32 dview (+ ptr 104) true)
|
||||
c (.getUint32 dview (+ ptr 108) true)
|
||||
d (.getUint32 dview (+ ptr 112) true)]
|
||||
(uuid/from-unsigned-parts a b c d))))
|
||||
|
||||
;; Read fills
|
||||
fills
|
||||
(into []
|
||||
(keep (fn [i]
|
||||
(read-fill-from-dview dview
|
||||
(+ ptr STYLES-HEADER-SIZE
|
||||
(* i RAW-FILL-DATA-SIZE)))))
|
||||
(range fills-count))]
|
||||
|
||||
(mem/free)
|
||||
{:vertical-align vertical-align
|
||||
:text-align text-align
|
||||
:text-direction text-direction
|
||||
:text-decoration text-decoration
|
||||
:text-transform text-transform
|
||||
:font-size font-size
|
||||
:font-weight font-weight
|
||||
:line-height line-height
|
||||
:letter-spacing letter-spacing
|
||||
:font-family font-family
|
||||
:font-variant-id font-variant-id
|
||||
:fills fills})))))
|
||||
|
||||
(defn text-editor-get-active-shape-id
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
|
||||
@ -35,6 +35,30 @@ impl Gradient {
|
||||
gradient
|
||||
}
|
||||
|
||||
pub fn start(&self) -> (f32, f32) {
|
||||
self.start
|
||||
}
|
||||
|
||||
pub fn end(&self) -> (f32, f32) {
|
||||
self.end
|
||||
}
|
||||
|
||||
pub fn opacity(&self) -> u8 {
|
||||
self.opacity
|
||||
}
|
||||
|
||||
pub fn width(&self) -> f32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn colors(&self) -> &[Color] {
|
||||
&self.colors
|
||||
}
|
||||
|
||||
pub fn offsets(&self) -> &[f32] {
|
||||
&self.offsets
|
||||
}
|
||||
|
||||
fn add_stops(&mut self, stops: &[(Color, f32)]) {
|
||||
let colors = stops.iter().map(|(color, _)| *color);
|
||||
let offsets = stops.iter().map(|(_, offset)| *offset);
|
||||
@ -123,6 +147,14 @@ impl ImageFill {
|
||||
self.opacity
|
||||
}
|
||||
|
||||
pub fn width(&self) -> i32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn height(&self) -> i32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn keep_aspect_ratio(&self) -> bool {
|
||||
self.keep_aspect_ratio
|
||||
}
|
||||
|
||||
@ -30,6 +30,18 @@ impl FontFamily {
|
||||
Self { id, style, weight }
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Uuid {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn style(&self) -> FontStyle {
|
||||
self.style
|
||||
}
|
||||
|
||||
pub fn weight(&self) -> u32 {
|
||||
self.weight
|
||||
}
|
||||
|
||||
pub fn alias(&self) -> String {
|
||||
format!("{}", self)
|
||||
}
|
||||
|
||||
@ -36,6 +36,39 @@ impl From<RawFillData> for shapes::Fill {
|
||||
}
|
||||
}
|
||||
|
||||
fn color_to_u32(color: &shapes::Color) -> u32 {
|
||||
((color.a() as u32) << 24)
|
||||
| ((color.r() as u32) << 16)
|
||||
| ((color.g() as u32) << 8)
|
||||
| (color.b() as u32)
|
||||
}
|
||||
|
||||
fn gradient_to_raw(g: &shapes::Gradient) -> gradient::RawGradientData {
|
||||
let mut stops = [gradient::RawStopData {
|
||||
color: 0,
|
||||
offset: 0.0,
|
||||
}; gradient::MAX_GRADIENT_STOPS];
|
||||
let colors = g.colors();
|
||||
let offsets = g.offsets();
|
||||
let stop_count = colors.len().min(gradient::MAX_GRADIENT_STOPS);
|
||||
for i in 0..stop_count {
|
||||
stops[i] = gradient::RawStopData {
|
||||
color: color_to_u32(&colors[i]),
|
||||
offset: offsets[i],
|
||||
};
|
||||
}
|
||||
gradient::RawGradientData {
|
||||
start_x: g.start().0,
|
||||
start_y: g.start().1,
|
||||
end_x: g.end().0,
|
||||
end_y: g.end().1,
|
||||
opacity: g.opacity(),
|
||||
width: g.width(),
|
||||
stop_count: stop_count as u8,
|
||||
stops,
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&shapes::Fill> for RawFillData {
|
||||
type Error = String;
|
||||
|
||||
@ -43,19 +76,29 @@ impl TryFrom<&shapes::Fill> for RawFillData {
|
||||
match fill {
|
||||
shapes::Fill::Solid(shapes::SolidColor(color)) => {
|
||||
Ok(RawFillData::Solid(solid::RawSolidData {
|
||||
color: ((color.a() as u32) << 24)
|
||||
| ((color.r() as u32) << 16)
|
||||
| ((color.g() as u32) << 8)
|
||||
| (color.b() as u32),
|
||||
color: color_to_u32(color),
|
||||
}))
|
||||
}
|
||||
shapes::Fill::LinearGradient(_) => {
|
||||
Err("LinearGradient serialization is not implemented".to_string())
|
||||
shapes::Fill::LinearGradient(g) => Ok(RawFillData::Linear(gradient_to_raw(g))),
|
||||
shapes::Fill::RadialGradient(g) => Ok(RawFillData::Radial(gradient_to_raw(g))),
|
||||
shapes::Fill::Image(img) => {
|
||||
let id_bytes: [u8; 16] = img.id().into();
|
||||
let a = u32::from_le_bytes(id_bytes[0..4].try_into().unwrap());
|
||||
let b = u32::from_le_bytes(id_bytes[4..8].try_into().unwrap());
|
||||
let c = u32::from_le_bytes(id_bytes[8..12].try_into().unwrap());
|
||||
let d = u32::from_le_bytes(id_bytes[12..16].try_into().unwrap());
|
||||
let flags = if img.keep_aspect_ratio() { 1u8 } else { 0u8 };
|
||||
Ok(RawFillData::Image(image::RawImageFillData {
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d,
|
||||
opacity: img.opacity(),
|
||||
flags,
|
||||
width: img.width(),
|
||||
height: img.height(),
|
||||
}))
|
||||
}
|
||||
shapes::Fill::RadialGradient(_) => {
|
||||
Err("RadialGradient serialization is not implemented".to_string())
|
||||
}
|
||||
shapes::Fill::Image(_) => Err("Image fill serialization is not implemented".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
use crate::shapes::{Color, Gradient};
|
||||
|
||||
const MAX_GRADIENT_STOPS: usize = 16;
|
||||
pub(crate) const MAX_GRADIENT_STOPS: usize = 16;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
#[repr(align(4))]
|
||||
pub struct RawGradientData {
|
||||
start_x: f32,
|
||||
start_y: f32,
|
||||
end_x: f32,
|
||||
end_y: f32,
|
||||
opacity: u8,
|
||||
pub(crate) start_x: f32,
|
||||
pub(crate) start_y: f32,
|
||||
pub(crate) end_x: f32,
|
||||
pub(crate) end_y: f32,
|
||||
pub(crate) opacity: u8,
|
||||
// 24-bit padding here, reserved for future use
|
||||
width: f32,
|
||||
stop_count: u8,
|
||||
stops: [RawStopData; MAX_GRADIENT_STOPS],
|
||||
pub(crate) width: f32,
|
||||
pub(crate) stop_count: u8,
|
||||
pub(crate) stops: [RawStopData; MAX_GRADIENT_STOPS],
|
||||
}
|
||||
|
||||
impl RawGradientData {
|
||||
@ -29,9 +29,9 @@ impl RawGradientData {
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
struct RawStopData {
|
||||
color: u32,
|
||||
offset: f32,
|
||||
pub(crate) struct RawStopData {
|
||||
pub(crate) color: u32,
|
||||
pub(crate) offset: f32,
|
||||
}
|
||||
|
||||
impl RawStopData {
|
||||
|
||||
@ -15,15 +15,15 @@ const IMAGE_HEADER_SIZE: usize = 36; // 32 bytes for IDs + 4 bytes for is_thumbn
|
||||
#[repr(C)]
|
||||
#[repr(align(4))]
|
||||
pub struct RawImageFillData {
|
||||
a: u32,
|
||||
b: u32,
|
||||
c: u32,
|
||||
d: u32,
|
||||
opacity: u8,
|
||||
flags: u8,
|
||||
pub(crate) a: u32,
|
||||
pub(crate) b: u32,
|
||||
pub(crate) c: u32,
|
||||
pub(crate) d: u32,
|
||||
pub(crate) opacity: u8,
|
||||
pub(crate) flags: u8,
|
||||
// 16-bit padding here, reserved for future use
|
||||
width: i32,
|
||||
height: i32,
|
||||
pub(crate) width: i32,
|
||||
pub(crate) height: i32,
|
||||
}
|
||||
|
||||
impl From<RawImageFillData> for ImageFill {
|
||||
|
||||
@ -2,10 +2,14 @@ use macros::{wasm_error, ToJs};
|
||||
|
||||
use crate::math::{Matrix, Point, Rect};
|
||||
use crate::mem;
|
||||
use crate::shapes::{Shape, TextContent, TextPositionWithAffinity, Type, VerticalAlign};
|
||||
use crate::shapes::{
|
||||
FontStyle, Shape, TextContent, TextDecoration, TextPositionWithAffinity, TextTransform, Type,
|
||||
VerticalAlign,
|
||||
};
|
||||
use crate::state::{TextEditorEvent, TextSelection};
|
||||
use crate::utils::uuid_from_u32_quartet;
|
||||
use crate::utils::uuid_to_u32_quartet;
|
||||
use crate::wasm::fills::write_fills_to_bytes;
|
||||
use crate::wasm::text::helpers as text_helpers;
|
||||
use crate::{with_state, with_state_mut, STATE};
|
||||
use skia_safe::Color;
|
||||
@ -517,6 +521,87 @@ pub extern "C" fn text_editor_get_cursor_rect() -> *mut u8 {
|
||||
})
|
||||
}
|
||||
|
||||
/// Serialize a skia TextAlign to its raw u32 representation.
|
||||
fn text_align_to_raw(align: &skia_safe::textlayout::TextAlign) -> u32 {
|
||||
use skia_safe::textlayout::TextAlign;
|
||||
match *align {
|
||||
TextAlign::Left => 0,
|
||||
TextAlign::Center => 1,
|
||||
TextAlign::Right => 2,
|
||||
TextAlign::Justify => 3,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize a skia TextDirection to its raw u32 representation.
|
||||
fn text_direction_to_raw(dir: &skia_safe::textlayout::TextDirection) -> u32 {
|
||||
use skia_safe::textlayout::TextDirection;
|
||||
match *dir {
|
||||
TextDirection::LTR => 0,
|
||||
TextDirection::RTL => 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize an Option<TextDecoration> to its raw u32 representation.
|
||||
fn text_decoration_to_raw(dec: &Option<TextDecoration>) -> u32 {
|
||||
match dec.as_ref() {
|
||||
None => 0,
|
||||
Some(d) if *d == TextDecoration::UNDERLINE => 1,
|
||||
Some(d) if *d == TextDecoration::LINE_THROUGH => 2,
|
||||
Some(d) if *d == TextDecoration::OVERLINE => 3,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize an Option<TextTransform> to its raw u32 representation.
|
||||
fn text_transform_to_raw(transform: &Option<TextTransform>) -> u32 {
|
||||
match transform.as_ref() {
|
||||
None => 0,
|
||||
Some(TextTransform::Uppercase) => 1,
|
||||
Some(TextTransform::Lowercase) => 2,
|
||||
Some(TextTransform::Capitalize) => 3,
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize a FontStyle to its raw u32 representation.
|
||||
fn font_style_to_raw(style: &FontStyle) -> u32 {
|
||||
match *style {
|
||||
FontStyle::Normal => 0,
|
||||
FontStyle::Italic => 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Binary layout for TextEditorStyles (all offsets in bytes, 4-byte aligned):
|
||||
///
|
||||
/// Offset Size Field
|
||||
/// ------ ---- -----
|
||||
/// 0 4 vertical_align (u32)
|
||||
/// 4 4 text_align.state (u32)
|
||||
/// 8 4 text_align.value (u32)
|
||||
/// 12 4 text_direction.state (u32)
|
||||
/// 16 4 text_direction.value (u32)
|
||||
/// 20 4 text_decoration.state (u32)
|
||||
/// 24 4 text_decoration.value (u32)
|
||||
/// 28 4 text_transform.state (u32)
|
||||
/// 32 4 text_transform.value (u32)
|
||||
/// 36 4 font_size.state (u32)
|
||||
/// 40 4 font_size.value (f32)
|
||||
/// 44 4 font_weight.state (u32)
|
||||
/// 48 4 font_weight.value (i32)
|
||||
/// 52 4 line_height.state (u32)
|
||||
/// 56 4 line_height.value (f32)
|
||||
/// 60 4 letter_spacing.state (u32)
|
||||
/// 64 4 letter_spacing.value (f32)
|
||||
/// 68 4 font_family.state (u32)
|
||||
/// 72 16 font_family.id (Uuid: 4×u32 LE)
|
||||
/// 88 4 font_family.style (u32)
|
||||
/// 92 4 font_family.weight (u32)
|
||||
/// 96 4 font_variant_id.state (u32)
|
||||
/// 100 16 font_variant_id.value (Uuid: 4×u32 LE)
|
||||
/// 116 4 fills_count (u32)
|
||||
/// 120+ var fills (RAW_FILL_DATA_SIZE each, same format as set_shape_fills)
|
||||
const STYLES_HEADER_SIZE: usize = 120;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn text_editor_get_current_styles() -> *mut u8 {
|
||||
with_state_mut!(state, {
|
||||
@ -528,38 +613,98 @@ pub extern "C" fn text_editor_get_current_styles() -> *mut u8 {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
let Some(shape_id) = state.text_editor_state.active_shape_id else {
|
||||
let Some(_shape_id) = state.text_editor_state.active_shape_id else {
|
||||
return std::ptr::null_mut();
|
||||
};
|
||||
|
||||
let Some(shape) = state.shapes.get(&shape_id) else {
|
||||
return std::ptr::null_mut();
|
||||
};
|
||||
let styles = &state.text_editor_state.current_styles;
|
||||
|
||||
let Type::Text(text_content) = &shape.shape_type else {
|
||||
return std::ptr::null_mut();
|
||||
};
|
||||
// Serialize fills using the existing RawFillData format
|
||||
let fill_bytes = write_fills_to_bytes(styles.fills.clone());
|
||||
let total_size = STYLES_HEADER_SIZE + fill_bytes.len();
|
||||
let mut bytes = vec![0u8; total_size];
|
||||
|
||||
let mut bytes = vec![0u8; 1024];
|
||||
bytes[0..4].copy_from_slice(&u32::to_le_bytes(state.text_editor_state.current_styles.vertical_align as u32));
|
||||
bytes[4..8].copy_from_slice(&u32::to_le_bytes(*state.text_editor_state.current_styles.text_align.state() as u32));
|
||||
bytes[8..12].copy_from_slice(&u32::to_le_bytes(*state.text_editor_state.current_styles.text_direction.state() as u32));
|
||||
bytes[12..16].copy_from_slice(&u32::to_le_bytes(*state.text_editor_state.current_styles.text_decoration.state() as u32));
|
||||
bytes[16..20].copy_from_slice(&u32::to_le_bytes(*state.text_editor_state.current_styles.text_transform.state() as u32));
|
||||
bytes[20..24].copy_from_slice(&u32::to_le_bytes(*state.text_editor_state.current_styles.font_family.state() as u32));
|
||||
bytes[24..28].copy_from_slice(&u32::to_le_bytes(*state.text_editor_state.current_styles.font_size.state() as u32));
|
||||
bytes[28..32].copy_from_slice(&u32::to_le_bytes(*state.text_editor_state.current_styles.font_weight.state() as u32));
|
||||
bytes[32..36].copy_from_slice(&u32::to_le_bytes(*state.text_editor_state.current_styles.font_variant_id.state() as u32));
|
||||
bytes[36..40].copy_from_slice(&u32::to_le_bytes(*state.text_editor_state.current_styles.line_height.state() as u32));
|
||||
bytes[40..44].copy_from_slice(&u32::to_le_bytes(*state.text_editor_state.current_styles.letter_spacing.state() as u32));
|
||||
bytes[44..48].copy_from_slice(&usize::to_le_bytes(state.text_editor_state.current_styles.fills.len()));
|
||||
// vertical_align
|
||||
bytes[0..4].copy_from_slice(&u32::to_le_bytes(styles.vertical_align as u32));
|
||||
|
||||
let offset: usize = 48;
|
||||
for fill in state.text_editor_state.current_styles.fills.iter() {
|
||||
|
||||
// text_align: state + value
|
||||
bytes[4..8].copy_from_slice(&u32::to_le_bytes(*styles.text_align.state() as u32));
|
||||
if let Some(val) = styles.text_align.value() {
|
||||
bytes[8..12].copy_from_slice(&u32::to_le_bytes(text_align_to_raw(val)));
|
||||
}
|
||||
|
||||
std::ptr::null_mut()
|
||||
// text_direction: state + value
|
||||
bytes[12..16].copy_from_slice(&u32::to_le_bytes(*styles.text_direction.state() as u32));
|
||||
if let Some(val) = styles.text_direction.value() {
|
||||
bytes[16..20].copy_from_slice(&u32::to_le_bytes(text_direction_to_raw(val)));
|
||||
}
|
||||
|
||||
// text_decoration: state + value
|
||||
bytes[20..24].copy_from_slice(&u32::to_le_bytes(*styles.text_decoration.state() as u32));
|
||||
// text_decoration merges Option<TextDecoration>, so value() is Option<Option<TextDecoration>>
|
||||
// When Single: value() is Some(inner) where inner is the Option<TextDecoration>
|
||||
if styles.text_decoration.is_single() {
|
||||
let inner = styles.text_decoration.value();
|
||||
// inner is &Option<TextDecoration>: the Some/None tells us Single vs not,
|
||||
// but for text_decoration the merged value IS an Option<TextDecoration>.
|
||||
// So we serialize the inner Option directly.
|
||||
bytes[24..28].copy_from_slice(&u32::to_le_bytes(text_decoration_to_raw(inner)));
|
||||
}
|
||||
|
||||
// text_transform: state + value (same pattern as text_decoration)
|
||||
bytes[28..32].copy_from_slice(&u32::to_le_bytes(*styles.text_transform.state() as u32));
|
||||
if styles.text_transform.is_single() {
|
||||
let inner = styles.text_transform.value();
|
||||
bytes[32..36].copy_from_slice(&u32::to_le_bytes(text_transform_to_raw(inner)));
|
||||
}
|
||||
|
||||
// font_size: state + value (f32)
|
||||
bytes[36..40].copy_from_slice(&u32::to_le_bytes(*styles.font_size.state() as u32));
|
||||
if let Some(val) = styles.font_size.value() {
|
||||
bytes[40..44].copy_from_slice(&f32::to_le_bytes(*val));
|
||||
}
|
||||
|
||||
// font_weight: state + value (i32)
|
||||
bytes[44..48].copy_from_slice(&u32::to_le_bytes(*styles.font_weight.state() as u32));
|
||||
if let Some(val) = styles.font_weight.value() {
|
||||
bytes[48..52].copy_from_slice(&i32::to_le_bytes(*val));
|
||||
}
|
||||
|
||||
// line_height: state + value (f32)
|
||||
bytes[52..56].copy_from_slice(&u32::to_le_bytes(*styles.line_height.state() as u32));
|
||||
if let Some(val) = styles.line_height.value() {
|
||||
bytes[56..60].copy_from_slice(&f32::to_le_bytes(*val));
|
||||
}
|
||||
|
||||
// letter_spacing: state + value (f32)
|
||||
bytes[60..64].copy_from_slice(&u32::to_le_bytes(*styles.letter_spacing.state() as u32));
|
||||
if let Some(val) = styles.letter_spacing.value() {
|
||||
bytes[64..68].copy_from_slice(&f32::to_le_bytes(*val));
|
||||
}
|
||||
|
||||
// font_family: state + id (16 bytes) + style (u32) + weight (u32)
|
||||
bytes[68..72].copy_from_slice(&u32::to_le_bytes(*styles.font_family.state() as u32));
|
||||
if let Some(family) = styles.font_family.value() {
|
||||
let id_bytes: [u8; 16] = family.id().into();
|
||||
bytes[72..88].copy_from_slice(&id_bytes);
|
||||
bytes[88..92].copy_from_slice(&u32::to_le_bytes(font_style_to_raw(&family.style())));
|
||||
bytes[92..96].copy_from_slice(&u32::to_le_bytes(family.weight()));
|
||||
}
|
||||
|
||||
// font_variant_id: state + uuid (16 bytes)
|
||||
bytes[96..100].copy_from_slice(&u32::to_le_bytes(*styles.font_variant_id.state() as u32));
|
||||
if let Some(variant_id) = styles.font_variant_id.value() {
|
||||
let id_bytes: [u8; 16] = (*variant_id).into();
|
||||
bytes[100..116].copy_from_slice(&id_bytes);
|
||||
}
|
||||
|
||||
// fills_count + fill data
|
||||
bytes[116..120].copy_from_slice(&u32::to_le_bytes(styles.fills.len() as u32));
|
||||
if !fill_bytes.is_empty() {
|
||||
bytes[STYLES_HEADER_SIZE..].copy_from_slice(&fill_bytes);
|
||||
}
|
||||
|
||||
mem::write_bytes(bytes)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user