1use std::collections::{BTreeSet, HashMap};
4use std::fmt;
5use std::sync::Arc;
6
7use ravenrs_core::{ImageData, PathFill};
8use serde_json::Value;
9
10use crate::player;
11use crate::property::{Numbers, Shape, numbers};
12
13#[derive(Clone, Debug, PartialEq, Eq)]
15#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
16pub enum LottieError {
17 NotJson(String),
19 Missing(&'static str),
21}
22
23impl fmt::Display for LottieError {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 match self {
26 Self::NotJson(why) => write!(f, "the file is not JSON: {why}"),
27 Self::Missing(field) => write!(f, "the file is not a Lottie: it has no `{field}`"),
28 }
29 }
30}
31
32impl std::error::Error for LottieError {}
33
34#[derive(Clone, Debug)]
35pub(crate) struct Transform {
36 pub anchor: Numbers,
37 pub position: Numbers,
38 pub split: Option<(Numbers, Numbers)>,
39 pub scale: Numbers,
40 pub rotation: Numbers,
41 pub opacity: Numbers,
42 pub skew: Numbers,
43 pub skew_axis: Numbers,
44 pub orient: bool,
46}
47
48#[derive(Clone, Debug)]
49pub(crate) struct GradientPaint {
50 pub radial: bool,
51 pub start: Numbers,
52 pub end: Numbers,
53 pub stops: Numbers,
54 pub count: usize,
55}
56
57#[derive(Clone, Debug)]
58pub(crate) struct StrokeLook {
59 pub width: Numbers,
60 pub cap: i64,
61 pub join: i64,
62 pub miter_limit: f32,
63 pub dashes: Vec<(String, Numbers)>,
64}
65
66#[derive(Clone, Debug)]
67pub(crate) enum Item {
68 Group(Vec<Item>, Option<Transform>),
69 Path(Shape),
70 Rect {
71 position: Numbers,
72 size: Numbers,
73 radius: Numbers,
74 },
75 Ellipse {
76 position: Numbers,
77 size: Numbers,
78 },
79 Star {
80 polygon: bool,
81 position: Numbers,
82 rotation: Numbers,
83 points: Numbers,
84 inner: Numbers,
85 outer: Numbers,
86 inner_round: Numbers,
87 outer_round: Numbers,
88 },
89 Fill {
90 color: Numbers,
91 opacity: Numbers,
92 even_odd: bool,
93 },
94 Stroke {
95 color: Numbers,
96 opacity: Numbers,
97 look: StrokeLook,
98 },
99 GradientFill {
100 paint: GradientPaint,
101 opacity: Numbers,
102 even_odd: bool,
103 },
104 GradientStroke {
105 paint: GradientPaint,
106 opacity: Numbers,
107 look: StrokeLook,
108 },
109 Trim {
110 start: Numbers,
111 end: Numbers,
112 offset: Numbers,
113 together: bool,
114 },
115 Repeater {
116 copies: Numbers,
117 offset: Numbers,
118 transform: Transform,
119 start_opacity: Numbers,
120 end_opacity: Numbers,
121 },
122 RoundedCorners(Numbers),
123}
124
125#[derive(Clone, Debug)]
126pub(crate) enum LayerKind {
127 Shape(Vec<Item>),
128 Solid { width: f32, height: f32, color: [f32; 3] },
129 Image { image: ImageData, width: f32, height: f32 },
130 Precomp { reference: String, width: f32, height: f32, remap: Option<Numbers> },
131 Empty,
132}
133
134#[derive(Clone, Debug)]
135pub(crate) struct Mask {
136 pub path: Shape,
137 pub subtract: bool,
138 pub inverted: bool,
139}
140
141#[derive(Clone, Debug)]
142pub(crate) struct Layer {
143 pub kind: LayerKind,
144 pub index: Option<i64>,
145 pub parent: Option<i64>,
146 pub transform: Transform,
147 pub in_frame: f32,
148 pub out_frame: f32,
149 pub start: f32,
150 pub stretch: f32,
151 pub hidden: bool,
152 pub masks: Vec<Mask>,
153 pub matte_mode: i64,
156 pub matte_index: Option<i64>,
157 pub is_matte: bool,
158 pub fill_effect: Option<(Numbers, Numbers)>,
160}
161
162#[derive(Debug)]
163pub(crate) struct Document {
164 pub width: f32,
165 pub height: f32,
166 pub frame_rate: f32,
167 pub in_frame: f32,
168 pub out_frame: f32,
169 pub layers: Vec<Layer>,
170 pub precomps: HashMap<String, Vec<Layer>>,
171 pub unsupported: BTreeSet<String>,
172}
173
174#[derive(Clone, Debug)]
185#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
186pub struct Animation(pub(crate) Arc<Document>);
187
188fn number(value: Option<&Value>, default: f32) -> f32 {
189 value.and_then(Value::as_f64).map_or(default, |v| v as f32)
190}
191
192struct Reader {
193 unsupported: BTreeSet<String>,
194 images: HashMap<String, (ImageData, f32, f32)>,
195}
196
197impl Reader {
198 fn fill_effect(&mut self, effect: &Value) -> (Numbers, Numbers) {
200 let controls = effect.get("ef").and_then(Value::as_array).map_or(&[][..], Vec::as_slice);
201 let control = |index: i64| {
202 controls
203 .iter()
204 .find(|c| c.get("ix").and_then(Value::as_i64) == Some(index))
205 .and_then(|c| c.get("v"))
206 };
207 if control(1).and_then(|v| v.get("k")).and_then(Value::as_f64).is_some_and(|mask| mask != 0.0) {
208 self.note("efeito Fill limitado a uma máscara, aplicado à camada toda");
209 }
210 (self.numbers(control(3)), self.numbers(control(7)))
211 }
212
213 fn note(&mut self, what: &str) {
214 self.unsupported.insert(what.to_owned());
215 }
216
217 fn numbers(&mut self, property: Option<&Value>) -> Numbers {
219 if property.and_then(|p| p.get("x")).and_then(Value::as_str).is_some_and(|x| !x.is_empty()) {
220 self.note("expressões");
221 }
222 Numbers::parse(property)
223 }
224
225 fn transform(&mut self, value: Option<&Value>) -> Transform {
226 let empty = Value::Null;
227 let v = value.unwrap_or(&empty);
228 let position = v.get("p");
229 let split = position
230 .filter(|p| {
231 p.get("s").and_then(Value::as_bool) == Some(true)
232 || p.get("s").and_then(Value::as_i64) == Some(1)
233 })
234 .map(|p| (self.numbers(p.get("x")), self.numbers(p.get("y"))));
235 if v.get("rx").is_some() || v.get("ry").is_some() {
236 self.note("rotação em 3D");
237 }
238 Transform {
239 anchor: self.numbers(v.get("a")),
240 position: self.numbers(position),
241 split,
242 scale: self.numbers(v.get("s")),
243 rotation: self.numbers(v.get("r").or_else(|| v.get("rz"))),
244 opacity: self.numbers(v.get("o")),
245 skew: self.numbers(v.get("sk")),
246 skew_axis: self.numbers(v.get("sa")),
247 orient: false,
248 }
249 }
250
251 fn stroke_look(&mut self, item: &Value) -> StrokeLook {
252 let dashes = item
253 .get("d")
254 .and_then(Value::as_array)
255 .map(|list| {
256 list.iter()
257 .map(|dash| {
258 (
259 dash.get("n").and_then(Value::as_str).unwrap_or("d").to_owned(),
260 self.numbers(dash.get("v")),
261 )
262 })
263 .collect()
264 })
265 .unwrap_or_default();
266 StrokeLook {
267 width: self.numbers(item.get("w")),
268 cap: item.get("lc").and_then(Value::as_i64).unwrap_or(1),
269 join: item.get("lj").and_then(Value::as_i64).unwrap_or(1),
270 miter_limit: number(item.get("ml"), 4.0),
271 dashes,
272 }
273 }
274
275 fn gradient(&mut self, item: &Value) -> GradientPaint {
276 if item
277 .get("h")
278 .is_some_and(|h| !numbers(h.get("k").unwrap_or(&Value::Null)).iter().all(|v| *v == 0.0))
279 {
280 self.note("gradiente com foco deslocado");
281 }
282 let g = item.get("g");
283 GradientPaint {
284 radial: item.get("t").and_then(Value::as_i64) == Some(2),
285 start: self.numbers(item.get("s")),
286 end: self.numbers(item.get("e")),
287 stops: self.numbers(g.and_then(|g| g.get("k"))),
288 count: g.and_then(|g| g.get("p")).and_then(Value::as_u64).unwrap_or(0) as usize,
289 }
290 }
291
292 fn items(&mut self, list: Option<&Value>) -> Vec<Item> {
293 let Some(list) = list.and_then(Value::as_array) else { return Vec::new() };
294 let mut items = Vec::with_capacity(list.len());
295 for item in list {
296 if item.get("hd").and_then(Value::as_bool) == Some(true) {
297 continue;
298 }
299 let even_odd = item.get("r").and_then(Value::as_i64) == Some(2);
300 let kind = item.get("ty").and_then(Value::as_str).unwrap_or("");
301 let read = match kind {
302 "gr" => {
303 let inner = item.get("it").and_then(Value::as_array);
304 let transform = inner
305 .and_then(|list| {
306 list.iter().find(|i| i.get("ty").and_then(Value::as_str) == Some("tr"))
307 })
308 .map(|tr| self.transform(Some(tr)));
309 Some(Item::Group(self.items(item.get("it")), transform))
310 }
311 "tr" => None,
313 "sh" => Some(Item::Path(Shape::parse(item.get("ks")))),
314 "rc" => Some(Item::Rect {
315 position: self.numbers(item.get("p")),
316 size: self.numbers(item.get("s")),
317 radius: self.numbers(item.get("r")),
318 }),
319 "el" => Some(Item::Ellipse {
320 position: self.numbers(item.get("p")),
321 size: self.numbers(item.get("s")),
322 }),
323 "sr" => Some(Item::Star {
324 polygon: item.get("sy").and_then(Value::as_i64) == Some(2),
325 position: self.numbers(item.get("p")),
326 rotation: self.numbers(item.get("r")),
327 points: self.numbers(item.get("pt")),
328 inner: self.numbers(item.get("ir")),
329 outer: self.numbers(item.get("or")),
330 inner_round: self.numbers(item.get("is")),
331 outer_round: self.numbers(item.get("os")),
332 }),
333 "fl" => Some(Item::Fill {
334 color: self.numbers(item.get("c")),
335 opacity: self.numbers(item.get("o")),
336 even_odd,
337 }),
338 "st" => Some(Item::Stroke {
339 color: self.numbers(item.get("c")),
340 opacity: self.numbers(item.get("o")),
341 look: self.stroke_look(item),
342 }),
343 "gf" => Some(Item::GradientFill {
344 paint: self.gradient(item),
345 opacity: self.numbers(item.get("o")),
346 even_odd,
347 }),
348 "gs" => Some(Item::GradientStroke {
349 paint: self.gradient(item),
350 opacity: self.numbers(item.get("o")),
351 look: self.stroke_look(item),
352 }),
353 "tm" => Some(Item::Trim {
354 start: self.numbers(item.get("s")),
355 end: self.numbers(item.get("e")),
356 offset: self.numbers(item.get("o")),
357 together: item.get("m").and_then(Value::as_i64) == Some(2),
358 }),
359 "rp" => {
360 let tr = item.get("tr");
361 Some(Item::Repeater {
362 copies: self.numbers(item.get("c")),
363 offset: self.numbers(item.get("o")),
364 transform: self.transform(tr),
365 start_opacity: self.numbers(tr.and_then(|t| t.get("so"))),
366 end_opacity: self.numbers(tr.and_then(|t| t.get("eo"))),
367 })
368 }
369 "rd" => Some(Item::RoundedCorners(self.numbers(item.get("r")))),
370 "mm" => {
371 self.note("juntar caminhos (merge paths)");
372 None
373 }
374 "tw" => {
375 self.note("torcer (twist)");
376 None
377 }
378 "pb" => {
379 self.note("inflar e contrair (pucker and bloat)");
380 None
381 }
382 "op" => {
383 self.note("deslocar caminho (offset path)");
384 None
385 }
386 other => {
387 self.note(&format!("forma do tipo {other}"));
388 None
389 }
390 };
391 items.extend(read);
392 }
393 items
394 }
395
396 fn layers(&mut self, list: Option<&Value>) -> Vec<Layer> {
397 let Some(list) = list.and_then(Value::as_array) else { return Vec::new() };
398 let mut layers = Vec::with_capacity(list.len());
399 for layer in list {
400 let kind = match layer.get("ty").and_then(Value::as_i64).unwrap_or(3) {
401 0 => {
402 let remap = layer.get("tm").map(|tm| self.numbers(Some(tm)));
403 LayerKind::Precomp {
404 reference: layer.get("refId").and_then(Value::as_str).unwrap_or("").to_owned(),
405 width: number(layer.get("w"), 0.0),
406 height: number(layer.get("h"), 0.0),
407 remap,
408 }
409 }
410 1 => LayerKind::Solid {
411 width: number(layer.get("sw"), 0.0),
412 height: number(layer.get("sh"), 0.0),
413 color: solid_color(layer.get("sc").and_then(Value::as_str).unwrap_or("#000000")),
414 },
415 2 => {
416 let found = layer.get("refId").and_then(Value::as_str).and_then(|id| self.images.get(id));
417 match found {
418 Some((image, width, height)) => {
419 LayerKind::Image { image: image.clone(), width: *width, height: *height }
420 }
421 None => {
422 self.note("imagens fora do arquivo");
423 LayerKind::Empty
424 }
425 }
426 }
427 3 => LayerKind::Empty,
428 4 => LayerKind::Shape(self.items(layer.get("shapes"))),
429 5 => {
430 self.note("texto");
431 LayerKind::Empty
432 }
433 other => {
434 self.note(&format!("camada do tipo {other}"));
435 LayerKind::Empty
436 }
437 };
438 if layer.get("bm").and_then(Value::as_i64).unwrap_or(0) != 0 {
439 self.note("modos de mistura");
440 }
441 let mut fill_effect = None;
442 for effect in layer.get("ef").and_then(Value::as_array).into_iter().flatten() {
443 if effect.get("en").and_then(Value::as_i64) == Some(0) {
444 continue;
445 }
446 match effect.get("ty").and_then(Value::as_i64) {
447 Some(21) => fill_effect = Some(self.fill_effect(effect)),
448 _ => self.note("efeitos do After Effects"),
449 }
450 }
451 if layer.get("ddd").and_then(Value::as_i64) == Some(1) {
452 self.note("camadas em 3D");
453 }
454 let matte_mode = layer.get("tt").and_then(Value::as_i64).unwrap_or(0);
455 if matte_mode >= 3 {
456 self.note("matte de luminosidade, tratado como alfa");
457 }
458 let masks = layer
459 .get("masksProperties")
460 .and_then(Value::as_array)
461 .map(|list| {
462 list.iter()
463 .filter_map(|mask| {
464 let mode = mask.get("mode").and_then(Value::as_str).unwrap_or("a");
465 if mode == "n" {
466 return None;
467 }
468 if mode != "a" && mode != "s" {
469 self.note("máscara que não soma nem subtrai, tratada como soma");
470 }
471 Some(Mask {
472 path: Shape::parse(mask.get("pt")),
473 subtract: mode == "s",
474 inverted: mask.get("inv").and_then(Value::as_bool).unwrap_or(false),
475 })
476 })
477 .collect()
478 })
479 .unwrap_or_default();
480 let start = number(layer.get("st"), 0.0);
481 layers.push(Layer {
482 kind,
483 index: layer.get("ind").and_then(Value::as_i64),
484 parent: layer.get("parent").and_then(Value::as_i64),
485 transform: Transform {
486 orient: layer.get("ao").and_then(Value::as_i64) == Some(1),
487 ..self.transform(layer.get("ks"))
488 },
489 in_frame: number(layer.get("ip"), f32::MIN),
490 out_frame: number(layer.get("op"), f32::MAX),
491 start,
492 stretch: {
493 let stretch = number(layer.get("sr"), 1.0);
494 if stretch == 0.0 { 1.0 } else { stretch }
495 },
496 hidden: layer.get("hd").and_then(Value::as_bool).unwrap_or(false),
497 masks,
498 matte_mode,
499 matte_index: layer.get("tp").and_then(Value::as_i64),
500 is_matte: layer.get("td").and_then(Value::as_i64).unwrap_or(0) != 0,
501 fill_effect,
502 });
503 }
504 layers
505 }
506}
507
508fn images_of(root: &Value) -> HashMap<String, (ImageData, f32, f32)> {
510 let mut images = HashMap::new();
511 for asset in root.get("assets").and_then(Value::as_array).into_iter().flatten() {
512 let (Some(id), Some(path)) =
513 (asset.get("id").and_then(Value::as_str), asset.get("p").and_then(Value::as_str))
514 else {
515 continue;
516 };
517 let Some(image) = path.split_once(";base64,").and_then(|(_, data)| decoded_image(data)) else {
518 continue;
519 };
520 let width = number(asset.get("w"), image.width() as f32);
521 let height = number(asset.get("h"), image.height() as f32);
522 images.insert(id.to_owned(), (image, width, height));
523 }
524 images
525}
526
527fn decoded_image(base64: &str) -> Option<ImageData> {
528 let picture = image::load_from_memory(&from_base64(base64)?).ok()?.into_rgba8();
529 ImageData::from_rgba8(picture.width(), picture.height(), picture.into_raw())
530}
531
532fn from_base64(text: &str) -> Option<Vec<u8>> {
533 let mut out = Vec::with_capacity(text.len() / 4 * 3);
534 let (mut bits, mut count) = (0u32, 0u32);
535 for byte in text.bytes() {
536 let value = match byte {
537 b'A'..=b'Z' => byte - b'A',
538 b'a'..=b'z' => byte - b'a' + 26,
539 b'0'..=b'9' => byte - b'0' + 52,
540 b'+' | b'-' => 62,
541 b'/' | b'_' => 63,
542 b'=' | b'\n' | b'\r' | b' ' => continue,
543 _ => return None,
544 };
545 bits = (bits << 6) | u32::from(value);
546 count += 6;
547 if count >= 8 {
548 count -= 8;
549 out.push((bits >> count) as u8);
550 bits &= (1 << count) - 1;
551 }
552 }
553 Some(out)
554}
555
556fn solid_color(text: &str) -> [f32; 3] {
557 let hex = text.trim_start_matches('#');
558 let part = |at: usize| {
559 hex.get(at..at + 2).and_then(|h| u8::from_str_radix(h, 16).ok()).map_or(0.0, |v| f32::from(v) / 255.0)
560 };
561 [part(0), part(2), part(4)]
562}
563
564#[cfg_attr(feature = "live", ravenrs_live::expose)]
565impl Animation {
566 pub fn from_json(bytes: &[u8]) -> Result<Self, LottieError> {
568 let root: Value = serde_json::from_slice(bytes).map_err(|e| LottieError::NotJson(e.to_string()))?;
569 let field =
570 |name: &'static str| root.get(name).and_then(Value::as_f64).ok_or(LottieError::Missing(name));
571 let (width, height) = (field("w")? as f32, field("h")? as f32);
572 let frame_rate = field("fr")? as f32;
573 let (in_frame, out_frame) = (field("ip")? as f32, field("op")? as f32);
574 if root.get("layers").is_none() {
575 return Err(LottieError::Missing("layers"));
576 }
577 let mut reader = Reader { unsupported: BTreeSet::new(), images: images_of(&root) };
578 let layers = reader.layers(root.get("layers"));
579 let mut precomps = HashMap::new();
580 if let Some(assets) = root.get("assets").and_then(Value::as_array) {
581 for asset in assets {
582 if let (Some(id), Some(_)) = (asset.get("id").and_then(Value::as_str), asset.get("layers")) {
583 let inner = reader.layers(asset.get("layers"));
584 precomps.insert(id.to_owned(), inner);
585 }
586 }
587 }
588 Ok(Self(Arc::new(Document {
589 width,
590 height,
591 frame_rate: frame_rate.max(1.0),
592 in_frame,
593 out_frame: out_frame.max(in_frame),
594 layers,
595 precomps,
596 unsupported: reader.unsupported,
597 })))
598 }
599
600 pub fn width(&self) -> f32 {
602 self.0.width
603 }
604
605 pub fn height(&self) -> f32 {
607 self.0.height
608 }
609
610 pub fn duration(&self) -> f64 {
612 f64::from(self.0.out_frame - self.0.in_frame) / f64::from(self.0.frame_rate)
613 }
614
615 pub fn frame_rate(&self) -> f32 {
618 self.0.frame_rate
619 }
620
621 pub fn unsupported(&self) -> Vec<String> {
624 self.0.unsupported.iter().cloned().collect()
625 }
626
627 pub fn paths_at(&self, seconds: f64, scale: f32) -> Vec<PathFill> {
631 player::paths_at(&self.0, seconds, scale)
632 }
633}