pub mod base_gradient;
pub mod base_stroke;
pub mod ellipse;
pub mod fill;
pub mod gradient_fill;
pub mod gradient_stroke;
pub mod graphic_element;
pub mod group;
pub mod merge;
pub mod modifier;
pub mod offset_path;
pub mod path;
pub mod polystar;
pub mod pucker_bloat;
pub mod rectangle;
pub mod repeater;
pub mod repeater_transform;
pub mod rounded_corners;
pub mod shape;
pub mod shape_style;
pub mod stroke;
pub mod stroke_dash;
pub mod transform;
pub mod trim_path;
pub mod twist;
use crate::schema::shapes::{rounded_corners::RoundedCornersShape, twist::TwistShape};
use self::fill::FillShape;
use self::gradient_fill::GradientFillShape;
use self::gradient_stroke::GradientStrokeShape;
use self::merge::MergeShape;
use self::offset_path::OffsetPathShape;
use self::path::PathShape;
use self::polystar::PolyStarShape;
use self::pucker_bloat::PuckerBloatShape;
use self::rectangle::RectangleShape;
use self::repeater::RepeaterShape;
use self::stroke::StrokeShape;
use self::transform::TransformShape;
use self::trim_path::TrimPathShape;
use super::animated_properties::value::FloatValue;
use ellipse::EllipseShape;
use group::GroupShape;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
#[serde(tag = "ty")]
#[expect(
clippy::large_enum_variant,
reason = "Shapes have inherently different sizes based on the Lottie spec"
)]
pub enum AnyShape {
#[serde(rename = "gr")]
Group(GroupShape),
#[serde(rename = "rc")]
Rectangle(RectangleShape),
#[serde(rename = "el")]
Ellipse(EllipseShape),
#[serde(rename = "tr")]
Transform(TransformShape),
#[serde(rename = "st")]
Stroke(StrokeShape),
#[serde(rename = "pb")]
PuckerBloat(PuckerBloatShape),
#[serde(rename = "mm")]
Merge(MergeShape),
#[serde(rename = "rp")]
Repeater(RepeaterShape),
#[serde(rename = "op")]
OffsetPath(OffsetPathShape),
#[serde(rename = "fl")]
Fill(FillShape),
#[serde(rename = "tm")]
Trim(TrimPathShape),
#[serde(rename = "sh")]
Path(PathShape),
#[serde(rename = "gf")]
GradientFill(GradientFillShape),
#[serde(rename = "gs")]
GradientStroke(GradientStrokeShape),
#[serde(rename = "tw")]
Twist(TwistShape),
#[serde(rename = "rd")]
RoundedCorners(RoundedCornersShape),
#[serde(rename = "sr")]
PolyStar(PolyStarShape),
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
pub enum ShapeType {
#[serde(rename = "rc")]
Rectangle,
#[serde(rename = "el")]
Ellipse,
#[serde(rename = "sr")]
PolyStar,
#[serde(rename = "sh")]
Path,
#[serde(rename = "fl")]
Fill,
#[serde(rename = "st")]
Stroke,
#[serde(rename = "gf")]
GradientFill,
#[serde(rename = "gs")]
GradientStroke,
#[serde(rename = "no")]
NoStyle,
#[serde(rename = "gr")]
Group,
#[serde(rename = "tr")]
Transform,
#[serde(rename = "rp")]
Repeater,
#[serde(rename = "tm")]
Trim,
#[serde(rename = "rd")]
RoundedCorners,
#[serde(rename = "pb")]
PuckerBloat,
#[serde(rename = "mm")]
Merge,
#[serde(rename = "tw")]
Twist,
#[serde(rename = "op")]
OffsetPath,
#[serde(rename = "zz")]
ZigZag,
}
#[cfg(test)]
mod tests {
use super::{
AnyShape, ellipse::EllipseShape, graphic_element::GraphicElementShape, group::GroupShape,
};
use crate::schema::{
animated_properties::{
animated_property::{AnimatedProperty, AnimatedPropertyK},
multi_dimensional::MultiDimensional,
position::{Position, PositionValueK},
},
helpers::{int_boolean::BoolInt, visual_object::VisualObject},
shapes::shape::Shape,
};
use serde_json::json;
use std::sync::LazyLock;
static JSON: LazyLock<serde_json::Value> = LazyLock::new(|| {
json!(
{
"ty": "gr",
"nm": "Group",
"mn": "{f1becc2a-49f0-4f0c-918f-bdffe4c6870f}",
"it": [
{
"ty": "el",
"nm": "Ellipse",
"mn": "{2aabac6e-1dd8-41b0-b60b-baf75ccb6318}",
"p": {
"a": 0,
"k": [
303.9044776119403,
324.9671641791045
]
},
"s": {
"a": 0,
"k": [
205.46865671641788,
204.6089552238806
]
}
}
]
}
)
});
static LAYER: LazyLock<AnyShape> = LazyLock::new(|| {
AnyShape::Group(GroupShape {
graphic_element: GraphicElementShape {
visual_object: VisualObject {
name: Some("Group".to_string()),
match_name: Some("{f1becc2a-49f0-4f0c-918f-bdffe4c6870f}".to_string()),
},
index: None,
hidden: None,
blend_mode: None,
property_index: None,
css_class: None,
xml_id: None,
},
num_properties: None,
property_index: None,
shapes: vec![AnyShape::Ellipse(EllipseShape {
shape: Shape {
graphic_element: GraphicElementShape {
visual_object: VisualObject {
name: Some("Ellipse".to_string()),
match_name: Some("{2aabac6e-1dd8-41b0-b60b-baf75ccb6318}".to_string()),
},
index: None,
hidden: None,
blend_mode: None,
property_index: None,
css_class: None,
xml_id: None,
},
direction: None,
},
position: Position {
property_index: None,
animated: Some(BoolInt::False),
expression: None,
length: None,
value: PositionValueK::Static(vec![303.9044776119403, 324.9671641791045]),
},
size: MultiDimensional {
animated_property: AnimatedProperty {
animated: Some(BoolInt::False),
property_index: None,
expression: None,
slot_id: None,
value: AnimatedPropertyK::Static(vec![
205.46865671641788,
204.6089552238806,
]),
},
length: None,
},
})],
})
});
#[test]
fn test_deserialize() {
let actual: Result<AnyShape, serde_json::Error> = serde_json::from_value(JSON.to_owned());
match actual {
Ok(actual) => assert_eq!(*LAYER, actual),
Err(e) => panic!("{e}"),
}
}
#[test]
fn test_can_serialize() {
serde_json::to_value(&*LAYER).unwrap();
}
}