tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
1// Copyright 2024 the Velato Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4pub mod base_gradient;
5pub mod base_stroke;
6pub mod ellipse;
7pub mod fill;
8pub mod gradient_fill;
9pub mod gradient_stroke;
10pub mod graphic_element;
11pub mod group;
12pub mod merge;
13pub mod modifier;
14pub mod offset_path;
15pub mod path;
16pub mod polystar;
17pub mod pucker_bloat;
18pub mod rectangle;
19pub mod repeater;
20pub mod repeater_transform;
21pub mod rounded_corners;
22pub mod shape;
23pub mod shape_style;
24pub mod stroke;
25pub mod stroke_dash;
26pub mod transform;
27pub mod trim_path;
28pub mod twist;
29// todo pub mod shape_list;
30// todo pub mod zig_zag;
31// todo pub mod no_style;
32
33use crate::schema::shapes::{rounded_corners::RoundedCornersShape, twist::TwistShape};
34
35use self::fill::FillShape;
36use self::gradient_fill::GradientFillShape;
37use self::gradient_stroke::GradientStrokeShape;
38use self::merge::MergeShape;
39use self::offset_path::OffsetPathShape;
40use self::path::PathShape;
41use self::pucker_bloat::PuckerBloatShape;
42use self::rectangle::RectangleShape;
43use self::repeater::RepeaterShape;
44use self::stroke::StrokeShape;
45use self::transform::TransformShape;
46use self::trim_path::TrimPathShape;
47use super::animated_properties::value::FloatValue;
48use ellipse::EllipseShape;
49use group::GroupShape;
50use serde::{Deserialize, Serialize};
51
52/// Lottie considers everything related to vector data as a "shape". All shapes
53/// share the properties in `shapes::common::Properties`.
54#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
55#[serde(tag = "ty")]
56#[expect(
57 clippy::large_enum_variant,
58 reason = "Shapes have inherently different sizes based on the Lottie spec"
59)]
60pub enum AnyShape {
61 /// A group is a shape that can contain other shapes (including other
62 /// groups)
63 #[serde(rename = "gr")]
64 Group(GroupShape),
65 /// A rectangle, defined by its center point and size.
66 #[serde(rename = "rc")]
67 Rectangle(RectangleShape),
68 /// An ellipse, defined by its center point and width and height.
69 #[serde(rename = "el")]
70 Ellipse(EllipseShape),
71 #[serde(rename = "tr")]
72 Transform(TransformShape),
73 #[serde(rename = "st")]
74 Stroke(StrokeShape),
75 #[serde(rename = "pb")]
76 PuckerBloat(PuckerBloatShape),
77 #[serde(rename = "mm")]
78 Merge(MergeShape),
79 #[serde(rename = "rp")]
80 Repeater(RepeaterShape),
81 #[serde(rename = "op")]
82 OffsetPath(OffsetPathShape),
83 #[serde(rename = "fl")]
84 Fill(FillShape),
85 #[serde(rename = "tm")]
86 Trim(TrimPathShape),
87 #[serde(rename = "sh")]
88 Path(PathShape),
89 #[serde(rename = "gf")]
90 GradientFill(GradientFillShape),
91 #[serde(rename = "gs")]
92 GradientStroke(GradientStrokeShape),
93 #[serde(rename = "tw")]
94 Twist(TwistShape),
95 #[serde(rename = "rd")]
96 RoundedCorners(RoundedCornersShape),
97 // TODO: model other shapes
98 // todo ZigZag(zig_zag),
99 // todo no_style(no_style),
100}
101
102#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
103pub enum ShapeType {
104 #[serde(rename = "rc")]
105 Rectangle,
106 #[serde(rename = "el")]
107 Ellipse,
108 #[serde(rename = "sr")]
109 PolyStar,
110 #[serde(rename = "sh")]
111 Path,
112 #[serde(rename = "fl")]
113 Fill,
114 #[serde(rename = "st")]
115 Stroke,
116 #[serde(rename = "gf")]
117 GradientFill,
118 #[serde(rename = "gs")]
119 GradientStroke,
120 #[serde(rename = "no")]
121 NoStyle,
122 #[serde(rename = "gr")]
123 Group,
124 #[serde(rename = "tr")]
125 Transform,
126 #[serde(rename = "rp")]
127 Repeater,
128 #[serde(rename = "tm")]
129 Trim,
130 #[serde(rename = "rd")]
131 RoundedCorners,
132 #[serde(rename = "pb")]
133 PuckerBloat,
134 #[serde(rename = "mm")]
135 Merge,
136 #[serde(rename = "tw")]
137 Twist,
138 #[serde(rename = "op")]
139 OffsetPath,
140 #[serde(rename = "zz")]
141 ZigZag,
142}
143
144#[cfg(test)]
145mod tests {
146 use super::{
147 AnyShape, ellipse::EllipseShape, graphic_element::GraphicElementShape, group::GroupShape,
148 };
149 use crate::schema::{
150 animated_properties::{
151 animated_property::{AnimatedProperty, AnimatedPropertyK},
152 multi_dimensional::MultiDimensional,
153 position::{Position, PositionValueK},
154 },
155 helpers::{int_boolean::BoolInt, visual_object::VisualObject},
156 shapes::shape::Shape,
157 };
158 use serde_json::json;
159 use std::sync::LazyLock;
160
161 static JSON: LazyLock<serde_json::Value> = LazyLock::new(|| {
162 json!(
163 {
164 "ty": "gr",
165 "nm": "Group",
166 "mn": "{f1becc2a-49f0-4f0c-918f-bdffe4c6870f}",
167 "it": [
168 {
169 "ty": "el",
170 "nm": "Ellipse",
171 "mn": "{2aabac6e-1dd8-41b0-b60b-baf75ccb6318}",
172 "p": {
173 "a": 0,
174 "k": [
175 303.9044776119403,
176 324.9671641791045
177 ]
178 },
179 "s": {
180 "a": 0,
181 "k": [
182 205.46865671641788,
183 204.6089552238806
184 ]
185 }
186 }
187 ]
188 }
189 )
190 });
191
192 static LAYER: LazyLock<AnyShape> = LazyLock::new(|| {
193 AnyShape::Group(GroupShape {
194 graphic_element: GraphicElementShape {
195 visual_object: VisualObject {
196 name: Some("Group".to_string()),
197 match_name: Some("{f1becc2a-49f0-4f0c-918f-bdffe4c6870f}".to_string()),
198 },
199 index: None,
200 hidden: None,
201 blend_mode: None,
202 property_index: None,
203 css_class: None,
204 xml_id: None,
205 },
206 num_properties: None,
207 property_index: None,
208 shapes: vec![AnyShape::Ellipse(EllipseShape {
209 shape: Shape {
210 graphic_element: GraphicElementShape {
211 visual_object: VisualObject {
212 name: Some("Ellipse".to_string()),
213 match_name: Some("{2aabac6e-1dd8-41b0-b60b-baf75ccb6318}".to_string()),
214 },
215 index: None,
216 hidden: None,
217 blend_mode: None,
218 property_index: None,
219 css_class: None,
220 xml_id: None,
221 },
222 direction: None,
223 },
224 position: Position {
225 property_index: None,
226 animated: Some(BoolInt::False),
227 expression: None,
228 length: None,
229 value: PositionValueK::Static(vec![303.9044776119403, 324.9671641791045]),
230 },
231 size: MultiDimensional {
232 animated_property: AnimatedProperty {
233 animated: Some(BoolInt::False),
234 property_index: None,
235 expression: None,
236 slot_id: None,
237 value: AnimatedPropertyK::Static(vec![
238 205.46865671641788,
239 204.6089552238806,
240 ]),
241 },
242 length: None,
243 },
244 })],
245 })
246 });
247
248 #[test]
249 fn test_deserialize() {
250 let actual: Result<AnyShape, serde_json::Error> = serde_json::from_value(JSON.to_owned());
251
252 match actual {
253 Ok(actual) => assert_eq!(*LAYER, actual),
254 Err(e) => panic!("{e}"),
255 }
256 }
257
258 #[test]
259 fn test_can_serialize() {
260 serde_json::to_value(&*LAYER).unwrap();
261 }
262}