mirror of
https://github.com/penpot/penpot.git
synced 2026-02-12 14:42:56 +00:00
♻️ 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:
@@ -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))))
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user