use skia_safe::{self as skia}; use crate::render::BlendMode; use crate::uuid::Uuid; use std::collections::{HashMap, HashSet}; mod blurs; mod bools; mod corners; mod fills; mod fonts; mod frames; mod groups; mod layouts; mod modifiers; mod paths; mod rects; mod shadows; mod strokes; mod svgraw; mod text; mod transform; pub use blurs::*; pub use bools::*; pub use corners::*; pub use fills::*; pub use fonts::*; pub use frames::*; pub use groups::*; pub use layouts::*; pub use modifiers::*; pub use paths::*; pub use rects::*; pub use shadows::*; pub use strokes::*; pub use svgraw::*; pub use text::*; pub use transform::*; use crate::math; use crate::math::{Bounds, Matrix, Point}; use indexmap::IndexSet; const MIN_VISIBLE_SIZE: f32 = 2.0; const ANTIALIAS_THRESHOLD: f32 = 15.0; #[derive(Debug, Clone, PartialEq)] pub enum Type { Frame(Frame), Group(Group), Bool(Bool), Rect(Rect), Path(Path), Circle, SVGRaw(SVGRaw), Text(TextContent), } impl Type { pub fn from(value: u8) -> Self { match value { 0 => Type::Frame(Frame::default()), 1 => Type::Group(Group::default()), 2 => Type::Bool(Bool::default()), 3 => Type::Rect(Rect::default()), 4 => Type::Path(Path::default()), 5 => Type::Text(TextContent::default()), 6 => Type::Circle, 7 => Type::SVGRaw(SVGRaw::default()), _ => Type::Rect(Rect::default()), } } pub fn corners(&self) -> Option { match self { Type::Rect(Rect { corners, .. }) => *corners, Type::Frame(Frame { corners, .. }) => *corners, _ => None, } } pub fn set_corners(&mut self, corners: Corners) { match self { Type::Rect(data) => { data.corners = Some(corners); } Type::Frame(data) => { data.corners = Some(corners); } _ => {} } } pub fn path(&self) -> Option<&Path> { match self { Type::Path(path) => Some(path), Type::Bool(Bool { path, .. }) => Some(path), _ => None, } } pub fn path_mut(&mut self) -> Option<&mut Path> { match self { Type::Path(path) => Some(path), Type::Bool(Bool { path, .. }) => Some(path), _ => None, } } } #[derive(Debug, Clone, PartialEq, Copy)] pub enum ConstraintH { Left, Right, LeftRight, Center, Scale, } impl ConstraintH { pub fn from(value: u8) -> Option { match value { 0 => Some(Self::Left), 1 => Some(Self::Right), 2 => Some(Self::LeftRight), 3 => Some(Self::Center), 4 => Some(Self::Scale), _ => None, } } } #[derive(Debug, Clone, PartialEq, Copy)] pub enum ConstraintV { Top, Bottom, TopBottom, Center, Scale, } impl ConstraintV { pub fn from(value: u8) -> Option { match value { 0 => Some(Self::Top), 1 => Some(Self::Bottom), 2 => Some(Self::TopBottom), 3 => Some(Self::Center), 4 => Some(Self::Scale), _ => None, } } } pub type Color = skia::Color; #[derive(Debug, Clone)] pub struct Shape { pub id: Uuid, pub parent_id: Option, pub shape_type: Type, pub children: IndexSet, pub selrect: math::Rect, pub transform: Matrix, pub rotation: f32, pub constraint_h: Option, pub constraint_v: Option, pub clip_content: bool, pub fills: Vec, pub strokes: Vec, pub blend_mode: BlendMode, pub blur: Blur, pub opacity: f32, pub hidden: bool, pub svg: Option, pub svg_attrs: HashMap, pub shadows: Vec, pub layout_item: Option, } impl Shape { pub fn new(id: Uuid) -> Self { Self { id, parent_id: None, shape_type: Type::Rect(Rect::default()), children: IndexSet::::new(), selrect: math::Rect::new_empty(), transform: Matrix::default(), rotation: 0., constraint_h: None, constraint_v: None, clip_content: true, fills: vec![], strokes: vec![], blend_mode: BlendMode::default(), opacity: 1., hidden: false, blur: Blur::default(), svg: None, svg_attrs: HashMap::new(), shadows: vec![], layout_item: None, } } pub fn set_parent(&mut self, id: Uuid) { self.parent_id = Some(id); } pub fn set_shape_type(&mut self, shape_type: Type) { self.shape_type = shape_type; } #[allow(dead_code)] pub fn is_frame(&self) -> bool { matches!(self.shape_type, Type::Frame(_)) } pub fn is_group_like(&self) -> bool { matches!(self.shape_type, Type::Group(_)) || matches!(self.shape_type, Type::Bool(_)) } pub fn has_layout(&self) -> bool { match self.shape_type { Type::Frame(Frame { layout: Some(_), .. }) => true, _ => false, } } pub fn set_selrect(&mut self, left: f32, top: f32, right: f32, bottom: f32) { self.selrect.set_ltrb(left, top, right, bottom); match self.shape_type { Type::Text(ref mut text) => { text.set_xywh(left, top, right - left, bottom - top); } _ => {} } } pub fn set_masked(&mut self, masked: bool) { match &mut self.shape_type { Type::Group(data) => { data.masked = masked; } _ => {} } } pub fn set_clip(&mut self, value: bool) { self.clip_content = value; } pub fn set_rotation(&mut self, angle: f32) { self.rotation = angle; } pub fn set_transform(&mut self, a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) { self.transform = Matrix::new_all(a, c, e, b, d, f, 0.0, 0.0, 1.0); } pub fn set_opacity(&mut self, opacity: f32) { self.opacity = opacity; } pub fn set_constraint_h(&mut self, constraint: Option) { self.constraint_h = constraint; } pub fn constraint_h(&self, default: ConstraintH) -> ConstraintH { self.constraint_h.clone().unwrap_or(default) } pub fn set_constraint_v(&mut self, constraint: Option) { self.constraint_v = constraint; } pub fn constraint_v(&self, default: ConstraintV) -> ConstraintV { self.constraint_v.clone().unwrap_or(default) } pub fn set_hidden(&mut self, value: bool) { self.hidden = value; } pub fn set_flex_layout_child_data( &mut self, margin_top: f32, margin_right: f32, margin_bottom: f32, margin_left: f32, h_sizing: Sizing, v_sizing: Sizing, max_h: Option, min_h: Option, max_w: Option, min_w: Option, align_self: Option, is_absolute: bool, z_index: i32, ) { self.layout_item = Some(LayoutItem { margin_top, margin_right, margin_bottom, margin_left, h_sizing, v_sizing, max_h, min_h, max_w, min_w, is_absolute, z_index, align_self, }); } pub fn set_flex_layout_data( &mut self, direction: FlexDirection, row_gap: f32, column_gap: f32, align_items: AlignItems, align_content: AlignContent, justify_items: JustifyItems, justify_content: JustifyContent, wrap_type: WrapType, padding_top: f32, padding_right: f32, padding_bottom: f32, padding_left: f32, ) { match &mut self.shape_type { Type::Frame(data) => { let layout_data = LayoutData { align_items, align_content, justify_items, justify_content, padding_top, padding_right, padding_bottom, padding_left, row_gap, column_gap, }; let flex_data = FlexData { direction, wrap_type, }; data.layout = Some(Layout::FlexLayout(layout_data, flex_data)); } _ => {} } } pub fn set_grid_layout_data( &mut self, direction: GridDirection, row_gap: f32, column_gap: f32, align_items: AlignItems, align_content: AlignContent, justify_items: JustifyItems, justify_content: JustifyContent, padding_top: f32, padding_right: f32, padding_bottom: f32, padding_left: f32, ) { match &mut self.shape_type { Type::Frame(data) => { let layout_data = LayoutData { align_items, align_content, justify_items, justify_content, padding_top, padding_right, padding_bottom, padding_left, row_gap, column_gap, }; let mut grid_data = GridData::default(); grid_data.direction = direction; data.layout = Some(Layout::GridLayout(layout_data, grid_data)); } _ => {} } } pub fn set_grid_columns(&mut self, tracks: Vec) { let Type::Frame(frame_data) = &mut self.shape_type else { return; }; let Some(Layout::GridLayout(_, grid_data)) = &mut frame_data.layout else { return; }; grid_data.columns = tracks.iter().map(GridTrack::from_raw).collect(); } pub fn set_grid_rows(&mut self, tracks: Vec) { let Type::Frame(frame_data) = &mut self.shape_type else { return; }; let Some(Layout::GridLayout(_, grid_data)) = &mut frame_data.layout else { return; }; grid_data.rows = tracks.iter().map(GridTrack::from_raw).collect(); } pub fn set_grid_cells(&mut self, cells: Vec) { let Type::Frame(frame_data) = &mut self.shape_type else { return; }; let Some(Layout::GridLayout(_, grid_data)) = &mut frame_data.layout else { return; }; grid_data.cells = cells.iter().map(GridCell::from_raw).collect(); } pub fn set_blur(&mut self, blur_type: u8, hidden: bool, value: f32) { self.blur = Blur::new(blur_type, hidden, value); } pub fn add_child(&mut self, id: Uuid) { self.children.insert(id); } pub fn compute_children_differences( &mut self, children: &IndexSet, ) -> (IndexSet, IndexSet) { let added = children.difference(&self.children).cloned().collect(); let removed = self.children.difference(children).cloned().collect(); (added, removed) } pub fn fills(&self) -> std::slice::Iter { self.fills.iter() } pub fn add_fill(&mut self, f: Fill) { self.fills.push(f); } pub fn clear_fills(&mut self) { self.fills.clear(); } pub fn strokes(&self) -> std::slice::Iter { self.strokes.iter() } pub fn add_stroke(&mut self, s: Stroke) { self.strokes.push(s) } pub fn set_stroke_fill(&mut self, f: Fill) -> Result<(), String> { let stroke = self.strokes.last_mut().ok_or("Shape has no strokes")?; stroke.fill = f; Ok(()) } pub fn clear_strokes(&mut self) { self.strokes.clear(); } pub fn set_path_segments(&mut self, buffer: Vec) -> Result<(), String> { let path = Path::try_from(buffer)?; match &mut self.shape_type { Type::Bool(Bool { bool_type, .. }) => { self.shape_type = Type::Bool(Bool { bool_type: *bool_type, path, }); } Type::Path(_) => { self.shape_type = Type::Path(path); } _ => {} }; Ok(()) } pub fn set_path_attr(&mut self, name: String, value: String) { match self.shape_type { Type::Path(_) => { self.set_svg_attr(name, value); } _ => unreachable!("This shape should have path attrs"), }; } pub fn set_svg_raw_content(&mut self, content: String) -> Result<(), String> { self.shape_type = Type::SVGRaw(SVGRaw::from_content(content)); Ok(()) } pub fn set_blend_mode(&mut self, mode: BlendMode) { self.blend_mode = mode; } pub fn set_bool_type(&mut self, bool_type: BoolType) { self.shape_type = match &self.shape_type { Type::Bool(Bool { path, .. }) => Type::Bool(Bool { bool_type, path: path.clone(), }), _ => Type::Bool(Bool { bool_type, path: Path::default(), }), }; } pub fn set_corners(&mut self, raw_corners: (f32, f32, f32, f32)) { if let Some(corners) = make_corners(raw_corners) { self.shape_type.set_corners(corners); } } pub fn set_svg(&mut self, svg: skia::svg::Dom) { self.svg = Some(svg); } pub fn set_svg_attr(&mut self, name: String, value: String) { self.svg_attrs.insert(name, value); } pub fn blend_mode(&self) -> crate::render::BlendMode { self.blend_mode } pub fn opacity(&self) -> f32 { self.opacity } pub fn hidden(&self) -> bool { self.hidden } pub fn width(&self) -> f32 { self.selrect.width() } pub fn visually_insignificant(&self, scale: f32) -> bool { self.selrect.width() * scale < MIN_VISIBLE_SIZE || self.selrect.height() * scale < MIN_VISIBLE_SIZE } pub fn should_use_antialias(&self, scale: f32) -> bool { self.selrect.width() * scale > ANTIALIAS_THRESHOLD || self.selrect.height() * scale > ANTIALIAS_THRESHOLD } // TODO: Maybe store this inside the shape pub fn bounds(&self) -> Bounds { let mut bounds = Bounds::new( Point::new(self.selrect.x(), self.selrect.y()), Point::new(self.selrect.x() + self.selrect.width(), self.selrect.y()), Point::new( self.selrect.x() + self.selrect.width(), self.selrect.y() + self.selrect.height(), ), Point::new(self.selrect.x(), self.selrect.y() + self.selrect.height()), ); let center = self.center(); let mut matrix = self.transform.clone(); matrix.post_translate(center); matrix.pre_translate(-center); bounds.transform_mut(&matrix); bounds } pub fn selrect(&self) -> math::Rect { self.selrect } pub fn extrect(&self) -> math::Rect { let mut rect = self.bounds().to_rect(); for shadow in self.shadows.iter() { let (x, y) = shadow.offset; let mut shadow_rect = rect.clone(); shadow_rect.left += x; shadow_rect.right += x; shadow_rect.top += y; shadow_rect.bottom += y; shadow_rect.left -= shadow.blur; shadow_rect.top -= shadow.blur; shadow_rect.right += shadow.blur; shadow_rect.bottom += shadow.blur; rect.join(shadow_rect); } if self.blur.blur_type != blurs::BlurType::None { rect.left -= self.blur.value; rect.top -= self.blur.value; rect.right += self.blur.value; rect.bottom += self.blur.value; } rect } pub fn center(&self) -> Point { self.selrect.center() } pub fn clip(&self) -> bool { self.clip_content } pub fn mask_id(&self) -> Option<&Uuid> { self.children.first() } pub fn children_ids(&self) -> IndexSet { if let Type::Bool(_) = self.shape_type { IndexSet::::new() } else if let Type::Group(group) = self.shape_type { if group.masked { self.children.iter().skip(1).cloned().collect() } else { self.children.clone().into_iter().collect() } } else { self.children.clone().into_iter().collect() } } pub fn image_filter(&self, scale: f32) -> Option { if !self.blur.hidden { match self.blur.blur_type { BlurType::None => None, BlurType::Layer => skia::image_filters::blur( (self.blur.value * scale, self.blur.value * scale), None, None, None, ), } } else { None } } pub fn is_recursive(&self) -> bool { matches!( self.shape_type, Type::Frame(_) | Type::Group(_) | Type::Bool(_) ) } pub fn add_shadow(&mut self, shadow: Shadow) { self.shadows.push(shadow); } pub fn clear_shadows(&mut self) { self.shadows.clear(); } pub fn drop_shadows(&self) -> impl DoubleEndedIterator { self.shadows .iter() .filter(|shadow| shadow.style() == ShadowStyle::Drop) } pub fn inner_shadows(&self) -> impl DoubleEndedIterator { self.shadows .iter() .filter(|shadow| shadow.style() == ShadowStyle::Inner) } pub fn to_path_transform(&self) -> Option { match self.shape_type { Type::Path(_) | Type::Bool(_) => { let center = self.center(); let mut matrix = Matrix::new_identity(); matrix.pre_translate(center); matrix.pre_concat(&self.transform.invert()?); matrix.pre_translate(-center); Some(matrix) } _ => None, } } pub fn add_paragraph(&mut self, paragraph: Paragraph) -> Result<(), String> { match self.shape_type { Type::Text(ref mut text) => { text.add_paragraph(paragraph); Ok(()) } _ => Err("Shape is not a text".to_string()), } } pub fn clear_text(&mut self) { match self.shape_type { Type::Text(_) => { let new_text_content = TextContent::new(self.selrect); self.shape_type = Type::Text(new_text_content); } _ => {} } } pub fn get_skia_path(&self) -> Option { if let Some(path) = self.shape_type.path() { let mut skia_path = path.to_skia_path(); if let Some(path_transform) = self.to_path_transform() { skia_path.transform(&path_transform); } if let Some("evenodd") = self.svg_attrs.get("fill-rule").map(String::as_str) { skia_path.set_fill_type(skia::PathFillType::EvenOdd); } Some(skia_path) } else { None } } fn transform_selrect(&mut self, transform: &Matrix) { let mut center = self.selrect.center(); center = transform.map_point(center); let bounds = self.bounds().transform(&transform); self.transform = bounds.transform_matrix().unwrap_or(Matrix::default()); let width = bounds.width(); let height = bounds.height(); let new_selrect = math::Rect::from_xywh( center.x - width / 2.0, center.y - height / 2.0, width, height, ); self.selrect = new_selrect; } pub fn apply_transform(&mut self, transform: &Matrix) { self.transform_selrect(&transform); match &mut self.shape_type { shape_type @ (Type::Path(_) | Type::Bool(_)) => { if let Some(path) = shape_type.path_mut() { path.transform(&transform); } } _ => {} } } pub fn is_absolute(&self) -> bool { match &self.layout_item { Some(LayoutItem { is_absolute, .. }) => *is_absolute, _ => false, } } pub fn z_index(&self) -> i32 { match &self.layout_item { Some(LayoutItem { z_index, .. }) => *z_index, _ => 0, } } pub fn is_layout_vertical_auto(&self) -> bool { match &self.layout_item { Some(LayoutItem { v_sizing, .. }) => v_sizing == &Sizing::Auto, _ => false, } } pub fn is_layout_vertical_fill(&self) -> bool { match &self.layout_item { Some(LayoutItem { v_sizing, .. }) => v_sizing == &Sizing::Fill, _ => false, } } pub fn is_layout_horizontal_auto(&self) -> bool { match &self.layout_item { Some(LayoutItem { h_sizing, .. }) => h_sizing == &Sizing::Auto, _ => false, } } pub fn is_layout_horizontal_fill(&self) -> bool { match &self.layout_item { Some(LayoutItem { h_sizing, .. }) => h_sizing == &Sizing::Fill, _ => false, } } pub fn has_fills(&self) -> bool { !self.fills.is_empty() } } /* Returns the list of children taking into account the structure modifiers */ pub fn modified_children_ids( element: &Shape, structure: Option<&Vec>, ) -> IndexSet { if let Some(structure) = structure { let mut result: Vec = Vec::from_iter(element.children_ids().iter().map(|id| *id)); let mut to_remove = HashSet::<&Uuid>::new(); for st in structure { match st.entry_type { StructureEntryType::AddChild => { result.insert(st.index as usize, st.id); } StructureEntryType::RemoveChild => { to_remove.insert(&st.id); } } } let ret: IndexSet = result .iter() .filter(|id| !to_remove.contains(id)) .map(|id| *id) .collect(); ret } else { element.children_ids() } } #[cfg(test)] mod tests { use super::*; fn any_shape() -> Shape { Shape::new(Uuid::nil()) } #[test] fn add_fill_pushes_a_new_fill() { let mut shape = any_shape(); assert_eq!(shape.fills.len(), 0); shape.add_fill(Fill::Solid(Color::TRANSPARENT)); assert_eq!(shape.fills.get(0), Some(&Fill::Solid(Color::TRANSPARENT))) } #[test] fn test_set_corners() { let mut shape = any_shape(); shape.set_corners((10.0, 20.0, 30.0, 40.0)); if let Type::Rect(Rect { corners, .. }) = shape.shape_type { assert_eq!( corners, Some([ Point { x: 10.0, y: 10.0 }, Point { x: 20.0, y: 20.0 }, Point { x: 30.0, y: 30.0 }, Point { x: 40.0, y: 40.0 } ]) ); } else { assert!(false); } } #[test] fn test_set_masked() { let mut shape = any_shape(); shape.set_shape_type(Type::Group(Group { masked: false })); shape.set_masked(true); if let Type::Group(Group { masked, .. }) = shape.shape_type { assert_eq!(masked, true); } else { assert!(false); } } #[test] fn test_apply_transform() { let mut shape = Shape::new(Uuid::new_v4()); shape.set_shape_type(Type::Rect(Rect::default())); shape.set_selrect(0.0, 10.0, 10.0, 0.0); shape.apply_transform(&Matrix::scale((2.0, 2.0))); assert_eq!(shape.selrect().width(), 20.0); assert_eq!(shape.selrect().height(), 20.0); } }