♻️ Decouple serialization from text/layout models" (#7360)

* ♻️ Move text serialization code to wasm module

* ♻️ Add serializer for TextAlign

* ♻️ Add serializers for TextDirection and TextDecoration

* ♻️ Add serializer for TextTransform

* ♻️ Remove unused font_style from TextLeaf model

* ♻️ Refactor parsing of TextLeaf from bytes

* ♻️ Decouple tight serialization of Paragraph
This commit is contained in:
Belén Albeza
2025-09-25 16:54:07 +02:00
committed by GitHub
parent 3827aa6bd4
commit 361bdb4a04
5 changed files with 348 additions and 297 deletions

View File

@@ -1068,6 +1068,10 @@
:sizing (unchecked-get module "RawSizing")
:vertical-align (unchecked-get module "RawVerticalAlign")
:fill-data (unchecked-get module "RawFillData")
:text-align (unchecked-get module "RawTextAlign")
:text-direction (unchecked-get module "RawTextDirection")
:text-decoration (unchecked-get module "RawTextDecoration")
:text-transform (unchecked-get module "RawTextTransform")
:segment-data (unchecked-get module "RawSegmentData")}]
(set! wasm/serializers serializers)
(default))))

View File

@@ -205,46 +205,33 @@
default (unchecked-get values "top")]
(d/nilv (unchecked-get values (d/name vertical-align)) default)))
;; TODO: Find/Create a Rust enum for this
(defn translate-text-align
[text-align]
(case text-align
"left" 0
"center" 1
"right" 2
"justify" 3
0))
(let [values (unchecked-get wasm/serializers "text-align")
default (unchecked-get values "left")]
(d/nilv (unchecked-get values (d/name text-align)) default)))
;; TODO: Find/Create a Rust enum for this
(defn translate-text-transform
[text-transform]
(case text-transform
"none" 0
"uppercase" 1
"lowercase" 2
"capitalize" 3
nil 0
0))
(let [values (unchecked-get wasm/serializers "text-transform")
default (unchecked-get values "none")]
(d/nilv (unchecked-get values (d/name text-transform)) default)))
;; TODO: Find/Create a Rust enum for this
(defn translate-text-decoration
[text-decoration]
(case text-decoration
"none" 0
"underline" 1
"line-through" 2
"overline" 3
nil 0
0))
(let [values (unchecked-get wasm/serializers "text-decoration")
default (unchecked-get values "none")]
(d/nilv (unchecked-get values (d/name text-decoration)) default)))
;; TODO: Find/Create a Rust enum for this
(defn translate-text-direction
[text-direction]
(case text-direction
"ltr" 0
"rtl" 1
nil 0
0))
(let [values (unchecked-get wasm/serializers "text-direction")
default (unchecked-get values "ltr")]
(d/nilv (unchecked-get values (d/name text-direction)) default)))
(defn translate-font-style
[font-style]

View File

@@ -15,9 +15,7 @@ use std::collections::HashSet;
use super::FontFamily;
use crate::shapes::{self, merge_fills};
use crate::utils::uuid_from_u32;
use crate::utils::{get_fallback_fonts, get_font_collection};
use crate::wasm::fills::parse_fills_from_bytes;
use crate::Uuid;
// TODO: maybe move this to the wasm module?
@@ -368,15 +366,25 @@ impl Default for TextContent {
}
}
pub type TextAlign = skia::textlayout::TextAlign;
pub type TextDirection = skia::textlayout::TextDirection;
pub type TextDecoration = skia::textlayout::TextDecoration;
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum TextTransform {
Lowercase,
Uppercase,
Capitalize,
}
// FIXME: Rethink this type. We'll probably need to move the serialization to the
// wasm moduel and store here meaningful model values (and/or skia type aliases)
// wasm module and store here meaningful model values (and/or skia type aliases)
#[derive(Debug, PartialEq, Clone)]
pub struct Paragraph {
num_leaves: u32,
text_align: u8,
text_direction: u8,
text_decoration: u8,
text_transform: u8,
text_align: TextAlign,
text_direction: TextDirection,
text_decoration: Option<TextDecoration>,
text_transform: Option<TextTransform>,
line_height: f32,
letter_spacing: f32,
typography_ref_file: Uuid,
@@ -387,11 +395,10 @@ pub struct Paragraph {
impl Default for Paragraph {
fn default() -> Self {
Self {
num_leaves: 0,
text_align: 0,
text_direction: 0,
text_decoration: 0,
text_transform: 0,
text_align: TextAlign::default(),
text_direction: TextDirection::LTR,
text_decoration: None,
text_transform: None,
line_height: 1.0,
letter_spacing: 0.0,
typography_ref_file: Uuid::nil(),
@@ -404,11 +411,10 @@ impl Default for Paragraph {
impl Paragraph {
#[allow(clippy::too_many_arguments)]
pub fn new(
num_leaves: u32,
text_align: u8,
text_direction: u8,
text_decoration: u8,
text_transform: u8,
text_align: TextAlign,
text_direction: TextDirection,
text_decoration: Option<TextDecoration>,
text_transform: Option<TextTransform>,
line_height: f32,
letter_spacing: f32,
typography_ref_file: Uuid,
@@ -416,7 +422,6 @@ impl Paragraph {
children: Vec<TextLeaf>,
) -> Self {
Self {
num_leaves,
text_align,
text_direction,
text_decoration,
@@ -446,18 +451,8 @@ impl Paragraph {
// FIXME: move serialization to wasm module
pub fn paragraph_to_style(&self) -> ParagraphStyle {
let mut style = ParagraphStyle::default();
style.set_text_align(match self.text_align {
0 => skia::textlayout::TextAlign::Left,
1 => skia::textlayout::TextAlign::Center,
2 => skia::textlayout::TextAlign::Right,
3 => skia::textlayout::TextAlign::Justify,
_ => skia::textlayout::TextAlign::Left,
});
style.set_text_direction(match self.text_direction {
0 => skia::textlayout::TextDirection::LTR,
1 => skia::textlayout::TextDirection::RTL,
_ => skia::textlayout::TextDirection::LTR,
});
style.set_text_align(self.text_align);
style.set_text_direction(self.text_direction);
if !self.children.is_empty() {
let reference_child = self
@@ -502,12 +497,11 @@ pub struct TextLeaf {
font_family: FontFamily,
font_size: f32,
letter_spacing: f32,
font_style: u8,
font_weight: i32,
font_variant_id: Uuid,
text_decoration: u8,
text_transform: u8,
text_direction: u8,
text_decoration: Option<TextDecoration>,
text_transform: Option<TextTransform>,
text_direction: TextDirection,
fills: Vec<shapes::Fill>,
}
@@ -518,10 +512,9 @@ impl TextLeaf {
font_family: FontFamily,
font_size: f32,
letter_spacing: f32,
font_style: u8,
text_decoration: u8,
text_transform: u8,
text_direction: u8,
text_decoration: Option<TextDecoration>,
text_transform: Option<TextTransform>,
text_direction: TextDirection,
font_weight: i32,
font_variant_id: Uuid,
fills: Vec<shapes::Fill>,
@@ -531,7 +524,6 @@ impl TextLeaf {
font_family,
font_size,
letter_spacing,
font_style,
text_decoration,
text_transform,
text_direction,
@@ -541,6 +533,10 @@ impl TextLeaf {
}
}
pub fn set_text(&mut self, text: String) {
self.text = text;
}
pub fn fills(&self) -> &[shapes::Fill] {
&self.fills
}
@@ -568,11 +564,8 @@ impl TextLeaf {
style.set_half_leading(false);
style.set_decoration_type(match self.text_decoration {
0 => skia::textlayout::TextDecoration::NO_DECORATION,
1 => skia::textlayout::TextDecoration::UNDERLINE,
2 => skia::textlayout::TextDecoration::LINE_THROUGH,
3 => skia::textlayout::TextDecoration::OVERLINE,
_ => skia::textlayout::TextDecoration::NO_DECORATION,
Some(text_decoration) => text_decoration,
None => skia::textlayout::TextDecoration::NO_DECORATION,
});
// Trick to avoid showing the text decoration
@@ -611,11 +604,8 @@ impl TextLeaf {
style.set_font_size(self.font_size);
style.set_letter_spacing(self.letter_spacing);
style.set_decoration_type(match self.text_decoration {
0 => skia::textlayout::TextDecoration::NO_DECORATION,
1 => skia::textlayout::TextDecoration::UNDERLINE,
2 => skia::textlayout::TextDecoration::LINE_THROUGH,
3 => skia::textlayout::TextDecoration::OVERLINE,
_ => skia::textlayout::TextDecoration::NO_DECORATION,
Some(text_decoration) => text_decoration,
None => skia::textlayout::TextDecoration::NO_DECORATION,
});
style
}
@@ -626,9 +616,9 @@ impl TextLeaf {
pub fn apply_text_transform(&self) -> String {
match self.text_transform {
1 => self.text.to_uppercase(),
2 => self.text.to_lowercase(),
3 => self
Some(TextTransform::Uppercase) => self.text.to_uppercase(),
Some(TextTransform::Lowercase) => self.text.to_lowercase(),
Some(TextTransform::Capitalize) => self
.text
.split_whitespace()
.map(|word| {
@@ -640,7 +630,7 @@ impl TextLeaf {
})
.collect::<Vec<_>>()
.join(" "),
_ => self.text.clone(),
None => self.text.clone(),
}
}
@@ -655,215 +645,3 @@ impl TextLeaf {
})
}
}
const RAW_PARAGRAPH_DATA_SIZE: usize = std::mem::size_of::<RawParagraphData>();
const RAW_LEAF_DATA_SIZE: usize = std::mem::size_of::<RawTextLeaf>();
pub const RAW_LEAF_FILLS_SIZE: usize = 160;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct RawTextLeaf {
font_style: u8,
text_decoration: u8,
text_transform: u8,
font_size: f32,
letter_spacing: f32,
font_weight: i32,
font_id: [u32; 4],
font_family: [u8; 4],
font_variant_id: [u32; 4],
text_length: u32,
total_fills: u32,
}
impl From<[u8; RAW_LEAF_DATA_SIZE]> for RawTextLeaf {
fn from(bytes: [u8; RAW_LEAF_DATA_SIZE]) -> Self {
unsafe { std::mem::transmute(bytes) }
}
}
impl TryFrom<&[u8]> for RawTextLeaf {
type Error = String;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let data: [u8; RAW_LEAF_DATA_SIZE] = bytes
.get(0..RAW_LEAF_DATA_SIZE)
.and_then(|slice| slice.try_into().ok())
.ok_or("Invalid text leaf data".to_string())?;
Ok(RawTextLeaf::from(data))
}
}
#[allow(dead_code)]
#[repr(C)]
#[derive(Debug, Clone)]
pub struct RawTextLeafData {
font_style: u8,
text_decoration: u8,
text_transform: u8,
text_direction: u8,
font_size: f32,
letter_spacing: f32,
font_weight: i32,
font_id: [u32; 4],
font_family: [u8; 4],
font_variant_id: [u32; 4],
text_length: u32,
total_fills: u32,
fills: Vec<shapes::Fill>,
}
impl From<&[u8]> for RawTextLeafData {
fn from(bytes: &[u8]) -> Self {
let text_leaf: RawTextLeaf = RawTextLeaf::try_from(bytes).unwrap();
let total_fills = text_leaf.total_fills as usize;
// Use checked_mul to prevent overflow
let fills_size = total_fills
.checked_mul(RAW_LEAF_FILLS_SIZE)
.expect("Overflow occurred while calculating fills size");
let fills_start = RAW_LEAF_DATA_SIZE;
let fills_end = fills_start + fills_size;
let buffer = &bytes[fills_start..fills_end];
let fills = parse_fills_from_bytes(buffer, total_fills);
Self {
font_style: text_leaf.font_style,
text_decoration: text_leaf.text_decoration,
text_transform: text_leaf.text_transform,
text_direction: 0, // TODO: Añadirlo
font_size: text_leaf.font_size,
letter_spacing: text_leaf.letter_spacing,
font_weight: text_leaf.font_weight,
font_id: text_leaf.font_id,
font_family: text_leaf.font_family,
font_variant_id: text_leaf.font_variant_id,
text_length: text_leaf.text_length,
total_fills: text_leaf.total_fills,
fills,
}
}
}
#[repr(C)]
#[repr(align(4))]
#[derive(Debug, Clone, Copy)]
pub struct RawParagraphData {
num_leaves: u32,
text_align: u8,
text_direction: u8,
text_decoration: u8,
text_transform: u8,
line_height: f32,
letter_spacing: f32,
typography_ref_file: [u32; 4],
typography_ref_id: [u32; 4],
}
impl From<[u8; RAW_PARAGRAPH_DATA_SIZE]> for RawParagraphData {
fn from(bytes: [u8; RAW_PARAGRAPH_DATA_SIZE]) -> Self {
unsafe { std::mem::transmute(bytes) }
}
}
impl TryFrom<&[u8]> for RawParagraphData {
type Error = String;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let data: [u8; RAW_PARAGRAPH_DATA_SIZE] = bytes
.get(0..RAW_PARAGRAPH_DATA_SIZE)
.and_then(|slice| slice.try_into().ok())
.ok_or("Invalid paragraph data".to_string())?;
Ok(RawParagraphData::from(data))
}
}
impl RawTextData {
fn text_from_bytes(buffer: &[u8], offset: usize, text_length: u32) -> (String, usize) {
let text_length = text_length as usize;
let text_end = offset + text_length;
if text_end > buffer.len() {
panic!(
"Invalid text range: offset={}, text_end={}, buffer_len={}",
offset,
text_end,
buffer.len()
);
}
let text_utf8 = buffer[offset..text_end].to_vec();
if text_utf8.is_empty() {
return (String::new(), text_end);
}
let text = String::from_utf8_lossy(&text_utf8).to_string();
(text, text_end)
}
}
// TODO: maybe move this to the wasm module?
pub struct RawTextData {
pub paragraph: Paragraph,
}
// TODO: maybe move this to the wasm module?
impl From<&Vec<u8>> for RawTextData {
fn from(bytes: &Vec<u8>) -> Self {
let paragraph = RawParagraphData::try_from(&bytes[..RAW_PARAGRAPH_DATA_SIZE]).unwrap();
let mut offset = RAW_PARAGRAPH_DATA_SIZE;
let mut raw_text_leaves: Vec<RawTextLeafData> = Vec::new();
let mut text_leaves: Vec<TextLeaf> = Vec::new();
for _ in 0..paragraph.num_leaves {
let text_leaf = RawTextLeafData::from(&bytes[offset..]);
raw_text_leaves.push(text_leaf.clone());
offset += RAW_LEAF_DATA_SIZE + (text_leaf.total_fills as usize * RAW_LEAF_FILLS_SIZE);
}
for text_leaf in raw_text_leaves.iter() {
let (text, new_offset) =
RawTextData::text_from_bytes(bytes, offset, text_leaf.text_length);
offset = new_offset;
let font_id = uuid_from_u32(text_leaf.font_id);
let font_variant_id = uuid_from_u32(text_leaf.font_variant_id);
let font_style = crate::wasm::fonts::RawFontStyle::from(text_leaf.font_style);
let font_family =
FontFamily::new(font_id, text_leaf.font_weight as u32, font_style.into());
let new_text_leaf = TextLeaf::new(
text,
font_family,
text_leaf.font_size,
text_leaf.letter_spacing,
text_leaf.font_style,
text_leaf.text_decoration,
text_leaf.text_transform,
text_leaf.text_direction,
text_leaf.font_weight,
font_variant_id,
text_leaf.fills.clone(),
);
text_leaves.push(new_text_leaf);
}
let typography_ref_file = uuid_from_u32(paragraph.typography_ref_file);
let typography_ref_id = uuid_from_u32(paragraph.typography_ref_id);
let paragraph = Paragraph::new(
paragraph.num_leaves,
paragraph.text_align,
paragraph.text_direction,
paragraph.text_decoration,
paragraph.text_transform,
paragraph.line_height,
paragraph.letter_spacing,
typography_ref_file,
typography_ref_id,
text_leaves.clone(),
);
Self { paragraph }
}
}

View File

@@ -53,6 +53,7 @@ impl TryFrom<&[u8]> for RawFillData {
}
}
// FIXME: return Result
pub fn parse_fills_from_bytes(buffer: &[u8], num_fills: usize) -> Vec<shapes::Fill> {
buffer
.chunks_exact(RAW_FILL_DATA_SIZE)

View File

@@ -1,9 +1,290 @@
use macros::ToJs;
use super::fonts::RawFontStyle;
use crate::mem;
use crate::shapes::{GrowType, RawTextData, Type};
use crate::shapes::{
self, GrowType, TextAlign, TextDecoration, TextDirection, TextTransform, Type,
};
use crate::utils::uuid_from_u32;
use crate::{with_current_shape_mut, STATE};
const RAW_LEAF_DATA_SIZE: usize = std::mem::size_of::<RawTextLeafAttrs>();
pub const RAW_LEAF_FILLS_SIZE: usize = 160;
const RAW_PARAGRAPH_DATA_SIZE: usize = std::mem::size_of::<RawParagraphData>();
#[derive(Debug, PartialEq, Clone, Copy, ToJs)]
#[repr(u8)]
pub enum RawTextAlign {
Left = 0,
Center = 1,
Right = 2,
Justify = 3,
}
impl From<RawTextAlign> for TextAlign {
fn from(value: RawTextAlign) -> Self {
match value {
RawTextAlign::Left => TextAlign::Left,
RawTextAlign::Center => TextAlign::Center,
RawTextAlign::Right => TextAlign::Right,
RawTextAlign::Justify => TextAlign::Justify,
}
}
}
#[derive(Debug, PartialEq, Clone, Copy, ToJs)]
#[repr(u8)]
pub enum RawTextDirection {
Ltr = 0,
Rtl = 1,
}
impl From<RawTextDirection> for TextDirection {
fn from(value: RawTextDirection) -> Self {
match value {
RawTextDirection::Ltr => TextDirection::LTR,
RawTextDirection::Rtl => TextDirection::RTL,
}
}
}
#[derive(Debug, PartialEq, Clone, Copy, ToJs)]
#[repr(u8)]
pub enum RawTextDecoration {
None = 0,
Underline = 1,
LineThrough = 2,
Overline = 3,
}
impl From<RawTextDecoration> for Option<TextDecoration> {
fn from(value: RawTextDecoration) -> Self {
match value {
RawTextDecoration::None => None,
RawTextDecoration::Underline => Some(TextDecoration::UNDERLINE),
RawTextDecoration::LineThrough => Some(TextDecoration::LINE_THROUGH),
RawTextDecoration::Overline => Some(TextDecoration::OVERLINE),
}
}
}
#[derive(Debug, PartialEq, Clone, Copy, ToJs)]
#[repr(u8)]
pub enum RawTextTransform {
None = 0,
Uppercase = 1,
Lowercase = 2,
Capitalize = 3,
}
impl From<RawTextTransform> for Option<TextTransform> {
fn from(value: RawTextTransform) -> Self {
match value {
RawTextTransform::None => None,
RawTextTransform::Uppercase => Some(TextTransform::Uppercase),
RawTextTransform::Lowercase => Some(TextTransform::Lowercase),
RawTextTransform::Capitalize => Some(TextTransform::Capitalize),
}
}
}
#[repr(C)]
#[repr(align(4))]
#[derive(Debug, Clone, Copy)]
pub struct RawParagraphData {
leaf_count: u32,
text_align: RawTextAlign,
text_direction: RawTextDirection,
text_decoration: RawTextDecoration,
text_transform: RawTextTransform,
line_height: f32,
letter_spacing: f32,
typography_ref_file: [u32; 4],
typography_ref_id: [u32; 4],
}
impl From<[u8; RAW_PARAGRAPH_DATA_SIZE]> for RawParagraphData {
fn from(bytes: [u8; RAW_PARAGRAPH_DATA_SIZE]) -> Self {
unsafe { std::mem::transmute(bytes) }
}
}
impl TryFrom<&[u8]> for RawParagraphData {
type Error = String;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let data: [u8; RAW_PARAGRAPH_DATA_SIZE] = bytes
.get(0..RAW_PARAGRAPH_DATA_SIZE)
.and_then(|slice| slice.try_into().ok())
.ok_or("Invalid paragraph data".to_string())?;
Ok(RawParagraphData::from(data))
}
}
// FIXME: Merge this struct with RawTextLeaf once we cap the amount of fills a text shape has
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct RawTextLeafAttrs {
font_style: RawFontStyle,
text_decoration: RawTextDecoration,
text_transform: RawTextTransform,
text_direction: RawTextDirection,
font_size: f32,
letter_spacing: f32,
font_weight: i32,
font_id: [u32; 4],
font_family: [u8; 4],
font_variant_id: [u32; 4], // TODO: maybe add RawUUID type
text_length: u32,
fill_count: u32, // FIXME: we should cap the amount of fills a text shape has
}
impl From<[u8; RAW_LEAF_DATA_SIZE]> for RawTextLeafAttrs {
fn from(bytes: [u8; RAW_LEAF_DATA_SIZE]) -> Self {
unsafe { std::mem::transmute(bytes) }
}
}
impl TryFrom<&[u8]> for RawTextLeafAttrs {
type Error = String;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let data: [u8; RAW_LEAF_DATA_SIZE] = bytes
.get(0..RAW_LEAF_DATA_SIZE)
.and_then(|slice| slice.try_into().ok())
.ok_or("Invalid text leaf data".to_string())?;
Ok(RawTextLeafAttrs::from(data))
}
}
#[allow(dead_code)]
#[repr(C)]
#[derive(Debug, Clone)]
pub struct RawTextLeaf {
attrs: RawTextLeafAttrs,
raw_fills: Vec<u8>, // FIXME: remove this once we cap the amount of fills a text shape has
}
impl TryFrom<&[u8]> for RawTextLeaf {
// TODO: use a proper error type
type Error = String;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let raw_attrs: RawTextLeafAttrs = RawTextLeafAttrs::try_from(bytes)?;
let total_fills = raw_attrs.fill_count as usize;
// Use checked_mul to prevent overflow
let fills_size = total_fills
.checked_mul(RAW_LEAF_FILLS_SIZE)
.ok_or("Overflow occurred while calculating fills size")?;
let fills_start = RAW_LEAF_DATA_SIZE;
let fills_end = fills_start + fills_size;
let raw_fills = &bytes[fills_start..fills_end];
Ok(Self {
attrs: raw_attrs,
raw_fills: raw_fills.to_vec(),
})
}
}
impl From<RawTextLeaf> for shapes::TextLeaf {
fn from(value: RawTextLeaf) -> Self {
let text = String::default();
let font_family = shapes::FontFamily::new(
uuid_from_u32(value.attrs.font_id),
value.attrs.font_weight as u32,
value.attrs.font_style.into(),
);
let fills =
super::fills::parse_fills_from_bytes(&value.raw_fills, value.attrs.fill_count as usize);
Self::new(
text,
font_family,
value.attrs.font_size,
value.attrs.letter_spacing,
value.attrs.text_decoration.into(),
value.attrs.text_transform.into(),
value.attrs.text_direction.into(),
value.attrs.font_weight,
uuid_from_u32(value.attrs.font_variant_id),
fills,
)
}
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct RawParagraph {
attrs: RawParagraphData,
leaves: Vec<RawTextLeaf>,
text_buffer: Vec<u8>,
}
impl TryFrom<&Vec<u8>> for RawParagraph {
// TODO: use a proper error type
type Error = String;
fn try_from(bytes: &Vec<u8>) -> Result<Self, Self::Error> {
let attrs = RawParagraphData::try_from(&bytes[..RAW_PARAGRAPH_DATA_SIZE])?;
let mut offset = RAW_PARAGRAPH_DATA_SIZE;
let mut raw_text_leaves: Vec<RawTextLeaf> = Vec::new();
for _ in 0..attrs.leaf_count {
let text_leaf = RawTextLeaf::try_from(&bytes[offset..])?;
let leaf_size =
RAW_LEAF_DATA_SIZE + (text_leaf.attrs.fill_count as usize * RAW_LEAF_FILLS_SIZE);
offset += leaf_size;
raw_text_leaves.push(text_leaf);
}
let text_buffer = &bytes[offset..];
Ok(Self {
attrs,
leaves: raw_text_leaves,
text_buffer: text_buffer.to_vec(),
})
}
}
impl From<RawParagraph> for shapes::Paragraph {
fn from(value: RawParagraph) -> Self {
let typography_ref_file = uuid_from_u32(value.attrs.typography_ref_file);
let typography_ref_id = uuid_from_u32(value.attrs.typography_ref_id);
let mut leaves = vec![];
let mut offset = 0;
for raw_leaf in value.leaves.into_iter() {
let delta = raw_leaf.attrs.text_length as usize;
let text_buffer = &value.text_buffer[offset..offset + delta];
let mut leaf = shapes::TextLeaf::from(raw_leaf);
if !text_buffer.is_empty() {
leaf.set_text(String::from_utf8_lossy(text_buffer).to_string());
}
leaves.push(leaf);
offset += delta;
}
shapes::Paragraph::new(
value.attrs.text_align.into(),
value.attrs.text_direction.into(),
value.attrs.text_decoration.into(),
value.attrs.text_transform.into(),
value.attrs.line_height,
value.attrs.letter_spacing,
typography_ref_file,
typography_ref_id,
leaves,
)
}
}
#[derive(Debug, PartialEq, Clone, Copy, ToJs)]
#[repr(u8)]
#[allow(dead_code)]
@@ -40,9 +321,9 @@ pub extern "C" fn clear_shape_text() {
pub extern "C" fn set_shape_text_content() {
let bytes = mem::bytes();
with_current_shape_mut!(state, |shape: &mut Shape| {
let raw_text_data = RawTextData::from(&bytes);
let raw_text_data = RawParagraph::try_from(&bytes).unwrap();
shape
.add_paragraph(raw_text_data.paragraph)
.add_paragraph(raw_text_data.into())
.expect("Failed to add paragraph");
});
mem::free_bytes();