lottie_core/
lib.rs

1pub mod animatable;
2pub mod modifiers;
3pub mod renderer;
4#[cfg(feature = "expressions")]
5pub mod expressions;
6
7use animatable::Animator;
8#[cfg(feature = "expressions")]
9use expressions::ExpressionEvaluator;
10use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _};
11use glam::{Mat3, Mat4, Vec2, Vec3, Vec4};
12use kurbo::{BezPath, Point, Shape as _};
13use lottie_data::model::{self as data, LottieJson};
14use modifiers::{
15    GeometryModifier, OffsetPathModifier, PuckerBloatModifier, TwistModifier, WiggleModifier,
16    ZigZagModifier,
17};
18pub use renderer::*;
19use std::collections::{HashMap, HashSet};
20use std::f64::consts::PI;
21use std::sync::Arc;
22
23#[derive(Clone)]
24struct PendingGeometry {
25    kind: GeometryKind,
26    transform: Mat3,
27}
28
29#[derive(Clone)]
30enum GeometryKind {
31    Path(BezPath),
32    Rect { size: Vec2, pos: Vec2, radius: f32 },
33    Polystar(PolystarParams),
34    Ellipse { size: Vec2, pos: Vec2 },
35    Merge(Vec<PendingGeometry>, MergeMode),
36}
37
38impl PendingGeometry {
39    fn to_shape_geometry(&self, builder: &SceneGraphBuilder) -> ShapeGeometry {
40        match &self.kind {
41            GeometryKind::Merge(geoms, mode) => {
42                let shapes = geoms.iter().map(|g| g.to_shape_geometry(builder)).collect();
43                ShapeGeometry::Boolean {
44                    mode: *mode,
45                    shapes,
46                }
47            }
48            _ => ShapeGeometry::Path(self.to_path(builder)),
49        }
50    }
51
52    fn to_path(&self, builder: &SceneGraphBuilder) -> BezPath {
53        let mut path = match &self.kind {
54            GeometryKind::Path(p) => p.clone(),
55            GeometryKind::Merge(geoms, _) => {
56                let mut p = BezPath::new();
57                for g in geoms {
58                    p.extend(g.to_path(builder));
59                }
60                p
61            }
62            GeometryKind::Rect { size, pos, radius } => {
63                let half = *size / 2.0;
64                let rect = kurbo::Rect::new(
65                    (pos.x - half.x) as f64,
66                    (pos.y - half.y) as f64,
67                    (pos.x + half.x) as f64,
68                    (pos.y + half.y) as f64,
69                );
70                if *radius > 0.0 {
71                    rect.to_rounded_rect(*radius as f64).to_path(0.1)
72                } else {
73                    rect.to_path(0.1)
74                }
75            }
76            GeometryKind::Ellipse { size, pos } => {
77                let half = *size / 2.0;
78                let ellipse = kurbo::Ellipse::new(
79                    (pos.x as f64, pos.y as f64),
80                    (half.x as f64, half.y as f64),
81                    0.0,
82                );
83                ellipse.to_path(0.1)
84            }
85            GeometryKind::Polystar(params) => builder.generate_polystar_path(params),
86        };
87
88        let m = self.transform.to_cols_array();
89        let affine = kurbo::Affine::new([
90            m[0] as f64,
91            m[1] as f64,
92            m[3] as f64,
93            m[4] as f64,
94            m[6] as f64,
95            m[7] as f64,
96        ]);
97        path.apply_affine(affine);
98        path
99    }
100}
101
102#[derive(Clone, Copy)]
103struct PolystarParams {
104    pos: Vec2,
105    outer_radius: f32,
106    inner_radius: f32,
107    outer_roundness: f32,
108    inner_roundness: f32,
109    rotation: f32,
110    points: f32,
111    kind: u8,           // 1=star, 2=polygon
112    corner_radius: f32, // From RoundCorners modifier
113}
114
115pub enum ImageSource {
116    Data(Vec<u8>), // Encoded bytes (PNG/JPG)
117}
118
119pub trait TextMeasurer: Send + Sync {
120    /// Returns the width of the text string for the given font and size.
121    fn measure(&self, text: &str, font_family: &str, size: f32) -> f32;
122}
123
124/// Immutable, shared assets for a Lottie animation.
125pub struct LottieAsset {
126    pub model: LottieJson,
127    pub width: f32,
128    pub height: f32,
129    pub duration_frames: f32,
130    pub frame_rate: f32,
131    pub assets: HashMap<String, ImageSource>,
132    pub text_measurer: Option<Box<dyn TextMeasurer>>,
133}
134
135impl LottieAsset {
136    pub fn from_model(model: LottieJson) -> Self {
137        let width = model.w as f32;
138        let height = model.h as f32;
139        let frame_rate = model.fr;
140        let duration_frames = model.op - model.ip;
141
142        Self {
143            model,
144            width,
145            height,
146            duration_frames,
147            frame_rate,
148            assets: HashMap::new(),
149            text_measurer: None,
150        }
151    }
152
153    pub fn set_text_measurer(&mut self, measurer: Box<dyn TextMeasurer>) {
154        self.text_measurer = Some(measurer);
155    }
156
157    pub fn set_asset(&mut self, id: String, data: Vec<u8>) {
158        self.assets.insert(id, ImageSource::Data(data));
159    }
160}
161
162pub struct LottiePlayer {
163    pub asset: Option<Arc<LottieAsset>>,
164    pub current_frame: f32,
165    #[cfg(feature = "expressions")]
166    pub expression_evaluator: Option<ExpressionEvaluator>,
167}
168
169impl LottiePlayer {
170    pub fn new() -> Self {
171        #[cfg(feature = "expressions")]
172        let expression_evaluator = Some(ExpressionEvaluator::new());
173        Self {
174            asset: None,
175            current_frame: 0.0,
176            #[cfg(feature = "expressions")]
177            expression_evaluator,
178        }
179    }
180
181    pub fn load(&mut self, asset: Arc<LottieAsset>) {
182        self.current_frame = asset.model.ip; // Start at in-point
183        self.asset = Some(asset);
184    }
185
186    // Legacy load for convenience (creates new Asset wrapper)
187    pub fn load_json(&mut self, data: LottieJson) {
188         let asset = Arc::new(LottieAsset::from_model(data));
189         self.load(asset);
190    }
191
192    pub fn advance(&mut self, dt: f32) {
193        if let Some(asset) = &self.asset {
194            // dt is in seconds
195            let frames = dt * asset.frame_rate;
196            self.current_frame += frames;
197
198            // Loop
199            if self.current_frame >= asset.model.op {
200                let duration = asset.model.op - asset.model.ip;
201                self.current_frame = asset.model.ip
202                    + (self.current_frame - asset.model.op) % duration;
203            }
204        }
205    }
206
207    pub fn render_tree(&mut self) -> RenderTree {
208        if let Some(asset) = &self.asset {
209            #[cfg(feature = "expressions")]
210            let evaluator = self.expression_evaluator.as_mut();
211            #[cfg(not(feature = "expressions"))]
212            let evaluator: Option<&mut ()> = None;
213
214            let mut builder = SceneGraphBuilder::new(
215                asset,
216                self.current_frame,
217            );
218            builder.build(evaluator)
219        } else {
220            // Return empty tree
221            RenderTree::mock_sample()
222        }
223    }
224}
225
226struct SceneGraphBuilder<'a> {
227    asset: &'a LottieAsset,
228    frame: f32,
229    model_assets_map: HashMap<String, &'a data::Asset>,
230}
231
232impl<'a> SceneGraphBuilder<'a> {
233    fn new(
234        asset: &'a LottieAsset,
235        frame: f32,
236    ) -> Self {
237        let mut model_assets_map = HashMap::new();
238        for a in &asset.model.assets {
239            model_assets_map.insert(a.id.clone(), a);
240        }
241        Self {
242            asset,
243            frame,
244            model_assets_map,
245        }
246    }
247
248    fn build(&mut self, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> RenderTree {
249        let mut layer_map = HashMap::new();
250        for layer in &self.asset.model.layers {
251            if let Some(ind) = layer.ind {
252                layer_map.insert(ind, layer);
253            }
254        }
255
256        let (view_matrix, projection_matrix) = self.get_camera_matrices(&self.asset.model.layers, &layer_map, evaluator.as_deref_mut());
257
258        let root_node = self.build_composition(&self.asset.model.layers, &layer_map, evaluator);
259
260        RenderTree {
261            width: self.asset.width,
262            height: self.asset.height,
263            root: root_node,
264            view_matrix,
265            projection_matrix,
266        }
267    }
268
269    fn get_camera_matrices(&self, layers: &'a [data::Layer], map: &HashMap<u32, &'a data::Layer>, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> (Mat4, Mat4) {
270        // Step 1: Find Active Camera (Top-most, ty=13, visible)
271        let mut camera_layer = None;
272        for layer in layers {
273            if layer.ty == 13 {
274                 // Check visibility
275                 if self.frame >= layer.ip && self.frame < layer.op {
276                     camera_layer = Some(layer);
277                     break; // Top-most found
278                 }
279            }
280        }
281
282        if let Some(cam) = camera_layer {
283            // Step 2: Compute View Matrix
284            let cam_transform = self.resolve_transform(cam, map, evaluator.as_deref_mut());
285            let view_matrix = cam_transform.inverse();
286
287            // Step 3: Compute Projection Matrix
288            let pe = if let Some(prop) = &cam.pe {
289                Animator::resolve(prop, self.frame - cam.st, |v| *v, 0.0, evaluator, self.asset.frame_rate)
290            } else {
291                0.0
292            };
293
294            let perspective = if pe > 0.0 { pe } else { 1000.0 }; // Default ?
295
296            // FOV Calculation
297            // pe is distance. Height is comp height.
298            // tan(fov/2) = (height/2) / pe
299            // fov = 2 * atan(height / (2 * pe))
300            let fov = 2.0 * (self.asset.height / (2.0 * perspective)).atan();
301
302            let aspect = self.asset.width / self.asset.height;
303            let near = 0.1;
304            let far = 10000.0;
305
306            let projection_matrix = Mat4::perspective_rh(fov, aspect, near, far);
307
308            (view_matrix, projection_matrix)
309
310        } else {
311            // Default 2D View
312            (Mat4::IDENTITY, Mat4::IDENTITY)
313        }
314    }
315
316    fn build_composition(&mut self, layers: &'a [data::Layer], layer_map: &HashMap<u32, &'a data::Layer>, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> RenderNode {
317        let mut nodes = Vec::new();
318        let mut consumed_indices = HashSet::new();
319        let len = layers.len();
320
321        for i in (0..len).rev() {
322            if consumed_indices.contains(&i) {
323                continue;
324            }
325
326            let layer = &layers[i];
327
328            if let Some(tt) = layer.tt {
329                if i > 0 {
330                    let matte_idx = i - 1;
331                    if !consumed_indices.contains(&matte_idx) {
332                        consumed_indices.insert(matte_idx);
333                        let matte_layer = &layers[matte_idx];
334
335                        if let Some(mut content_node) = self.process_layer(layer, layer_map, evaluator.as_deref_mut()) {
336                            if let Some(matte_node) = self.process_layer(matte_layer, layer_map, evaluator.as_deref_mut()) {
337                                let mode = match tt {
338                                    1 => MatteMode::Alpha,
339                                    2 => MatteMode::AlphaInverted,
340                                    3 => MatteMode::Luma,
341                                    4 => MatteMode::LumaInverted,
342                                    _ => MatteMode::Alpha,
343                                };
344                                content_node.matte = Some(Box::new(Matte {
345                                    mode,
346                                    node: matte_node,
347                                }));
348                            }
349                            nodes.push(content_node);
350                        }
351                        continue;
352                    }
353                }
354            }
355
356            if let Some(node) = self.process_layer(layer, layer_map, evaluator.as_deref_mut()) {
357                nodes.push(node);
358            }
359        }
360
361        RenderNode {
362            transform: Mat4::IDENTITY,
363            alpha: 1.0,
364            blend_mode: BlendMode::Normal,
365            content: NodeContent::Group(nodes),
366            masks: vec![], styles: vec![],
367            matte: None,
368            effects: vec![],
369            is_adjustment_layer: false,
370        }
371    }
372
373    fn process_layer(
374        &mut self,
375        layer: &'a data::Layer,
376        layer_map: &HashMap<u32, &'a data::Layer>,
377        #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>,
378        #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>,
379    ) -> Option<RenderNode> {
380        let is_adjustment_layer = layer.ao == Some(1);
381
382        if self.frame < layer.ip || self.frame >= layer.op {
383            return None;
384        }
385
386        let transform = self.resolve_transform(layer, layer_map, evaluator.as_deref_mut());
387
388        let opacity = Animator::resolve(&layer.ks.o, self.frame - layer.st, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
389
390        let content = if let Some(shapes) = &layer.shapes {
391            let shape_nodes = self.process_shapes(shapes, self.frame - layer.st, evaluator.as_deref_mut());
392            NodeContent::Group(shape_nodes)
393        } else if let Some(text_data) = &layer.t {
394            // Text Layer
395            let doc = Animator::resolve(
396                &text_data.d,
397                self.frame - layer.st,
398                |v| v.clone(),
399                data::TextDocument::default(),
400                evaluator.as_deref_mut(), self.asset.frame_rate
401            );
402
403            let base_fill_color = Vec4::new(doc.fc[0], doc.fc[1], doc.fc[2], 1.0);
404            let base_stroke_color = if let Some(sc) = &doc.sc {
405                Some(Vec4::new(sc[0], sc[1], sc[2], 1.0))
406            } else {
407                None
408            };
409
410            let chars: Vec<char> = doc.t.chars().collect();
411            let char_count = chars.len();
412
413            let mut glyphs = Vec::with_capacity(char_count);
414
415            for &c in &chars {
416                let g = RenderGlyph {
417                    character: c,
418                    pos: Vec3::ZERO,
419                    scale: Vec3::ONE,
420                    rotation: Vec3::ZERO,
421                    tracking: 0.0,
422                    alpha: 1.0,
423                    fill: Some(Fill {
424                        paint: Paint::Solid(base_fill_color),
425                        opacity: 1.0,
426                        rule: FillRule::NonZero,
427                    }),
428                    stroke: if let Some(col) = base_stroke_color {
429                        Some(Stroke {
430                            paint: Paint::Solid(col),
431                            width: doc.sw.unwrap_or(1.0),
432                            opacity: 1.0,
433                            cap: LineCap::Round,
434                            join: LineJoin::Round,
435                            miter_limit: None,
436                            dash: None,
437                        })
438                    } else {
439                        None
440                    },
441                };
442                glyphs.push(g);
443            }
444
445            if let Some(animators) = &text_data.a {
446                for animator in animators {
447                    let sel = &animator.s;
448                    let start_val = Animator::resolve(
449                        sel.s.as_ref().unwrap_or(&data::Property::default()),
450                        self.frame - layer.st,
451                        |v| *v,
452                        0.0,
453                        evaluator.as_deref_mut(), self.asset.frame_rate
454                    );
455                    let end_val = Animator::resolve(
456                        sel.e.as_ref().unwrap_or(&data::Property::default()),
457                        self.frame - layer.st,
458                        |v| *v,
459                        100.0,
460                        evaluator.as_deref_mut(), self.asset.frame_rate
461                    );
462                    let offset_val = Animator::resolve(
463                        sel.o.as_ref().unwrap_or(&data::Property::default()),
464                        self.frame - layer.st,
465                        |v| *v,
466                        0.0,
467                        evaluator.as_deref_mut(), self.asset.frame_rate
468                    );
469
470                    let start_idx = char_count as f32 * start_val / 100.0;
471                    let end_idx = char_count as f32 * end_val / 100.0;
472                    let offset_idx = char_count as f32 * offset_val / 100.0;
473
474                    let style = &animator.a;
475                    let p_delta = Animator::resolve(
476                        style.p.as_ref().unwrap_or(&data::Property::default()),
477                        self.frame - layer.st,
478                        |v| Vec3::from(v.0),
479                        Vec3::ZERO,
480                        evaluator.as_deref_mut(), self.asset.frame_rate
481                    );
482                    let s_val = Animator::resolve(
483                        style.s.as_ref().unwrap_or(&data::Property::default()),
484                        self.frame - layer.st,
485                        |v| Vec3::from(v.0) / 100.0,
486                        Vec3::ONE,
487                        evaluator.as_deref_mut(), self.asset.frame_rate
488                    );
489                    let o_val = Animator::resolve(
490                        style.o.as_ref().unwrap_or(&data::Property::default()),
491                        self.frame - layer.st,
492                        |v| *v,
493                        100.0,
494                        evaluator.as_deref_mut(), self.asset.frame_rate
495                    );
496                    // RZ
497                    let r_val = Animator::resolve(
498                        style.r.as_ref().unwrap_or(&data::Property::default()),
499                        self.frame - layer.st,
500                        |v| *v,
501                        0.0,
502                        evaluator.as_deref_mut(), self.asset.frame_rate
503                    );
504
505                    // Tracking
506                    let t_val = Animator::resolve(
507                        style.t.as_ref().unwrap_or(&data::Property::default()),
508                        self.frame - layer.st,
509                        |v| *v,
510                        0.0,
511                        evaluator.as_deref_mut(), self.asset.frame_rate
512                    );
513
514                    let fc_val = if let Some(fc_prop) = &style.fc {
515                        Some(Animator::resolve(
516                            fc_prop,
517                            self.frame - layer.st,
518                            |v| Vec4::from_slice(v),
519                            Vec4::ONE,
520                            evaluator.as_deref_mut(), self.asset.frame_rate
521                        ))
522                    } else {
523                        None
524                    };
525
526                    let sc_val = if let Some(sc_prop) = &style.sc {
527                        Some(Animator::resolve(
528                            sc_prop,
529                            self.frame - layer.st,
530                            |v| Vec4::from_slice(v),
531                            Vec4::ONE,
532                            evaluator.as_deref_mut(), self.asset.frame_rate
533                        ))
534                    } else {
535                        None
536                    };
537
538                    for (i, glyph) in glyphs.iter_mut().enumerate() {
539                        let idx = i as f32;
540                        let effective_start = start_idx + offset_idx;
541                        let effective_end = end_idx + offset_idx;
542
543                        let overlap_start = idx.max(effective_start);
544                        let overlap_end = (idx + 1.0).min(effective_end);
545
546                        let factor = (overlap_end - overlap_start).max(0.0).min(1.0);
547
548                        if factor > 0.0 {
549                            glyph.pos += p_delta * factor;
550
551                            // Scale mixing
552                            let scale_factor = Vec3::ONE + (s_val - Vec3::ONE) * factor;
553                            glyph.scale *= scale_factor;
554
555                            // Rotation (RZ only for now, mapped to Z component)
556                            glyph.rotation.z += r_val.to_radians() * factor;
557
558                            glyph.tracking += t_val * factor;
559
560                            let target_alpha = o_val / 100.0;
561                            let alpha_mult = 1.0 + (target_alpha - 1.0) * factor;
562                            glyph.alpha *= alpha_mult;
563
564                            if let Some(target_color) = fc_val {
565                                if let Some(fill) = &mut glyph.fill {
566                                    if let Paint::Solid(current_color) = &mut fill.paint {
567                                        *current_color = current_color.lerp(target_color, factor);
568                                    }
569                                }
570                            }
571
572                            if let Some(target_color) = sc_val {
573                                if let Some(stroke) = &mut glyph.stroke {
574                                    if let Paint::Solid(current_color) = &mut stroke.paint {
575                                        *current_color = current_color.lerp(target_color, factor);
576                                    }
577                                }
578                            }
579                        }
580                    }
581                }
582            }
583
584            // Layout
585            if let Some(measurer) = self.asset.text_measurer.as_deref() {
586                let box_size = doc.sz.map(|v| Vec2::from_slice(&v));
587                let box_pos = doc.ps.map(|v| Vec2::from_slice(&v)).unwrap_or(Vec2::ZERO);
588                let tracking_val = doc.tr;
589
590                if let Some(sz) = box_size {
591                    // Box Text
592                    let box_width = sz.x;
593                    let mut lines: Vec<Vec<usize>> = Vec::new();
594                    let mut current_line: Vec<usize> = Vec::new();
595                    let mut current_line_width = 0.0;
596
597                    let mut i = 0;
598                    while i < glyphs.len() {
599                        let start = i;
600                        let mut end = i;
601                        let mut word_width = 0.0;
602
603                        while end < glyphs.len() {
604                            let g = &glyphs[end];
605                            let char_str = g.character.to_string();
606                            let w = measurer.measure(&char_str, &doc.f, doc.s);
607                            let advance = w + tracking_val + g.tracking;
608                            word_width += advance;
609                            let is_space = g.character == ' ';
610                            let is_newline = g.character == '\n';
611                            end += 1;
612                            if is_space || is_newline {
613                                break;
614                            }
615                        }
616
617                        let is_newline = if end > 0 { glyphs[end-1].character == '\n' } else { false };
618
619                        if is_newline {
620                             for k in start..end { current_line.push(k); }
621                             lines.push(current_line);
622                             current_line = Vec::new();
623                             current_line_width = 0.0;
624                        } else {
625                            if !current_line.is_empty() && current_line_width + word_width > box_width {
626                                lines.push(current_line);
627                                current_line = Vec::new();
628                                current_line_width = 0.0;
629                            }
630                            for k in start..end { current_line.push(k); }
631                            current_line_width += word_width;
632                        }
633                        i = end;
634                    }
635                    if !current_line.is_empty() { lines.push(current_line); }
636
637                    let mut current_y = box_pos.y;
638                    for line_indices in lines {
639                         let mut line_width = 0.0;
640                         let mut advances = Vec::new();
641
642                         for &idx in &line_indices {
643                             let g = &glyphs[idx];
644                             let w = measurer.measure(&g.character.to_string(), &doc.f, doc.s);
645                             let advance = w + tracking_val + g.tracking;
646                             advances.push(advance);
647                             line_width += advance;
648                         }
649
650                         let align_width = line_width;
651                         let start_x = match doc.j {
652                             1 => box_width - align_width,
653                             2 => (box_width - align_width) / 2.0,
654                             _ => 0.0,
655                         };
656
657                         let mut x = box_pos.x + start_x;
658                         for (k, &idx) in line_indices.iter().enumerate() {
659                             let g = &mut glyphs[idx];
660                             g.pos += Vec3::new(x, current_y, 0.0);
661                             x += advances[k];
662                         }
663                         current_y += doc.lh;
664                    }
665
666                } else {
667                    // Point Text
668                    let mut current_y = 0.0;
669                    let mut lines: Vec<Vec<usize>> = Vec::new();
670                    let mut current_line = Vec::new();
671
672                    for (i, g) in glyphs.iter().enumerate() {
673                        if g.character == '\n' {
674                            lines.push(current_line);
675                            current_line = Vec::new();
676                        } else {
677                            current_line.push(i);
678                        }
679                    }
680                    lines.push(current_line);
681
682                    for line_indices in lines {
683                        let mut line_width = 0.0;
684                        let mut advances = Vec::new();
685                        for &idx in &line_indices {
686                             let g = &glyphs[idx];
687                             let w = measurer.measure(&g.character.to_string(), &doc.f, doc.s);
688                             let advance = w + tracking_val + g.tracking;
689                             advances.push(advance);
690                             line_width += advance;
691                         }
692
693                        let start_x = match doc.j {
694                            1 => -line_width,
695                            2 => -line_width / 2.0,
696                            _ => 0.0,
697                        };
698
699                        let mut x = start_x;
700                        for (k, &idx) in line_indices.iter().enumerate() {
701                             let g = &mut glyphs[idx];
702                             g.pos += Vec3::new(x, current_y, 0.0);
703                             x += advances[k];
704                         }
705                         current_y += doc.lh;
706                    }
707                }
708            } else {
709                 let fixed_width = 10.0;
710                 let mut x = 0.0;
711                 let mut y = 0.0;
712                 for g in &mut glyphs {
713                     if g.character == '\n' {
714                         x = 0.0;
715                         y += doc.lh;
716                     } else {
717                         g.pos += Vec3::new(x, y, 0.0);
718                         x += fixed_width + doc.tr + g.tracking;
719                     }
720                 }
721            }
722
723            NodeContent::Text(Text {
724                glyphs,
725                font_family: doc.f,
726                size: doc.s,
727                justify: match doc.j {
728                    1 => Justification::Right,
729                    2 => Justification::Center,
730                    _ => Justification::Left,
731                },
732                tracking: doc.tr,
733                line_height: doc.lh,
734            })
735        } else if let Some(ref_id) = &layer.ref_id {
736            if let Some(asset) = self.model_assets_map.get(ref_id) {
737                if let Some(layers) = &asset.layers {
738                    let local_frame = if let Some(tm_prop) = &layer.tm {
739                        let tm_sec = Animator::resolve(tm_prop, self.frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
740                        tm_sec * self.asset.frame_rate
741                    } else {
742                        self.frame - layer.st
743                    };
744
745                    let mut sub_layer_map = HashMap::new();
746                    for l in layers {
747                        if let Some(ind) = l.ind {
748                            sub_layer_map.insert(ind, l);
749                        }
750                    }
751
752                    let mut sub_builder = SceneGraphBuilder::new(
753                        self.asset,
754                        local_frame,
755                    );
756                    let root = sub_builder.build_composition(layers, &sub_layer_map, evaluator.as_deref_mut());
757                    root.content
758                } else {
759                    let data = if let Some(ImageSource::Data(bytes)) =
760                        self.asset.assets.get(&asset.id)
761                    {
762                        Some(bytes.clone())
763                    } else if let Some(p) = &asset.p {
764                        if p.starts_with("data:image/") && p.contains(";base64,") {
765                            let split: Vec<&str> = p.splitn(2, ',').collect();
766                            if split.len() > 1 {
767                                match BASE64_STANDARD.decode(split[1]) {
768                                    Ok(bytes) => Some(bytes),
769                                    Err(_) => None
770                                }
771                            } else {
772                                None
773                            }
774                        } else {
775                            if let Ok(bytes) = std::fs::read(p) {
776                                Some(bytes)
777                            } else {
778                                None
779                            }
780                        }
781                    } else {
782                        None
783                    };
784
785                    NodeContent::Image(Image {
786                        data,
787                        width: asset.w.unwrap_or(100),
788                        height: asset.h.unwrap_or(100),
789                        id: Some(asset.id.clone()),
790                    })
791                }
792            } else {
793                NodeContent::Group(vec![])
794            }
795        } else if let Some(color) = &layer.color {
796            let w = layer.sw.unwrap_or(100) as f64;
797            let h = layer.sh.unwrap_or(100) as f64;
798            let mut path = BezPath::new();
799            path.move_to((0.0, 0.0));
800            path.line_to((w, 0.0));
801            path.line_to((w, h));
802            path.line_to((0.0, h));
803            path.close_path();
804
805            let c_str = color.trim_start_matches('#');
806            let r = u8::from_str_radix(&c_str[0..2], 16).unwrap_or(0) as f32 / 255.0;
807            let g = u8::from_str_radix(&c_str[2..4], 16).unwrap_or(0) as f32 / 255.0;
808            let b = u8::from_str_radix(&c_str[4..6], 16).unwrap_or(0) as f32 / 255.0;
809
810            NodeContent::Shape(renderer::Shape {
811                geometry: renderer::ShapeGeometry::Path(path),
812                fill: Some(Fill {
813                    paint: Paint::Solid(Vec4::new(r, g, b, 1.0)),
814                    opacity: 1.0,
815                    rule: FillRule::NonZero,
816                }),
817                stroke: None,
818                trim: None,
819            })
820        } else {
821            NodeContent::Group(vec![])
822        };
823
824        let masks = if let Some(props) = &layer.masks_properties {
825            self.process_masks(props, self.frame - layer.st, evaluator.as_deref_mut())
826        } else {
827            vec![]
828        };
829
830        let effects = self.process_effects(layer, evaluator.as_deref_mut());
831        let styles = self.process_layer_styles(layer, evaluator.as_deref_mut());
832
833        Some(RenderNode {
834            transform,
835            alpha: opacity,
836            blend_mode: BlendMode::Normal,
837            content,
838            masks,
839            matte: None,
840            effects,
841            styles,
842            is_adjustment_layer,
843        })
844    }
845
846    fn process_layer_styles(&self, layer: &data::Layer, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Vec<LayerStyle> {
847        let mut styles = Vec::new();
848        if let Some(sy_list) = &layer.sy {
849            for sy in sy_list {
850                let ty = sy.ty.unwrap_or(8);
851                let mut kind = None;
852                if ty == 0 { kind = Some("DropShadow"); }
853                else if ty == 1 { kind = Some("InnerShadow"); }
854                else if ty == 2 { kind = Some("OuterGlow"); }
855                else if let Some(nm) = &sy.nm {
856                    if nm.contains("Stroke") { kind = Some("Stroke"); }
857                }
858
859                if kind.is_none() {
860                    if ty == 3 || ty == 8 {
861                        kind = Some("Stroke");
862                    }
863                }
864
865                if let Some(k) = kind {
866                    match k {
867                        "DropShadow" => {
868                             let color = self.resolve_json_vec4_arr(&sy.c, self.frame - layer.st, evaluator.as_deref_mut());
869                             let opacity = Animator::resolve(&sy.o, self.frame - layer.st, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
870                             let angle = Animator::resolve(&sy.a, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
871                             let distance = Animator::resolve(&sy.d, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
872                             let size = Animator::resolve(&sy.s, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
873                             let spread = Animator::resolve(&sy.ch, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
874                             styles.push(LayerStyle::DropShadow {
875                                 color, opacity, angle, distance, size, spread
876                             });
877                        },
878                        "InnerShadow" => {
879                             let color = self.resolve_json_vec4_arr(&sy.c, self.frame - layer.st, evaluator.as_deref_mut());
880                             let opacity = Animator::resolve(&sy.o, self.frame - layer.st, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
881                             let angle = Animator::resolve(&sy.a, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
882                             let distance = Animator::resolve(&sy.d, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
883                             let size = Animator::resolve(&sy.s, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
884                             let choke = Animator::resolve(&sy.ch, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
885                             styles.push(LayerStyle::InnerShadow {
886                                 color, opacity, angle, distance, size, choke
887                             });
888                        },
889                        "OuterGlow" => {
890                             let color = self.resolve_json_vec4_arr(&sy.c, self.frame - layer.st, evaluator.as_deref_mut());
891                             let opacity = Animator::resolve(&sy.o, self.frame - layer.st, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
892                             let size = Animator::resolve(&sy.s, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
893                             let range = Animator::resolve(&sy.ch, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
894                             styles.push(LayerStyle::OuterGlow {
895                                 color, opacity, size, range
896                             });
897                        },
898                        "Stroke" => {
899                             let color = self.resolve_json_vec4_arr(&sy.c, self.frame - layer.st, evaluator.as_deref_mut());
900                             let opacity = Animator::resolve(&sy.o, self.frame - layer.st, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
901                             let width = Animator::resolve(&sy.s, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
902                             styles.push(LayerStyle::Stroke {
903                                 color, width, opacity
904                             });
905                        },
906                        _ => {}
907                    }
908                }
909            }
910        }
911        styles
912    }
913
914    fn process_effects(&self, layer: &data::Layer, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Vec<Effect> {
915        let mut effects = Vec::new();
916        if let Some(ef_list) = &layer.ef {
917            for ef in ef_list {
918                if let Some(en) = ef.en { if en == 0 { continue; } }
919                let ty = ef.ty.unwrap_or(0);
920                let values = if let Some(vals) = &ef.ef { vals } else { continue; };
921
922                match ty {
923                    20 => {
924                        let black = self.find_effect_vec4(values, 0, "Black", layer, evaluator.as_deref_mut());
925                        let white = self.find_effect_vec4(values, 1, "White", layer, evaluator.as_deref_mut());
926                        let amount = self.find_effect_scalar(values, 2, "Intensity", layer, evaluator.as_deref_mut()) / 100.0;
927                        effects.push(Effect::Tint { black, white, amount });
928                    }
929                    21 => {
930                        let color = self.find_effect_vec4(values, 2, "Color", layer, evaluator.as_deref_mut());
931                        let opacity = self.find_effect_scalar(values, 6, "Opacity", layer, evaluator.as_deref_mut()) / 100.0;
932                        effects.push(Effect::Fill { color, opacity });
933                    }
934                    22 => {
935                        let color = self.find_effect_vec4(values, 3, "Color", layer, evaluator.as_deref_mut());
936                        let width = self.find_effect_scalar(values, 4, "Brush Size", layer, evaluator.as_deref_mut());
937                        let opacity = self.find_effect_scalar(values, 6, "Opacity", layer, evaluator.as_deref_mut()) / 100.0;
938                        let all_masks_val = self.find_effect_scalar(values, 9999, "All Masks", layer, evaluator.as_deref_mut());
939                        let all_masks = all_masks_val > 0.5;
940                        let mut mask_idx_val = self.find_effect_scalar(values, 9999, "Path", layer, evaluator.as_deref_mut());
941                        if mask_idx_val < 0.5 { mask_idx_val = self.find_effect_scalar(values, 9999, "Mask", layer, evaluator.as_deref_mut()); }
942                        let mask_index = if mask_idx_val >= 0.5 { Some(mask_idx_val.round() as usize) } else { None };
943                        effects.push(Effect::Stroke { color, width, opacity, mask_index, all_masks });
944                    }
945                    23 => {
946                        let highlights = self.find_effect_vec4(values, 0, "bright", layer, evaluator.as_deref_mut());
947                        let midtones = self.find_effect_vec4(values, 1, "mid", layer, evaluator.as_deref_mut());
948                        let shadows = self.find_effect_vec4(values, 2, "dark", layer, evaluator.as_deref_mut());
949                        effects.push(Effect::Tritone { highlights, midtones, shadows });
950                    }
951                    24 => {
952                        let in_black = self.find_effect_scalar(values, 3, "inblack", layer, evaluator.as_deref_mut());
953                        let in_white = self.find_effect_scalar(values, 4, "inwhite", layer, evaluator.as_deref_mut());
954                        let gamma = self.find_effect_scalar(values, 5, "gamma", layer, evaluator.as_deref_mut());
955                        let out_black = self.find_effect_scalar(values, 6, "outblack", layer, evaluator.as_deref_mut());
956                        let out_white = self.find_effect_scalar(values, 7, "outwhite", layer, evaluator.as_deref_mut());
957                        effects.push(Effect::Levels { in_black, in_white, gamma, out_black, out_white });
958                    }
959                    _ => {}
960                }
961            }
962        }
963        effects
964    }
965
966    fn find_effect_scalar(&self, values: &[data::EffectValue], index: usize, name_hint: &str, layer: &data::Layer, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> f32 {
967        if let Some(v) = values.get(index) {
968            if let Some(prop) = &v.v {
969                return self.resolve_json_scalar(prop, self.frame - layer.st, evaluator.as_deref_mut());
970            }
971        }
972        for v in values {
973            if let Some(nm) = &v.nm {
974                if nm.contains(name_hint) {
975                    if let Some(prop) = &v.v {
976                        return self.resolve_json_scalar(prop, self.frame - layer.st, evaluator.as_deref_mut());
977                    }
978                }
979            }
980        }
981        0.0
982    }
983
984    fn find_effect_vec4(&self, values: &[data::EffectValue], index: usize, name_hint: &str, layer: &data::Layer, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Vec4 {
985        if let Some(v) = values.get(index) {
986            if let Some(prop) = &v.v {
987                return self.resolve_json_vec4(prop, self.frame - layer.st, evaluator.as_deref_mut());
988            }
989        }
990        for v in values {
991            if let Some(nm) = &v.nm {
992                if nm.contains(name_hint) {
993                    if let Some(prop) = &v.v {
994                        return self.resolve_json_vec4(prop, self.frame - layer.st, evaluator.as_deref_mut());
995                    }
996                }
997            }
998        }
999        Vec4::ZERO
1000    }
1001
1002    fn resolve_json_scalar(&self, prop: &data::Property<serde_json::Value>, frame: f32, #[cfg(feature = "expressions")] evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> f32 {
1003        Animator::resolve(prop, frame, |v| {
1004            if let Some(n) = v.as_f64() { n as f32 }
1005            else if let Some(arr) = v.as_array() { arr.get(0).and_then(|x| x.as_f64()).unwrap_or(0.0) as f32 }
1006            else { 0.0 }
1007        }, 0.0, evaluator, self.asset.frame_rate)
1008    }
1009
1010    fn resolve_json_vec4(&self, prop: &data::Property<serde_json::Value>, frame: f32, #[cfg(feature = "expressions")] evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Vec4 {
1011        Animator::resolve(prop, frame, |v| {
1012            if let Some(arr) = v.as_array() {
1013                let r = arr.get(0).and_then(|x| x.as_f64()).unwrap_or(0.0) as f32;
1014                let g = arr.get(1).and_then(|x| x.as_f64()).unwrap_or(0.0) as f32;
1015                let b = arr.get(2).and_then(|x| x.as_f64()).unwrap_or(0.0) as f32;
1016                let a = arr.get(3).and_then(|x| x.as_f64()).unwrap_or(1.0) as f32;
1017                Vec4::new(r, g, b, a)
1018            } else { Vec4::ZERO }
1019        }, Vec4::ZERO, evaluator, self.asset.frame_rate)
1020    }
1021
1022    fn resolve_json_vec4_arr(&self, prop: &data::Property<Vec<f32>>, frame: f32, #[cfg(feature = "expressions")] evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Vec4 {
1023        Animator::resolve(prop, frame, |v| {
1024            if v.len() >= 4 { Vec4::new(v[0], v[1], v[2], v[3]) }
1025            else if v.len() >= 3 { Vec4::new(v[0], v[1], v[2], 1.0) }
1026            else { Vec4::ZERO }
1027        }, Vec4::ONE, evaluator, self.asset.frame_rate)
1028    }
1029
1030    fn resolve_transform(&self, layer: &data::Layer, map: &HashMap<u32, &data::Layer>, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Mat4 {
1031        let local = self.get_layer_transform(layer, evaluator.as_deref_mut());
1032        if let Some(parent_ind) = layer.parent {
1033            if let Some(parent) = map.get(&parent_ind) {
1034                return self.resolve_transform(parent, map, evaluator) * local;
1035            }
1036        }
1037        local
1038    }
1039
1040    fn get_layer_transform(&self, layer: &data::Layer, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Mat4 {
1041        let t_frame = self.frame - layer.st;
1042        let ks = &layer.ks;
1043
1044        let is_3d = layer.ddd.unwrap_or(0) == 1 || layer.ty == 13;
1045
1046        // Camera LookAt Check
1047        if layer.ty == 13 {
1048             // Position
1049             let pos = match &ks.p {
1050                 data::PositionProperty::Unified(p) => Animator::resolve(p, t_frame, |v| Vec3::from(v.0), Vec3::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate),
1051                 data::PositionProperty::Split { x, y, z } => {
1052                     let px = Animator::resolve(x, t_frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1053                     let py = Animator::resolve(y, t_frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1054                     let pz = if let Some(z_prop) = z { Animator::resolve(z_prop, t_frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate) } else { 0.0 };
1055                     Vec3::new(px, py, pz)
1056                 }
1057             };
1058
1059             // Point of Interest (Anchor)
1060             let anchor = Animator::resolve(&ks.a, t_frame, |v| Vec3::from(v.0), Vec3::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1061
1062             // Use LookAt logic
1063             // View = LookAt(pos, anchor, up)
1064             // Global Transform = View.inverse()
1065             // But we are resolving LOCAL transform here?
1066             // As established, we assume p and a are in parent/global space context.
1067             // If Camera has parent, this local transform is applied relative to parent.
1068             // LookAt constructs a matrix that transforms points from Local(Camera) to World (or Parent).
1069             // Actually `look_at_rh` creates View Matrix (World -> Camera).
1070             // We want Camera -> World (Transform).
1071             // So we return `look_at_rh(pos, anchor, UP).inverse()`.
1072
1073             let up = Vec3::new(0.0, -1.0, 0.0); // Y down -> Up is -Y
1074             let view = Mat4::look_at_rh(pos, anchor, up);
1075
1076             // Roll?
1077             // If we apply roll, it is around the local Z axis.
1078             // Camera looks down -Z.
1079             // Roll Z means rotate around Z.
1080             let rz = Animator::resolve(&ks.rz, t_frame, |v| v.to_radians(), 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1081             // Inverse of (Roll * View) ? No.
1082             // Camera Transform = (RotZ * View).inverse() ?
1083             // Or Transform = View.inverse() * RotZ?
1084             // Let's assume Transform = LookAtInv * RotZ.
1085             return view.inverse() * Mat4::from_rotation_z(rz);
1086        }
1087
1088        let mut anchor = Animator::resolve(&ks.a, t_frame, |v| Vec3::from(v.0), Vec3::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1089
1090        let mut pos = match &ks.p {
1091            data::PositionProperty::Unified(p) => {
1092                Animator::resolve(p, t_frame, |v| Vec3::from(v.0), Vec3::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate)
1093            }
1094            data::PositionProperty::Split { x, y, z } => {
1095                let px = Animator::resolve(x, t_frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1096                let py = Animator::resolve(y, t_frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1097                let pz = if let Some(z_prop) = z {
1098                    Animator::resolve(z_prop, t_frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate)
1099                } else { 0.0 };
1100                Vec3::new(px, py, pz)
1101            }
1102        };
1103
1104        let scale = Animator::resolve(&ks.s, t_frame, |v| Vec3::from(v.0) / 100.0, Vec3::ONE, evaluator.as_deref_mut(), self.asset.frame_rate);
1105
1106        let rz = Animator::resolve(&ks.rz, t_frame, |v| v.to_radians(), 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1107        let mut rx = 0.0;
1108        let mut ry = 0.0;
1109        if let Some(p) = &ks.rx { rx = Animator::resolve(p, t_frame, |v| v.to_radians(), 0.0, evaluator.as_deref_mut(), self.asset.frame_rate); }
1110        if let Some(p) = &ks.ry { ry = Animator::resolve(p, t_frame, |v| v.to_radians(), 0.0, evaluator.as_deref_mut(), self.asset.frame_rate); }
1111
1112        let mut orientation = if let Some(or) = &ks.or {
1113            Animator::resolve(or, t_frame, |v| Vec3::from(v.0), Vec3::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate)
1114        } else {
1115            Vec3::ZERO
1116        };
1117
1118        // Enforce 2D limits if not 3D layer
1119        if !is_3d {
1120             pos.z = 0.0;
1121             rx = 0.0;
1122             ry = 0.0;
1123             orientation = Vec3::ZERO; // Usually ignored in 2D
1124             // scale.z? leave as is (usually 1.0)
1125             anchor.z = 0.0;
1126        }
1127
1128        // Calculation: T * R * S * -A
1129        let mat_t = Mat4::from_translation(pos);
1130
1131        // Rotation: Orientation * X * Y * Z
1132        // Orientation (degrees)
1133        let mat_or = Mat4::from_euler(glam::EulerRot::YXZ, orientation.y.to_radians(), orientation.x.to_radians(), orientation.z.to_radians());
1134
1135        // Axis Rotations
1136        let mat_rx = Mat4::from_rotation_x(rx);
1137        let mat_ry = Mat4::from_rotation_y(ry);
1138        let mat_rz = Mat4::from_rotation_z(rz);
1139
1140        let mat_r = mat_or * mat_rx * mat_ry * mat_rz;
1141
1142        let mat_s = Mat4::from_scale(scale);
1143        let mat_a = Mat4::from_translation(-anchor);
1144
1145        mat_t * mat_r * mat_s * mat_a
1146    }
1147
1148    // Shapes logic must remain 2D (Mat3)
1149    fn get_shape_transform_2d(&self, ks: &data::Transform, frame: f32, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Mat3 {
1150         // Anchor (2D)
1151         let anchor_3d = Animator::resolve(&ks.a, frame, |v| Vec3::from(v.0), Vec3::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1152         let anchor = Vec2::new(anchor_3d.x, anchor_3d.y);
1153
1154         // Position (2D)
1155         let pos = match &ks.p {
1156             data::PositionProperty::Unified(p) => {
1157                 let v3 = Animator::resolve(p, frame, |v| Vec3::from(v.0), Vec3::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1158                 Vec2::new(v3.x, v3.y)
1159             }
1160             data::PositionProperty::Split { x, y, .. } => {
1161                 let px = Animator::resolve(x, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1162                 let py = Animator::resolve(y, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1163                 Vec2::new(px, py)
1164             }
1165         };
1166
1167         // Scale (2D)
1168         let s3 = Animator::resolve(&ks.s, frame, |v| Vec3::from(v.0) / 100.0, Vec3::ONE, evaluator.as_deref_mut(), self.asset.frame_rate);
1169         let scale = Vec2::new(s3.x, s3.y);
1170
1171         // Rotation (Z)
1172         let r = Animator::resolve(&ks.rz, frame, |v| v.to_radians(), 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1173
1174         let mat_t = Mat3::from_translation(pos);
1175         let mat_r = Mat3::from_rotation_z(r);
1176         let mat_s = Mat3::from_scale(scale);
1177         let mat_a = Mat3::from_translation(-anchor);
1178
1179         mat_t * mat_r * mat_s * mat_a
1180    }
1181
1182    fn process_shapes(&self, shapes: &'a [data::Shape], frame: f32, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Vec<RenderNode> {
1183        let mut processed_nodes = Vec::new();
1184        let mut active_geometries: Vec<PendingGeometry> = Vec::new();
1185
1186        let mut trim: Option<Trim> = None;
1187        for item in shapes {
1188            if let data::Shape::Trim(t) = item {
1189                let s = Animator::resolve(&t.s, frame, |v| *v / 100.0, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1190                let e = Animator::resolve(&t.e, frame, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1191                let o = Animator::resolve(&t.o, frame, |v| *v / 360.0, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1192                trim = Some(Trim { start: s, end: e, offset: o });
1193            }
1194        }
1195
1196        for item in shapes {
1197            match item {
1198                data::Shape::MergePaths(mp) => {
1199                    if !active_geometries.is_empty() {
1200                        let mode = match mp.mm {
1201                            1 => MergeMode::Merge, 2 => MergeMode::Add, 3 => MergeMode::Subtract,
1202                            4 => MergeMode::Intersect, 5 => MergeMode::Exclude, _ => MergeMode::Merge,
1203                        };
1204                        let merged = PendingGeometry {
1205                            kind: GeometryKind::Merge(active_geometries.clone(), mode),
1206                            transform: Mat3::IDENTITY,
1207                        };
1208                        active_geometries.clear();
1209                        active_geometries.push(merged);
1210                    }
1211                }
1212                data::Shape::Path(p) => {
1213                    let path = self.convert_path(p, frame, evaluator.as_deref_mut());
1214                    active_geometries.push(PendingGeometry {
1215                        kind: GeometryKind::Path(path),
1216                        transform: Mat3::IDENTITY,
1217                    });
1218                }
1219                data::Shape::Rect(r) => {
1220                    let size = Animator::resolve(&r.s, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1221                    let pos = Animator::resolve(&r.p, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1222                    let radius = Animator::resolve(&r.r, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1223                    active_geometries.push(PendingGeometry {
1224                        kind: GeometryKind::Rect { size, pos, radius },
1225                        transform: Mat3::IDENTITY,
1226                    });
1227                }
1228                data::Shape::Ellipse(e) => {
1229                    let size = Animator::resolve(&e.s, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1230                    let pos = Animator::resolve(&e.p, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1231                    active_geometries.push(PendingGeometry {
1232                        kind: GeometryKind::Ellipse { size, pos },
1233                        transform: Mat3::IDENTITY,
1234                    });
1235                }
1236                data::Shape::Polystar(sr) => {
1237                    let pos = match &sr.p {
1238                        data::PositionProperty::Unified(p) => Animator::resolve(p, 0.0, |v| Vec2::from_slice(&v.0[0..2]), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate),
1239                        data::PositionProperty::Split { x, y, .. } => {
1240                            let px = Animator::resolve(x, 0.0, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1241                            let py = Animator::resolve(y, 0.0, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1242                            Vec2::new(px, py)
1243                        }
1244                    };
1245                    let or = Animator::resolve(&sr.or, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1246                    let os = Animator::resolve(&sr.os, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1247                    let r = Animator::resolve(&sr.r, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1248                    let pt = Animator::resolve(&sr.pt, frame, |v| *v, 5.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1249                    let ir = if let Some(prop) = &sr.ir { Animator::resolve(prop, 0.0, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate) } else { 0.0 };
1250                    let is = if let Some(prop) = &sr.is { Animator::resolve(prop, 0.0, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate) } else { 0.0 };
1251
1252                    active_geometries.push(PendingGeometry {
1253                        kind: GeometryKind::Polystar(PolystarParams {
1254                            pos, outer_radius: or, inner_radius: ir, outer_roundness: os,
1255                            inner_roundness: is, rotation: r, points: pt, kind: sr.sy, corner_radius: 0.0,
1256                        }),
1257                        transform: Mat3::IDENTITY,
1258                    });
1259                }
1260                data::Shape::Transform(tr) => {
1261                    // Update current active geometries transform
1262                    let local = self.get_shape_transform_2d(&tr.t, frame, evaluator.as_deref_mut());
1263                    for geom in &mut active_geometries {
1264                        geom.transform = local * geom.transform;
1265                    }
1266                }
1267                data::Shape::RoundCorners(rd) => {
1268                    let r = Animator::resolve(&rd.r, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1269                    if r > 0.0 {
1270                        for geom in &mut active_geometries {
1271                            match &mut geom.kind {
1272                                GeometryKind::Rect { radius, .. } => *radius += r,
1273                                GeometryKind::Polystar(p) => p.corner_radius += r,
1274                                _ => {}
1275                            }
1276                        }
1277                    }
1278                }
1279                data::Shape::ZigZag(zz) => {
1280                    let ridges = Animator::resolve(&zz.r, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1281                    let size = Animator::resolve(&zz.s, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1282                    let pt = Animator::resolve(&zz.pt, frame, |v| *v, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1283                    let modifier = ZigZagModifier { ridges, size, smooth: pt > 1.5 };
1284                    self.apply_modifier_to_active(&mut active_geometries, &modifier);
1285                }
1286                data::Shape::PuckerBloat(pb) => {
1287                    let amount = Animator::resolve(&pb.a, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1288                    let modifier = PuckerBloatModifier { amount, center: Vec2::ZERO };
1289                    self.apply_modifier_to_active(&mut active_geometries, &modifier);
1290                }
1291                data::Shape::Twist(tw) => {
1292                    let angle = Animator::resolve(&tw.a, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1293                    let center = Animator::resolve(&tw.c, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1294                    let modifier = TwistModifier { angle, center };
1295                    self.apply_modifier_to_active(&mut active_geometries, &modifier);
1296                }
1297                data::Shape::OffsetPath(op) => {
1298                    let amount = Animator::resolve(&op.a, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1299                    let miter_limit = op.ml.unwrap_or(4.0);
1300                    let line_join = op.lj;
1301                    let modifier = OffsetPathModifier { amount, miter_limit, line_join };
1302                    self.apply_modifier_to_active(&mut active_geometries, &modifier);
1303                }
1304                data::Shape::WigglePath(wg) => {
1305                    let speed = Animator::resolve(&wg.s, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1306                    let size = Animator::resolve(&wg.w, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1307                    let correlation = Animator::resolve(&wg.r, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1308                    let seed_prop = Animator::resolve(&wg.sh, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1309                    let mut modifier = WiggleModifier { seed: seed_prop, time: frame / 60.0, speed: speed / self.asset.frame_rate, amount: size, correlation };
1310                    modifier.time = frame;
1311                    modifier.speed = speed / self.asset.frame_rate;
1312                    self.apply_modifier_to_active(&mut active_geometries, &modifier);
1313                }
1314                data::Shape::Repeater(rp) => {
1315                    let copies = Animator::resolve(&rp.c, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1316                    let offset = Animator::resolve(&rp.o, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1317                    let t_anchor_3d = Animator::resolve(&rp.tr.t.a, frame, |v| Vec3::from(v.0), Vec3::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1318                    let t_anchor = Vec2::new(t_anchor_3d.x, t_anchor_3d.y);
1319
1320                    let t_pos = match &rp.tr.t.p {
1321                        data::PositionProperty::Unified(p) => Animator::resolve(p, 0.0, |v| Vec2::from_slice(&v.0[0..2]), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate),
1322                        data::PositionProperty::Split { x, y, .. } => {
1323                            let px = Animator::resolve(x, 0.0, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1324                            let py = Animator::resolve(y, 0.0, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1325                            Vec2::new(px, py)
1326                        }
1327                    };
1328                    let t_scale_3d = Animator::resolve(&rp.tr.t.s, 0.0, |v| Vec3::from(v.0) / 100.0, Vec3::ONE, evaluator.as_deref_mut(), self.asset.frame_rate);
1329                    let t_scale = Vec2::new(t_scale_3d.x, t_scale_3d.y);
1330
1331                    let t_rot = Animator::resolve(&rp.tr.t.rz, frame, |v| v.to_radians(), 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1332
1333                    let start_opacity = Animator::resolve(&rp.tr.so, frame, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1334                    let end_opacity = Animator::resolve(&rp.tr.eo, frame, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1335
1336                    self.apply_repeater(copies, offset, t_anchor, t_pos, t_scale, t_rot, start_opacity, end_opacity, &mut active_geometries, &mut processed_nodes);
1337                }
1338                data::Shape::Fill(f) => {
1339                    let color = Animator::resolve(&f.c, frame, |v| Vec4::from_slice(v), Vec4::ONE, evaluator.as_deref_mut(), self.asset.frame_rate);
1340                    let opacity = Animator::resolve(&f.o, frame, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1341                    for geom in &active_geometries {
1342                        let path = self.convert_geometry(geom);
1343                        processed_nodes.push(RenderNode {
1344                            transform: Mat4::IDENTITY,
1345                            alpha: 1.0, blend_mode: BlendMode::Normal,
1346                            content: NodeContent::Shape(renderer::Shape {
1347                                geometry: path,
1348                                fill: Some(Fill { paint: Paint::Solid(color), opacity, rule: FillRule::NonZero }),
1349                                stroke: None, trim: trim.clone(),
1350                            }),
1351                            masks: vec![], styles: vec![], matte: None, effects: vec![], is_adjustment_layer: false,
1352                        });
1353                    }
1354                }
1355                data::Shape::GradientFill(gf) => {
1356                    let start = Animator::resolve(&gf.s, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1357                    let end = Animator::resolve(&gf.e, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1358                    let opacity = Animator::resolve(&gf.o, frame, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1359                    let raw_stops = Animator::resolve(&gf.g.k, frame, |v| v.clone(), Vec::new(), evaluator.as_deref_mut(), self.asset.frame_rate);
1360                    let stops = parse_gradient_stops(&raw_stops, gf.g.p as usize);
1361                    let kind = if gf.t == 1 { GradientKind::Linear } else { GradientKind::Radial };
1362                    for geom in &active_geometries {
1363                        let path = self.convert_geometry(geom);
1364                        processed_nodes.push(RenderNode {
1365                            transform: Mat4::IDENTITY,
1366                            alpha: 1.0, blend_mode: BlendMode::Normal,
1367                            content: NodeContent::Shape(renderer::Shape {
1368                                geometry: path,
1369                                fill: Some(Fill { paint: Paint::Gradient(Gradient { kind, stops: stops.clone(), start, end }), opacity, rule: FillRule::NonZero }),
1370                                stroke: None, trim: trim.clone(),
1371                            }),
1372                            masks: vec![], styles: vec![], matte: None, effects: vec![], is_adjustment_layer: false,
1373                        });
1374                    }
1375                }
1376                data::Shape::GradientStroke(gs) => {
1377                    let start = Animator::resolve(&gs.s, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1378                    let end = Animator::resolve(&gs.e, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1379                    let width = Animator::resolve(&gs.w, frame, |v| *v, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1380                    let opacity = Animator::resolve(&gs.o, frame, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1381                    let raw_stops = Animator::resolve(&gs.g.k, frame, |v| v.clone(), Vec::new(), evaluator.as_deref_mut(), self.asset.frame_rate);
1382                    let stops = parse_gradient_stops(&raw_stops, gs.g.p as usize);
1383                    let kind = if gs.t == 1 { GradientKind::Linear } else { GradientKind::Radial };
1384                    let dash = self.resolve_dash(&gs.d, frame, evaluator.as_deref_mut());
1385                    for geom in &active_geometries {
1386                        let path = self.convert_geometry(geom);
1387                        processed_nodes.push(RenderNode {
1388                            transform: Mat4::IDENTITY,
1389                            alpha: 1.0, blend_mode: BlendMode::Normal,
1390                            content: NodeContent::Shape(renderer::Shape {
1391                                geometry: path,
1392                                fill: None,
1393                                stroke: Some(Stroke { paint: Paint::Gradient(Gradient { kind, stops: stops.clone(), start, end }), width, opacity, cap: match gs.lc { 1 => LineCap::Butt, 3 => LineCap::Square, _ => LineCap::Round }, join: match gs.lj { 1 => LineJoin::Miter, 3 => LineJoin::Bevel, _ => LineJoin::Round }, miter_limit: gs.ml, dash: dash.clone() }),
1394                                trim: trim.clone(),
1395                            }),
1396                            masks: vec![], styles: vec![], matte: None, effects: vec![], is_adjustment_layer: false,
1397                        });
1398                    }
1399                }
1400                data::Shape::Stroke(s) => {
1401                    let color = Animator::resolve(&s.c, frame, |v| Vec4::from_slice(v), Vec4::ONE, evaluator.as_deref_mut(), self.asset.frame_rate);
1402                    let width = Animator::resolve(&s.w, frame, |v| *v, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1403                    let opacity = Animator::resolve(&s.o, frame, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1404                    let dash = self.resolve_dash(&s.d, frame, evaluator.as_deref_mut());
1405                    for geom in &active_geometries {
1406                        let path = self.convert_geometry(geom);
1407                        processed_nodes.push(RenderNode {
1408                            transform: Mat4::IDENTITY,
1409                            alpha: 1.0, blend_mode: BlendMode::Normal,
1410                            content: NodeContent::Shape(renderer::Shape {
1411                                geometry: path,
1412                                fill: None,
1413                                stroke: Some(Stroke { paint: Paint::Solid(color), width, opacity, cap: match s.lc { 1 => LineCap::Butt, 3 => LineCap::Square, _ => LineCap::Round }, join: match s.lj { 1 => LineJoin::Miter, 3 => LineJoin::Bevel, _ => LineJoin::Round }, miter_limit: s.ml, dash: dash.clone() }),
1414                                trim: trim.clone(),
1415                            }),
1416                            masks: vec![], styles: vec![], matte: None, effects: vec![], is_adjustment_layer: false,
1417                        });
1418                    }
1419                }
1420                data::Shape::Group(g) => {
1421                    let group_nodes = self.process_shapes(&g.it, frame, evaluator.as_deref_mut());
1422                    processed_nodes.push(RenderNode {
1423                        transform: Mat4::IDENTITY,
1424                        alpha: 1.0, blend_mode: BlendMode::Normal,
1425                        content: NodeContent::Group(group_nodes),
1426                        masks: vec![], styles: vec![], matte: None, effects: vec![], is_adjustment_layer: false,
1427                    });
1428                }
1429                _ => {}
1430            }
1431        }
1432        processed_nodes
1433    }
1434
1435    fn resolve_dash(&self, props: &[data::DashProperty], frame: f32, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Option<DashPattern> {
1436        if props.is_empty() { return None; }
1437        let mut array = Vec::new();
1438        let mut offset = 0.0;
1439        for prop in props {
1440            match prop.n.as_deref() {
1441                Some("o") => offset = Animator::resolve(&prop.v, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate),
1442                Some("d") | Some("v") | Some("g") => array.push(Animator::resolve(&prop.v, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate)),
1443                _ => {}
1444            }
1445        }
1446        if !array.is_empty() {
1447            if array.len() % 2 != 0 { let clone = array.clone(); array.extend(clone); }
1448            let total: f32 = array.iter().sum();
1449            if total > 0.0 { offset = (offset % total + total) % total; } else { offset = 0.0; }
1450            Some(DashPattern { array, offset })
1451        } else {
1452            None
1453        }
1454    }
1455
1456    fn apply_repeater(
1457        &self,
1458        copies: f32,
1459        _offset: f32,
1460        anchor: Vec2,
1461        pos: Vec2,
1462        scale: Vec2,
1463        rot: f32,
1464        start_op: f32,
1465        end_op: f32,
1466        geoms: &mut Vec<PendingGeometry>,
1467        nodes: &mut Vec<RenderNode>,
1468    ) {
1469        let num_copies = copies.round() as usize;
1470        if num_copies <= 1 { return; }
1471
1472        let original_geoms = geoms.clone();
1473        let original_nodes = nodes.clone();
1474
1475        let mat_t = Mat3::from_translation(pos);
1476        let mat_r = Mat3::from_rotation_z(rot); // Radians
1477        let mat_s = Mat3::from_scale(scale);
1478        let mat_a = Mat3::from_translation(-anchor);
1479        let mat_pre_a = Mat3::from_translation(anchor);
1480
1481        let pivot_transform = mat_pre_a * mat_r * mat_s * mat_a;
1482        let step_transform = mat_t * pivot_transform;
1483
1484        // RenderNode uses Mat4, but repeater internal logic here is mixed?
1485        // nodes have Mat4. step_transform is Mat3.
1486        // We need Mat4 step transform for nodes.
1487        let mat_t4 = Mat4::from_translation(Vec3::new(pos.x, pos.y, 0.0));
1488        let mat_r4 = Mat4::from_rotation_z(rot);
1489        let mat_s4 = Mat4::from_scale(Vec3::new(scale.x, scale.y, 1.0));
1490        let mat_a4 = Mat4::from_translation(Vec3::new(-anchor.x, -anchor.y, 0.0));
1491        let mat_pre_a4 = Mat4::from_translation(Vec3::new(anchor.x, anchor.y, 0.0));
1492        let step_transform4 = mat_t4 * mat_pre_a4 * mat_r4 * mat_s4 * mat_a4;
1493
1494        geoms.clear();
1495        nodes.clear();
1496
1497        for i in 0..num_copies {
1498            let t = if num_copies > 1 { i as f32 / (num_copies as f32 - 1.0) } else { 0.0 };
1499            let op = start_op + (end_op - start_op) * t;
1500
1501            let mut copy_transform = Mat3::IDENTITY;
1502            let mut copy_transform4 = Mat4::IDENTITY;
1503            for _ in 0..i {
1504                copy_transform = copy_transform * step_transform;
1505                copy_transform4 = copy_transform4 * step_transform4;
1506            }
1507
1508            for geom in &original_geoms {
1509                let mut g = geom.clone();
1510                g.transform = copy_transform * g.transform;
1511                geoms.push(g);
1512            }
1513
1514            for node in &original_nodes {
1515                let mut n = node.clone();
1516                n.transform = copy_transform4 * n.transform;
1517                n.alpha *= op;
1518                nodes.push(n);
1519            }
1520        }
1521    }
1522
1523    fn apply_modifier_to_active(
1524        &self,
1525        active: &mut Vec<PendingGeometry>,
1526        modifier: &impl GeometryModifier,
1527    ) {
1528        for geom in active.iter_mut() {
1529            let mut path = geom.to_path(self);
1530            modifier.modify(&mut path);
1531            geom.transform = Mat3::IDENTITY;
1532            geom.kind = GeometryKind::Path(path);
1533        }
1534    }
1535
1536    fn convert_geometry(&self, geom: &PendingGeometry) -> ShapeGeometry {
1537        geom.to_shape_geometry(self)
1538    }
1539
1540    fn generate_polystar_path(&self, params: &PolystarParams) -> BezPath {
1541        let mut path = BezPath::new();
1542        let num_points = params.points.round();
1543        if num_points < 3.0 { return path; }
1544
1545        let is_star = params.kind == 1;
1546        let has_roundness = params.outer_roundness.abs() > 0.01 || (is_star && params.inner_roundness.abs() > 0.01);
1547        let total_points = if is_star { num_points * 2.0 } else { num_points } as usize;
1548        let current_angle = (params.rotation - 90.0).to_radians();
1549        let angle_step = 2.0 * PI / total_points as f64;
1550
1551        if has_roundness {
1552            let mut elements = Vec::with_capacity(total_points);
1553            for i in 0..total_points {
1554                let (r, roundness) = if is_star {
1555                    if i % 2 == 0 { (params.outer_radius, params.outer_roundness) } else { (params.inner_radius, params.inner_roundness) }
1556                } else {
1557                    (params.outer_radius, params.outer_roundness)
1558                };
1559
1560                let angle = current_angle as f64 + angle_step * i as f64;
1561                let cos_a = angle.cos();
1562                let sin_a = angle.sin();
1563                let x = params.pos.x as f64 + r as f64 * cos_a;
1564                let y = params.pos.y as f64 + r as f64 * sin_a;
1565                let vertex = Point::new(x, y);
1566
1567                let tx = -sin_a; let ty = cos_a;
1568                let tangent = kurbo::Vec2::new(tx, ty);
1569                let cp_d = r as f64 * angle_step * roundness as f64 * 0.01;
1570                let in_cp = vertex - tangent * cp_d;
1571                let out_cp = vertex + tangent * cp_d;
1572                elements.push((vertex, in_cp, out_cp));
1573            }
1574            if elements.is_empty() { return path; }
1575            path.move_to(elements[0].0);
1576            let len = elements.len();
1577            for i in 0..len {
1578                let curr_idx = i;
1579                let next_idx = (i + 1) % len;
1580                let curr_out_cp = elements[curr_idx].2;
1581                let next_in_cp = elements[next_idx].1;
1582                let next_vertex = elements[next_idx].0;
1583                path.curve_to(curr_out_cp, next_in_cp, next_vertex);
1584            }
1585            path.close_path();
1586            return path;
1587        }
1588
1589        let mut vertices = Vec::with_capacity(total_points);
1590        for i in 0..total_points {
1591            let r = if is_star { if i % 2 == 0 { params.outer_radius } else { params.inner_radius } } else { params.outer_radius };
1592            let angle = current_angle as f64 + angle_step * i as f64;
1593            let x = params.pos.x as f64 + r as f64 * angle.cos();
1594            let y = params.pos.y as f64 + r as f64 * angle.sin();
1595            vertices.push(Point::new(x, y));
1596        }
1597
1598        let radius = params.corner_radius as f64;
1599        if radius <= 0.1 {
1600            if !vertices.is_empty() {
1601                path.move_to(vertices[0]);
1602                for v in vertices.iter().skip(1) { path.line_to(*v); }
1603                path.close_path();
1604            }
1605            return path;
1606        }
1607
1608        let len = vertices.len();
1609        for i in 0..len {
1610            let prev = vertices[(i + len - 1) % len];
1611            let curr = vertices[i];
1612            let next = vertices[(i + 1) % len];
1613            let v1 = prev - curr;
1614            let v2 = next - curr;
1615            let len1 = v1.hypot();
1616            let len2 = v2.hypot();
1617
1618            if len1 < 0.001 || len2 < 0.001 {
1619                if i == 0 { path.move_to(curr); } else { path.line_to(curr); }
1620                continue;
1621            }
1622
1623            let u1 = v1 * (1.0 / len1);
1624            let u2 = v2 * (1.0 / len2);
1625            let dot = (u1.x * u2.x + u1.y * u2.y).clamp(-1.0, 1.0);
1626            let angle = dot.acos();
1627            let dist = if angle.abs() < 0.001 { 0.0 } else { radius / (angle / 2.0).tan() };
1628            let max_d = (len1.min(len2)) * 0.5;
1629            let d = dist.min(max_d);
1630            let p_start = curr + u1 * d;
1631            let p_end = curr + u2 * d;
1632
1633            if i == 0 { path.move_to(p_start); } else { path.line_to(p_start); }
1634            path.quad_to(curr, p_end);
1635        }
1636        path.close_path();
1637        path
1638    }
1639
1640    fn convert_path(&self, p: &data::PathShape, frame: f32, #[cfg(feature = "expressions")] evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> BezPath {
1641        let path_data = Animator::resolve(&p.ks, frame, |v| v.clone(), data::BezierPath::default(), evaluator, self.asset.frame_rate);
1642        self.convert_bezier_path(&path_data)
1643    }
1644
1645    fn convert_bezier_path(&self, path_data: &data::BezierPath) -> BezPath {
1646        let mut bp = BezPath::new();
1647        if path_data.v.is_empty() { return bp; }
1648        let start = path_data.v[0];
1649        bp.move_to(Point::new(start[0] as f64, start[1] as f64));
1650        for i in 0..path_data.v.len() {
1651            let next_idx = (i + 1) % path_data.v.len();
1652            if next_idx == 0 && !path_data.c { break; }
1653            let p0 = path_data.v[i];
1654            let p1 = path_data.v[next_idx];
1655            let o = if i < path_data.o.len() { path_data.o[i] } else { [0.0, 0.0] };
1656            let in_ = if next_idx < path_data.i.len() { path_data.i[next_idx] } else { [0.0, 0.0] };
1657            let cp1 = [p0[0] + o[0], p0[1] + o[1]];
1658            let cp2 = [p1[0] + in_[0], p1[1] + in_[1]];
1659            bp.curve_to(Point::new(cp1[0] as f64, cp1[1] as f64), Point::new(cp2[0] as f64, cp2[1] as f64), Point::new(p1[0] as f64, p1[1] as f64));
1660        }
1661        if path_data.c { bp.close_path(); }
1662        bp
1663    }
1664
1665    fn process_masks(&self, masks_props: &[data::MaskProperties], frame: f32, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Vec<Mask> {
1666        let mut masks = Vec::new();
1667        for m in masks_props {
1668            let mode = match m.mode.as_deref() {
1669                Some("n") => MaskMode::None, Some("a") => MaskMode::Add, Some("s") => MaskMode::Subtract,
1670                Some("i") => MaskMode::Intersect, Some("l") => MaskMode::Lighten, Some("d") => MaskMode::Darken,
1671                Some("f") => MaskMode::Difference, _ => continue,
1672            };
1673            let path_data = Animator::resolve(&m.pt, frame, |v| v.clone(), data::BezierPath::default(), evaluator.as_deref_mut(), self.asset.frame_rate);
1674            let geometry = self.convert_bezier_path(&path_data);
1675            let opacity = Animator::resolve(&m.o, frame, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1676            let expansion = Animator::resolve(&m.x, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1677            let inverted = m.inv;
1678            masks.push(Mask { mode, geometry, opacity, expansion, inverted });
1679        }
1680        masks
1681    }
1682}
1683
1684// Helpers
1685struct ColorStop { t: f32, r: f32, g: f32, b: f32 }
1686struct AlphaStop { t: f32, a: f32 }
1687
1688fn parse_gradient_stops(raw: &[f32], color_count: usize) -> Vec<GradientStop> {
1689    let mut stops = Vec::new();
1690    if raw.is_empty() { return stops; }
1691    let mut color_stops = Vec::new();
1692    let mut alpha_stops = Vec::new();
1693    let color_data_len = color_count * 4;
1694    for chunk in raw.iter().take(color_data_len).collect::<Vec<_>>().chunks(4) {
1695        if chunk.len() == 4 { color_stops.push(ColorStop { t: *chunk[0], r: *chunk[1], g: *chunk[2], b: *chunk[3] }); }
1696    }
1697    if raw.len() > color_data_len {
1698        for chunk in raw[color_data_len..].chunks(2) {
1699            if chunk.len() == 2 { alpha_stops.push(AlphaStop { t: chunk[0], a: chunk[1] }); }
1700        }
1701    }
1702    if alpha_stops.is_empty() {
1703        for c in color_stops { stops.push(GradientStop { offset: c.t, color: Vec4::new(c.r, c.g, c.b, 1.0) }); }
1704        return stops;
1705    }
1706    let mut unique_t: Vec<f32> = Vec::new();
1707    for c in &color_stops { unique_t.push(c.t); }
1708    for a in &alpha_stops { unique_t.push(a.t); }
1709    unique_t.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1710    unique_t.dedup();
1711    for t in unique_t {
1712        let (r, g, b) = interpolate_color(&color_stops, t);
1713        let a = interpolate_alpha(&alpha_stops, t);
1714        stops.push(GradientStop { offset: t, color: Vec4::new(r, g, b, a) });
1715    }
1716    stops
1717}
1718
1719fn interpolate_color(stops: &[ColorStop], t: f32) -> (f32, f32, f32) {
1720    if stops.is_empty() { return (1.0, 1.0, 1.0); }
1721    if t <= stops[0].t { return (stops[0].r, stops[0].g, stops[0].b); }
1722    if t >= stops.last().unwrap().t { let last = stops.last().unwrap(); return (last.r, last.g, last.b); }
1723    for i in 0..stops.len() - 1 {
1724        let s1 = &stops[i];
1725        let s2 = &stops[i + 1];
1726        if t >= s1.t && t <= s2.t {
1727            let range = s2.t - s1.t;
1728            let ratio = if range == 0.0 { 0.0 } else { (t - s1.t) / range };
1729            return (s1.r + (s2.r - s1.r) * ratio, s1.g + (s2.g - s1.g) * ratio, s1.b + (s2.b - s1.b) * ratio);
1730        }
1731    }
1732    (1.0, 1.0, 1.0)
1733}
1734
1735fn interpolate_alpha(stops: &[AlphaStop], t: f32) -> f32 {
1736    if stops.is_empty() { return 1.0; }
1737    if t <= stops[0].t { return stops[0].a; }
1738    if t >= stops.last().unwrap().t { return stops.last().unwrap().a; }
1739    for i in 0..stops.len() - 1 {
1740        let s1 = &stops[i];
1741        let s2 = &stops[i + 1];
1742        if t >= s1.t && t <= s2.t {
1743            let range = s2.t - s1.t;
1744            let ratio = if range == 0.0 { 0.0 } else { (t - s1.t) / range };
1745            return s1.a + (s2.a - s1.a) * ratio;
1746        }
1747    }
1748    1.0
1749}
1750
1751#[cfg(test)]
1752mod tests {
1753    use super::*;
1754    use lottie_data::model as data;
1755
1756    #[test]
1757    fn test_camera_transform() {
1758        let camera_layer = data::Layer {
1759            ty: 13,
1760            ind: Some(1),
1761            parent: None,
1762            nm: Some("Camera".to_string()),
1763            ip: 0.0,
1764            op: 60.0,
1765            st: 0.0,
1766            ks: data::Transform {
1767                p: data::PositionProperty::Unified(data::Property {
1768                    k: data::Value::Static(data::Vec3DefaultZero([0.0, 0.0, -500.0])),
1769                    ..Default::default()
1770                }),
1771                a: data::Property {
1772                    k: data::Value::Static(data::Vec3DefaultZero([0.0, 0.0, 0.0])),
1773                    ..Default::default()
1774                },
1775                ..Default::default()
1776            },
1777            pe: Some(data::Property {
1778                k: data::Value::Static(1000.0),
1779                ..Default::default()
1780            }),
1781            ddd: Some(1),
1782            ao: None, tm: None, masks_properties: None, tt: None, ef: None, sy: None,
1783            ref_id: None, w: None, h: None, color: None, sw: None, sh: None,
1784            shapes: None, t: None,
1785        };
1786
1787        let model = LottieJson {
1788            v: None, ip: 0.0, op: 60.0, fr: 60.0, w: 1000, h: 1000,
1789            layers: vec![camera_layer],
1790            assets: vec![],
1791        };
1792
1793        let mut player = LottiePlayer::new();
1794        player.load_json(model);
1795        let tree = player.render_tree();
1796
1797        let vm = tree.view_matrix;
1798        // World point (0,0,0) -> View Space
1799        let p_world = Vec4::new(0.0, 0.0, 0.0, 1.0);
1800        let p_view = vm * p_world;
1801
1802        // Camera at -500. Looking at 0.
1803        // In View Space (RH, -Z forward), the object at 0 (distance 500 in front)
1804        // should be at Z = -500.
1805        assert!((p_view.z - (-500.0)).abs() < 1.0, "Expected Z=-500, got {}", p_view.z);
1806    }
1807}