tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
chore: refresco desde el monorepo (pin tawasuyu.git v0.3.0)
1// Copyright 2024 the Velato Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4pub mod image;
5pub mod layer;
6pub mod null;
7pub mod precomposition;
8pub mod shape;
9pub mod solid;
10pub mod visual;
11
12use self::solid::SolidLayer;
13use self::visual::VisualLayer;
14use image::ImageLayer;
15use null::NullLayer;
16use precomposition::PrecompositionLayer;
17use serde::{Deserialize, Deserializer, Serialize};
18use shape::ShapeLayer;
19
20/// There are several layer types, which is specified by the 'ty' attribute. All
21/// layers share the properties in `layers::common::Properties`.
22#[derive(Serialize, Debug, Clone, PartialEq)]
23#[serde(untagged)]
24pub enum AnyLayer {
25 /// Renders a Precomposition
26 Precomposition(PrecompositionLayer),
27 /// Static rectangle filling the canvas with a single color
28 Solid(SolidLayer),
29 /// No contents, only used for parenting.
30 /// Has an array of shapes
31 Shape(ShapeLayer),
32 /// Null
33 Null(NullLayer),
34 // unimplemented - Text(TextLayer),
35 Image(ImageLayer),
36 /// FORK tawasuyu: cualquier `ty` que este schema no modela — texto (5),
37 /// cámara, luz, ajuste, guía, audio, vídeo. Upstream devolvía
38 /// `Err("unknown layer type")` desde el deserializador, y como serde
39 /// aborta el documento entero al primer error, **un solo layer de texto
40 /// tumbaba el `.json` completo**: no se perdía el rótulo, se perdía la
41 /// animación. Eso contradecía lo que el README de este fork ya prometía
42 /// («degradan a omisión, no a panic») y es el caso más común de un export
43 /// de After Effects, donde una cámara o una luz vienen de fábrica. Ahora
44 /// la capa se reconoce como no soportada y `conv_layer` la saltea, igual
45 /// que a una capa oculta.
46 Unsupported {
47 /// El `ty` que traía, para que un diagnóstico pueda nombrarlo.
48 ty: u64,
49 },
50 // unimplemented - Audio(AudioLayer),
51 // unimplemented - VideoPlaceholder(VideoPlaceholderLayer)
52 // unimplemented - Video(VideoLayer)
53 // unimplemented - ImagePlaceholder(ImagePlaceholderLayer)
54 // unimplemented - Guide(GuideLayer)
55 // unimplemented - Adjustment(AdjustmentLayer)
56 // unimplemented - Camera(CameraLayer)
57 // unimplemented - Light(LightLayer)
58 // unimplemented - Data(DataLayer)
59}
60
61impl<'de> Deserialize<'de> for AnyLayer {
62 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
63 where
64 D: Deserializer<'de>,
65 {
66 use serde::de::Error;
67
68 let value = serde_json::Value::deserialize(deserializer)?;
69
70 let layer_type = value
71 .get("ty")
72 .and_then(|v| v.as_u64())
73 .ok_or_else(|| D::Error::missing_field("ty"))?;
74
75 match layer_type {
76 0 => serde_json::from_value(value)
77 .map(AnyLayer::Precomposition)
78 .map_err(D::Error::custom),
79 1 => serde_json::from_value(value)
80 .map(AnyLayer::Solid)
81 .map_err(D::Error::custom),
82 2 => serde_json::from_value(value)
83 .map(AnyLayer::Image)
84 .map_err(D::Error::custom),
85 3 => serde_json::from_value(value)
86 .map(AnyLayer::Null)
87 .map_err(D::Error::custom),
88 4 => serde_json::from_value(value)
89 .map(AnyLayer::Shape)
90 .map_err(D::Error::custom),
91 // FORK tawasuyu: omitir la capa en vez de rechazar el documento.
92 // Ver el comentario de `AnyLayer::Unsupported`.
93 otro => Ok(AnyLayer::Unsupported { ty: otro }),
94 }
95 }
96}
97
98#[cfg(test)]
99mod tests {
100
101 use crate::schema::{
102 animated_properties::{
103 animated_property::{AnimatedProperty, AnimatedPropertyK},
104 multi_dimensional::MultiDimensional,
105 position::{Position, PositionValueK},
106 value::FloatValue,
107 },
108 helpers::{
109 int_boolean::BoolInt,
110 transform::{AnyTransformP, AnyTransformR, Transform},
111 visual_object::VisualObject,
112 },
113 layers::layer::Layer,
114 shapes::{
115 AnyShape, ellipse::EllipseShape, graphic_element::GraphicElementShape,
116 group::GroupShape, shape::Shape,
117 },
118 };
119
120 use super::{AnyLayer, shape::ShapeLayer, visual::VisualLayer};
121 use serde_json::json;
122 use std::sync::LazyLock;
123
124 static JSON: LazyLock<serde_json::Value> = LazyLock::new(|| {
125 json!(
126 {
127 "ddd": 0,
128 "ty": 4,
129 "ind": 1,
130 "st": 0,
131 "ip": 0,
132 "op": 180,
133 "nm": "Ellipse",
134 "mn": "{0a36d01c-18e1-48d3-8e8f-cc093b3f24ba}",
135 "ks": {
136 "a": {
137 "a": 0,
138 "k": [
139 256,
140 256
141 ]
142 },
143 "p": {
144 "a": 0,
145 "k": [
146 256,
147 256
148 ]
149 },
150 "s": {
151 "a": 0,
152 "k": [
153 100,
154 100
155 ]
156 },
157 "r": {
158 "a": 0,
159 "k": 0
160 },
161 "o": {
162 "a": 0,
163 "k": 100
164 }
165 },
166 "shapes": [
167 {
168 "ty": "gr",
169 "nm": "Group",
170 "mn": "{f1becc2a-49f0-4f0c-918f-bdffe4c6870f}",
171 "it": [
172 {
173 "ty": "el",
174 "nm": "Ellipse",
175 "mn": "{2aabac6e-1dd8-41b0-b60b-baf75ccb6318}",
176 "p": {
177 "a": 0,
178 "k": [
179 303.9044776119403,
180 324.9671641791045
181 ]
182 },
183 "s": {
184 "a": 0,
185 "k": [
186 205.46865671641788,
187 204.6089552238806
188 ]
189 }
190 }
191 ]
192 }
193 ]
194 }
195 )
196 });
197
198 #[expect(deprecated, reason = "Uses deprecated attributes")]
199 static LAYER: LazyLock<AnyLayer> = LazyLock::new(|| {
200 AnyLayer::Shape(ShapeLayer {
201 visual_layer: VisualLayer {
202 layer: Layer {
203 visual_object: VisualObject {
204 name: Some("Ellipse".to_string()),
205 match_name: Some("{0a36d01c-18e1-48d3-8e8f-cc093b3f24ba}".to_string()),
206 },
207 layer_type: 4,
208 three_dimensional: Some(BoolInt::False),
209 index: Some(1),
210 start_time: Some(0.0),
211 in_point: 0.0,
212 out_point: 180.0,
213 hidden: None,
214 parent_index: None,
215 time_stretch: None,
216 },
217 transform: Transform {
218 anchor_point: Some(Position {
219 property_index: None,
220 animated: Some(BoolInt::False),
221 expression: None,
222 length: None,
223 value: PositionValueK::Static(vec![256.0, 256.0]),
224 }),
225 position: AnyTransformP::Position(Position {
226 property_index: None,
227 animated: Some(BoolInt::False),
228 expression: None,
229 length: None,
230 value: PositionValueK::Static(vec![256.0, 256.0]),
231 }),
232 scale: Some(MultiDimensional {
233 animated_property: AnimatedProperty {
234 animated: Some(BoolInt::False),
235 property_index: None,
236 expression: None,
237 slot_id: None,
238 value: AnimatedPropertyK::Static(vec![100.0, 100.0]),
239 },
240 length: None,
241 }),
242 rotation: Some(AnyTransformR::Rotation(FloatValue {
243 animated_property: AnimatedProperty {
244 animated: Some(BoolInt::False),
245 property_index: None,
246 expression: None,
247 slot_id: None,
248 value: AnimatedPropertyK::Static(0.0),
249 },
250 })),
251 opacity: Some(FloatValue {
252 animated_property: AnimatedProperty {
253 animated: Some(BoolInt::False),
254 property_index: None,
255 expression: None,
256 slot_id: None,
257 value: AnimatedPropertyK::Static(100.0),
258 },
259 }),
260 skew: None,
261 skew_axis: None,
262 },
263 matte_mode: None,
264 matte_target: None,
265 masks_properties: None,
266 rotate_to_match_anim_pos_path: None,
267 matte_layer_index: None,
268 has_mask: None,
269 motion_blur: None,
270 blend_mode: None,
271 css_class: None,
272 id: None,
273 tag_name: None,
274 tranform_before_mask_deprecated: None,
275 transform_before_mask: None,
276 },
277 shapes: vec![AnyShape::Group(GroupShape {
278 graphic_element: GraphicElementShape {
279 visual_object: VisualObject {
280 name: Some("Group".to_string()),
281 match_name: Some("{f1becc2a-49f0-4f0c-918f-bdffe4c6870f}".to_string()),
282 },
283 index: None,
284 hidden: None,
285 blend_mode: None,
286 property_index: None,
287 css_class: None,
288 xml_id: None,
289 },
290 num_properties: None,
291 property_index: None,
292 shapes: vec![AnyShape::Ellipse(EllipseShape {
293 shape: Shape {
294 graphic_element: GraphicElementShape {
295 visual_object: VisualObject {
296 name: Some("Ellipse".to_string()),
297 match_name: Some(
298 "{2aabac6e-1dd8-41b0-b60b-baf75ccb6318}".to_string(),
299 ),
300 },
301 index: None,
302 hidden: None,
303 blend_mode: None,
304 property_index: None,
305 css_class: None,
306 xml_id: None,
307 },
308 direction: None,
309 },
310 position: Position {
311 property_index: None,
312 animated: Some(BoolInt::False),
313 expression: None,
314 length: None,
315 value: PositionValueK::Static(vec![303.9044776119403, 324.9671641791045]),
316 },
317 size: MultiDimensional {
318 animated_property: AnimatedProperty {
319 animated: Some(BoolInt::False),
320 property_index: None,
321 expression: None,
322 slot_id: None,
323 value: AnimatedPropertyK::Static(vec![
324 205.46865671641788,
325 204.6089552238806,
326 ]),
327 },
328 length: None,
329 },
330 })],
331 })],
332 })
333 });
334
335 #[test]
336 fn test_deserialize() {
337 let actual = serde_json::from_value(JSON.to_owned());
338
339 match actual {
340 Ok(actual) => assert_eq!(*LAYER, actual),
341 Err(e) => panic!("{e}"),
342 }
343 }
344
345 #[test]
346 fn test_can_serialize() {
347 serde_json::to_value(&*LAYER).unwrap();
348 }
349}