use alloc::ffi::CString;
use alloc::string::String;
use alloc::vec::Vec;
use crate::animation::Animation;
use crate::error::{Error, Result};
use thorvg_sys as sys;
#[derive(Debug, Clone)]
pub struct Marker {
pub name: String,
pub begin: f32,
pub end: f32,
}
pub struct AudioInfo<'a> {
raw: &'a sys::Tvg_Audio_Info,
}
impl<'a> AudioInfo<'a> {
pub fn source(&self) -> Option<&'a str> {
if self.raw.embedded || self.raw.src.is_null() {
return None;
}
unsafe { core::ffi::CStr::from_ptr(self.raw.src) }
.to_str()
.ok()
}
pub fn embedded_data(&self) -> Option<&'a [u8]> {
if !self.raw.embedded || self.raw.src.is_null() {
return None;
}
Some(unsafe {
core::slice::from_raw_parts(self.raw.src.cast::<u8>(), self.raw.size as usize)
})
}
pub fn mime_type(&self) -> Option<&'a str> {
if self.raw.mimeType.is_null() {
return None;
}
unsafe { core::ffi::CStr::from_ptr(self.raw.mimeType) }
.to_str()
.ok()
}
pub fn size(&self) -> u32 {
self.raw.size
}
pub fn offset(&self) -> f32 {
self.raw.offset
}
pub fn volume(&self) -> f32 {
self.raw.volume
}
pub fn is_active(&self) -> bool {
self.raw.active
}
pub fn is_embedded(&self) -> bool {
self.raw.embedded
}
}
impl core::fmt::Debug for AudioInfo<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("AudioInfo")
.field("active", &self.is_active())
.field("embedded", &self.is_embedded())
.field("offset", &self.offset())
.field("volume", &self.volume())
.finish_non_exhaustive()
}
}
pub struct LottieAnimation<'eng> {
inner: Animation<'eng>,
audio_resolver: Option<ErasedAudioResolver>,
}
impl LottieAnimation<'_> {
pub(crate) fn new() -> Result<Self> {
let raw = unsafe { sys::tvg_lottie_animation_new() };
if raw.is_null() {
return Err(Error::FailedAllocation);
}
Ok(Self {
inner: unsafe { Animation::from_raw(raw) },
audio_resolver: None,
})
}
pub fn load_data(&mut self, data: &[u8]) -> Result<()> {
let pic = self.picture_mut();
pic.load_data(data, crate::picture::MimeType::Lottie, None)
}
#[cfg(feature = "std")]
pub fn load_file(&mut self, path: &str) -> Result<()> {
let pic = self.picture_mut();
pic.load_from_str(path)
}
pub fn set_size(&mut self, w: f32, h: f32) -> Result<()> {
let pic = self.picture_mut();
pic.set_size(w, h)
}
pub fn gen_slot(&mut self, slot_json: &str) -> Option<u32> {
let c_slot = CString::new(slot_json).ok()?;
let id = unsafe { sys::tvg_lottie_animation_gen_slot(self.inner.raw(), c_slot.as_ptr()) };
if id == 0 {
None
} else {
Some(id)
}
}
pub fn apply_slot(&mut self, id: u32) -> Result<()> {
Error::from_raw(unsafe { sys::tvg_lottie_animation_apply_slot(self.inner.raw(), id) })
}
pub fn del_slot(&mut self, id: u32) -> Result<()> {
Error::from_raw(unsafe { sys::tvg_lottie_animation_del_slot(self.inner.raw(), id) })
}
pub fn markers_count(&self) -> Result<u32> {
let mut cnt: u32 = 0;
Error::from_raw(unsafe {
sys::tvg_lottie_animation_get_markers_cnt(self.inner.raw(), &raw mut cnt)
})?;
Ok(cnt)
}
pub fn marker_name(&self, idx: u32) -> Result<String> {
let mut name_ptr: *const core::ffi::c_char = core::ptr::null();
Error::from_raw(unsafe {
sys::tvg_lottie_animation_get_marker(self.inner.raw(), idx, &raw mut name_ptr)
})?;
if name_ptr.is_null() {
return Ok(String::new());
}
Ok(unsafe { core::ffi::CStr::from_ptr(name_ptr) }
.to_string_lossy()
.into_owned())
}
pub fn marker_info(&self, idx: u32) -> Result<Marker> {
let mut name_ptr: *const core::ffi::c_char = core::ptr::null();
let mut begin: f32 = 0.0;
let mut end: f32 = 0.0;
Error::from_raw(unsafe {
sys::tvg_lottie_animation_get_marker_info(
self.inner.raw(),
idx,
&raw mut name_ptr,
&raw mut begin,
&raw mut end,
)
})?;
let name = if name_ptr.is_null() {
String::new()
} else {
unsafe { core::ffi::CStr::from_ptr(name_ptr) }
.to_string_lossy()
.into_owned()
};
Ok(Marker { name, begin, end })
}
pub fn markers(&self) -> Result<Vec<Marker>> {
let count = self.markers_count()?;
let mut markers = Vec::with_capacity(count as usize);
for i in 0..count {
markers.push(self.marker_info(i)?);
}
Ok(markers)
}
pub fn set_marker(&mut self, marker: &str) -> Result<()> {
let c_marker = CString::new(marker)?;
Error::from_raw(unsafe {
sys::tvg_lottie_animation_set_marker(self.inner.raw(), c_marker.as_ptr())
})
}
pub fn tween(&mut self, from: f32, to: f32, progress: f32) -> Result<()> {
Error::from_raw(unsafe {
sys::tvg_lottie_animation_tween(self.inner.raw(), from, to, progress)
})
}
pub fn set_quality(&mut self, value: u8) -> Result<()> {
Error::from_raw(unsafe { sys::tvg_lottie_animation_set_quality(self.inner.raw(), value) })
}
pub fn set_audio_resolver<F>(&mut self, resolver: F) -> Result<()>
where
F: FnMut(&AudioInfo<'_>) + Send + 'static,
{
if self.audio_resolver.is_some() {
unsafe {
sys::tvg_lottie_animation_set_audio_resolver(
self.inner.raw(),
None,
core::ptr::null_mut(),
);
}
self.audio_resolver = None;
}
let boxed: alloc::boxed::Box<F> = alloc::boxed::Box::new(resolver);
let raw_f: *mut F = alloc::boxed::Box::into_raw(boxed);
let data = unsafe { core::ptr::NonNull::new_unchecked(raw_f.cast::<()>()) };
self.audio_resolver = Some(ErasedAudioResolver {
data,
drop_fn: drop_audio_resolver::<F>,
});
Error::from_raw(unsafe {
sys::tvg_lottie_animation_set_audio_resolver(
self.inner.raw(),
Some(audio_trampoline::<F>),
data.as_ptr().cast::<core::ffi::c_void>(),
)
})
}
pub fn clear_audio_resolver(&mut self) -> Result<()> {
let r = Error::from_raw(unsafe {
sys::tvg_lottie_animation_set_audio_resolver(
self.inner.raw(),
None,
core::ptr::null_mut(),
)
});
self.audio_resolver = None;
r
}
}
impl<'eng> core::ops::Deref for LottieAnimation<'eng> {
type Target = Animation<'eng>;
fn deref(&self) -> &Animation<'eng> {
&self.inner
}
}
impl<'eng> core::ops::DerefMut for LottieAnimation<'eng> {
fn deref_mut(&mut self) -> &mut Animation<'eng> {
&mut self.inner
}
}
impl core::fmt::Debug for LottieAnimation<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("LottieAnimation").finish_non_exhaustive()
}
}
impl Drop for LottieAnimation<'_> {
fn drop(&mut self) {
if self.audio_resolver.is_some() {
unsafe {
sys::tvg_lottie_animation_set_audio_resolver(
self.inner.raw(),
None,
core::ptr::null_mut(),
);
}
}
}
}
struct ErasedAudioResolver {
data: core::ptr::NonNull<()>,
drop_fn: unsafe fn(core::ptr::NonNull<()>),
}
impl Drop for ErasedAudioResolver {
fn drop(&mut self) {
unsafe { (self.drop_fn)(self.data) }
}
}
unsafe impl Send for ErasedAudioResolver {}
unsafe fn drop_audio_resolver<F>(data: core::ptr::NonNull<()>) {
drop(unsafe { alloc::boxed::Box::from_raw(data.as_ptr().cast::<F>()) });
}
unsafe extern "C" fn audio_trampoline<F>(
info: *const sys::Tvg_Audio_Info,
data: *mut core::ffi::c_void,
) where
F: FnMut(&AudioInfo<'_>) + Send + 'static,
{
if data.is_null() || info.is_null() {
return;
}
let f = unsafe { &mut *data.cast::<F>() };
let view = AudioInfo {
raw: unsafe { &*info },
};
invoke_audio::<F>(f, &view);
}
#[cfg(feature = "std")]
fn invoke_audio<F>(f: &mut F, info: &AudioInfo<'_>)
where
F: FnMut(&AudioInfo<'_>) + Send + 'static,
{
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(info)));
}
#[cfg(not(feature = "std"))]
fn invoke_audio<F>(f: &mut F, info: &AudioInfo<'_>)
where
F: FnMut(&AudioInfo<'_>) + Send + 'static,
{
f(info);
}