diff --git a/dotlottie-ffi/emscripten_bindings.cpp b/dotlottie-ffi/emscripten_bindings.cpp index 67eac3db..44a44d6c 100644 --- a/dotlottie-ffi/emscripten_bindings.cpp +++ b/dotlottie-ffi/emscripten_bindings.cpp @@ -18,29 +18,12 @@ bool load_dotlottie_data(DotLottiePlayer &player, std::string data, uint32_t wid return player.load_dotlottie_data(data_vector, width, height); } -val markers(DotLottiePlayer &player) -{ - auto markers = player.markers(); - - val result = val::array(); - - for (auto &marker : markers) - { - val marker_obj = val::object(); - marker_obj.set("name", marker.name); - marker_obj.set("time", marker.time); - marker_obj.set("duration", marker.duration); - result.call("push", marker_obj); - } - - return result; -} - EMSCRIPTEN_BINDINGS(DotLottiePlayer) { // Register std::vector as VectorFloat for the Config::segments field register_vector("VectorFloat"); + register_vector("VectorMarker"); // register_vector("VectorString"); // register_vector("VectorManifestTheme"); // register_vector("VectorManifestAnimation"); @@ -51,6 +34,11 @@ EMSCRIPTEN_BINDINGS(DotLottiePlayer) .value("Bounce", Mode::BOUNCE) .value("ReverseBounce", Mode::REVERSE_BOUNCE); + value_object("Marker") + .field("name", &Marker::name) + .field("time", &Marker::time) + .field("duration", &Marker::duration); + value_object("Config") .field("autoplay", &Config::autoplay) .field("loopAnimation", &Config::loop_animation) diff --git a/dotlottie-rs/src/dotlottie_player.rs b/dotlottie-rs/src/dotlottie_player.rs index afa4bd97..452df2f8 100644 --- a/dotlottie-rs/src/dotlottie_player.rs +++ b/dotlottie-rs/src/dotlottie_player.rs @@ -111,40 +111,31 @@ impl DotLottieRuntime { } fn start_frame(&self) -> f32 { - let start_frame: f32 = { - if !self.config.marker.is_empty() { - if let Some((time, _)) = self.markers.get(&self.config.marker) { - return *time; - } - } - - if self.config.segments.len() == 2 { - return self.config.segments[0]; + if !self.config.marker.is_empty() { + if let Some((time, _)) = self.markers.get(&self.config.marker) { + return (*time).max(0.0); } + } - 0.0 - }; + if self.config.segments.len() == 2 { + return self.config.segments[0].max(0.0); + } - start_frame.clamp(0.0, self.total_frames()) + 0.0 } fn end_frame(&self) -> f32 { - let end_frame: f32 = - { - if !self.config.marker.is_empty() { - if let Some((time, duration)) = self.markers.get(&self.config.marker) { - return time + duration; - } - } - - if self.config.segments.len() == 2 { - return self.config.segments[1]; - } + if !self.config.marker.is_empty() { + if let Some((time, duration)) = self.markers.get(&self.config.marker) { + return (time + duration).min(self.total_frames()); + } + } - self.total_frames() - }; + if self.config.segments.len() == 2 { + return self.config.segments[1].min(self.total_frames()); + } - end_frame.clamp(0.0, self.total_frames()) + self.total_frames() } pub fn is_loaded(&self) -> bool { @@ -595,7 +586,7 @@ impl DotLottieRuntime { pub fn load_animation_data(&mut self, animation_data: &str, width: u32, height: u32) -> bool { self.dotlottie_manager = DotLottieManager::new(None).unwrap(); - self.markers = extract_markers(animation_data).unwrap_or_default(); + self.markers = extract_markers(animation_data); self.load_animation_common( |renderer, w, h| renderer.load_data(animation_data, w, h, false), @@ -621,7 +612,7 @@ impl DotLottieRuntime { match first_animation { Ok(animation_data) => { - self.markers = extract_markers(animation_data.as_str()).unwrap_or_default(); + self.markers = extract_markers(animation_data.as_str()); // For the moment we're ignoring manifest values diff --git a/dotlottie-rs/src/markers.rs b/dotlottie-rs/src/markers.rs index 0ccf44fa..206b1d56 100644 --- a/dotlottie-rs/src/markers.rs +++ b/dotlottie-rs/src/markers.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; -use serde_json::Result; #[derive(Serialize, Deserialize)] pub struct Marker { @@ -20,20 +19,174 @@ struct Lottie { pub type MarkersMap = HashMap; -pub fn extract_markers(json_data: &str) -> Result { - let lottie: Lottie = serde_json::from_str(json_data)?; - +pub fn extract_markers(json_data: &str) -> MarkersMap { let mut markers_map = HashMap::new(); - for marker in lottie.markers { - let name = marker.name.trim(); + match serde_json::from_str::(json_data) { + Ok(lottie) => { + for marker in lottie.markers { + let name = marker.name.trim(); + + if name.is_empty() || marker.duration < 0.0 || marker.time < 0.0 { + continue; + } + + markers_map.insert(name.to_string(), (marker.time, marker.duration)); + } - if name.is_empty() { - continue; + markers_map } + Err(_) => markers_map, + } +} - markers_map.insert(name.to_string(), (marker.time, marker.duration)); +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test_extract_markers_normal() { + let json_data = + json!({ + "markers": [ + {"cm": "Marker1", "dr": 1.5, "tm": 0.5}, + {"cm": "Marker2", "dr": 2.5, "tm": 1.5} + ] + }) + .to_string(); + + let markers = extract_markers(&json_data); + + assert_eq!(markers.len(), 2); + assert!(markers.contains_key("Marker1")); + assert_eq!(markers["Marker1"], (0.5, 1.5)); + assert!(markers.contains_key("Marker2")); + assert_eq!(markers["Marker2"], (1.5, 2.5)); } - Ok(markers_map) + #[test] + fn test_extract_markers_empty_name() { + let json_data = + json!({ + "markers": [ + {"cm": "", "dr": 1.5, "tm": 0.5}, + {"cm": "Marker2", "dr": 2.5, "tm": 1.5} + ] + }) + .to_string(); + + let markers = extract_markers(&json_data); + + assert_eq!(markers.len(), 1); + assert!(markers.contains_key("Marker2")); + } + + #[test] + fn test_extract_markers_invalid_json() { + let json_data = "This is not a valid JSON".to_string(); + assert!(extract_markers(&json_data).is_empty()); + } + + #[test] + fn test_extract_markers_wrong_structure() { + let json_data = json!({"unexpected_field": "unexpected_value"}).to_string(); + assert!(extract_markers(&json_data).is_empty()); + } + + #[test] + fn test_extract_markers_empty() { + let json_data = json!({}).to_string(); + assert!(extract_markers(&json_data).is_empty()); + } + + #[test] + fn test_extract_markers_duplicate_names() { + let json_data = + json!({ + "markers": [ + {"cm": "Marker1", "dr": 1.5, "tm": 0.5}, + {"cm": "Marker1", "dr": 2.5, "tm": 1.5} + ] + }) + .to_string(); + + let markers = extract_markers(&json_data); + + assert_eq!(markers.len(), 1); + assert!(markers.contains_key("Marker1")); + assert_eq!(markers["Marker1"], (1.5, 2.5)); + } + + #[test] + fn test_extract_markers_negative_duration() { + let json_data = json!({ + "markers": [ + {"cm": "Marker1", "dr": -1.5, "tm": 0.5}, + {"cm": "Marker2", "dr": 2.5, "tm": 1.5} + ] + }) + .to_string(); + + let markers = extract_markers(&json_data); + + assert_eq!(markers.len(), 1); + assert!(markers.contains_key("Marker2")); + assert_eq!(markers["Marker2"], (1.5, 2.5)); + } + + #[test] + fn test_extract_markers_negative_time() { + let json_data = json!({ + "markers": [ + {"cm": "Marker1", "dr": 1.5, "tm": -0.5}, + {"cm": "Marker2", "dr": 2.5, "tm": 1.5} + ] + }) + .to_string(); + + let markers = extract_markers(&json_data); + + assert_eq!(markers.len(), 1); + assert!(markers.contains_key("Marker2")); + assert_eq!(markers["Marker2"], (1.5, 2.5)); + } + + #[test] + fn test_extract_markers_large_numbers() { + let json_data = json!({ + "markers": [ + {"cm": "Marker1", "dr": 1.5, "tm": 1e10}, + {"cm": "Marker2", "dr": 2.5, "tm": 1.5} + ] + }) + .to_string(); + + let markers = extract_markers(&json_data); + + assert_eq!(markers.len(), 2); + assert!(markers.contains_key("Marker1")); + assert_eq!(markers["Marker1"], (1e10, 1.5)); + assert!(markers.contains_key("Marker2")); + assert_eq!(markers["Marker2"], (1.5, 2.5)); + } + + #[test] + fn test_trim_marker_name() { + let json_data = json!({ + "markers": [ + {"cm": " Marker1 ", "dr": 1.5, "tm": 0.5}, + {"cm": "Marker2", "dr": 2.5, "tm": 1.5} + ] + }) + .to_string(); + + let markers = extract_markers(&json_data); + + assert_eq!(markers.len(), 2); + assert!(markers.contains_key("Marker1")); + assert_eq!(markers["Marker1"], (0.5, 1.5)); + assert!(markers.contains_key("Marker2")); + assert_eq!(markers["Marker2"], (1.5, 2.5)); + } }