use alloc::ffi::CString;
use crate::error::{Error, Result};
use crate::paint::Paint;
use thorvg_sys as sys;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FilterMethod {
Bilinear,
Nearest,
}
impl FilterMethod {
fn to_raw(self) -> sys::Tvg_Filter_Method {
match self {
FilterMethod::Bilinear => sys::Tvg_Filter_Method::TVG_FILTER_METHOD_BILINEAR,
FilterMethod::Nearest => sys::Tvg_Filter_Method::TVG_FILTER_METHOD_NEAREST,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum MimeType {
Svg,
Png,
Jpg,
Webp,
Lottie,
Raw,
}
impl MimeType {
fn as_c_str(self) -> &'static core::ffi::CStr {
match self {
MimeType::Svg => c"svg",
MimeType::Png => c"png",
MimeType::Jpg => c"jpg",
MimeType::Webp => c"webp",
MimeType::Lottie => c"lottie+json",
MimeType::Raw => c"raw",
}
}
}
pub struct Picture<'eng> {
raw: sys::Tvg_Paint,
owned: bool,
resolver: Option<ErasedResolver>,
_engine: core::marker::PhantomData<&'eng ()>,
}
struct ErasedResolver {
data: core::ptr::NonNull<()>,
drop_fn: unsafe fn(core::ptr::NonNull<()>),
}
impl Drop for ErasedResolver {
fn drop(&mut self) {
unsafe { (self.drop_fn)(self.data) }
}
}
unsafe impl Send for ErasedResolver {}
unsafe fn drop_resolver<F>(data: core::ptr::NonNull<()>) {
drop(unsafe { alloc::boxed::Box::from_raw(data.as_ptr().cast::<F>()) });
}
unsafe impl Send for Picture<'_> {}
impl Picture<'_> {
pub(crate) fn new() -> Result<Self> {
let raw = unsafe { sys::tvg_picture_new() };
if raw.is_null() {
return Err(Error::FailedAllocation);
}
Ok(Self {
raw,
owned: true,
resolver: None,
_engine: core::marker::PhantomData,
})
}
pub(crate) unsafe fn from_raw(raw: sys::Tvg_Paint, owned: bool) -> Self {
Self {
raw,
owned,
resolver: None,
_engine: core::marker::PhantomData,
}
}
pub fn load_from_str(&mut self, path: &str) -> Result<()> {
let c_path = CString::new(path)?;
Error::from_raw(unsafe { sys::tvg_picture_load(self.raw, c_path.as_ptr()) })
}
#[cfg(feature = "std")]
pub fn load<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<()> {
self.load_from_str(&path.as_ref().to_string_lossy())
}
pub fn load_data(
&mut self,
data: &[u8],
mime: MimeType,
resource_path: Option<&str>,
) -> Result<()> {
load_data_inner(self.raw, data, mime, resource_path, true)
}
pub fn load_data_static(
&mut self,
data: &'static [u8],
mime: MimeType,
resource_path: Option<&str>,
) -> Result<()> {
load_data_inner(self.raw, data, mime, resource_path, false)
}
pub fn load_raw(
&mut self,
data: &[u32],
w: u32,
h: u32,
cs: crate::ColorSpace,
) -> Result<()> {
load_raw_inner(self.raw, data, w, h, cs, true)
}
pub fn load_raw_static(
&mut self,
data: &'static [u32],
w: u32,
h: u32,
cs: crate::ColorSpace,
) -> Result<()> {
load_raw_inner(self.raw, data, w, h, cs, false)
}
pub fn set_size(&mut self, w: f32, h: f32) -> Result<()> {
Error::from_raw(unsafe { sys::tvg_picture_set_size(self.raw, w, h) })
}
pub fn size(&self) -> Result<(f32, f32)> {
let (mut w, mut h) = (0.0f32, 0.0f32);
Error::from_raw(unsafe { sys::tvg_picture_get_size(self.raw, &raw mut w, &raw mut h) })?;
Ok((w, h))
}
pub fn set_origin(&mut self, x: f32, y: f32) -> Result<()> {
Error::from_raw(unsafe { sys::tvg_picture_set_origin(self.raw, x, y) })
}
pub fn origin(&self) -> Result<(f32, f32)> {
let (mut x, mut y) = (0.0f32, 0.0f32);
Error::from_raw(unsafe { sys::tvg_picture_get_origin(self.raw, &raw mut x, &raw mut y) })?;
Ok((x, y))
}
pub fn set_filter(&mut self, method: FilterMethod) -> Result<()> {
Error::from_raw(unsafe { sys::tvg_picture_set_filter(self.raw, method.to_raw()) })
}
pub fn set_accessible(&mut self, accessible: bool) -> Result<()> {
Error::from_raw(unsafe { sys::tvg_picture_set_accessible(self.raw, accessible) })
}
pub fn set_asset_resolver<F>(&mut self, resolver: F) -> Result<()>
where
F: FnMut(&str) -> Option<(alloc::vec::Vec<u8>, MimeType)> + Send + 'static,
{
if self.resolver.is_some() {
unsafe {
sys::tvg_picture_set_asset_resolver(
self.raw,
None,
core::ptr::null_mut(),
);
}
self.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.resolver = Some(ErasedResolver {
data,
drop_fn: drop_resolver::<F>,
});
Error::from_raw(unsafe {
sys::tvg_picture_set_asset_resolver(
self.raw,
Some(resolver_trampoline::<F>),
data.as_ptr().cast::<core::ffi::c_void>(),
)
})
}
pub fn clear_asset_resolver(&mut self) -> Result<()> {
let r = Error::from_raw(unsafe {
sys::tvg_picture_set_asset_resolver(self.raw, None, core::ptr::null_mut())
});
self.resolver = None;
r
}
pub fn get_paint(&self, id: u32) -> Option<crate::paint::BorrowedPaint<'_>> {
let raw = unsafe { sys::tvg_picture_get_paint(self.raw, id) };
if raw.is_null() {
None
} else {
Some(unsafe { crate::paint::BorrowedPaint::from_raw(raw) })
}
}
}
impl crate::paint::sealed::Sealed for Picture<'_> {}
impl Paint for Picture<'_> {
fn raw(&self) -> sys::Tvg_Paint {
self.raw
}
fn into_raw(mut self) -> sys::Tvg_Paint {
self.owned = false;
self.raw
}
unsafe fn from_raw_paint(raw: sys::Tvg_Paint) -> Self {
Self {
raw,
owned: true,
resolver: None,
_engine: core::marker::PhantomData,
}
}
}
impl Drop for Picture<'_> {
fn drop(&mut self) {
if self.resolver.is_some() {
unsafe {
sys::tvg_picture_set_asset_resolver(self.raw, None, core::ptr::null_mut());
}
}
if self.owned {
unsafe {
sys::tvg_paint_rel(self.raw);
}
}
}
}
#[allow(clippy::cast_possible_truncation)]
fn load_data_inner(
raw: sys::Tvg_Paint,
data: &[u8],
mime: MimeType,
resource_path: Option<&str>,
copy: bool,
) -> Result<()> {
let c_rpath = resource_path.map(CString::new).transpose()?;
let rpath_ptr = c_rpath.as_ref().map_or(core::ptr::null(), |c| c.as_ptr());
Error::from_raw(unsafe {
sys::tvg_picture_load_data(
raw,
data.as_ptr().cast::<core::ffi::c_char>(),
data.len() as u32,
mime.as_c_str().as_ptr(),
rpath_ptr,
copy,
)
})
}
fn load_raw_inner(
raw: sys::Tvg_Paint,
data: &[u32],
w: u32,
h: u32,
cs: crate::ColorSpace,
copy: bool,
) -> Result<()> {
let Some(needed) = u64::from(w).checked_mul(u64::from(h)) else {
return Err(Error::InvalidArguments);
};
if (data.len() as u64) < needed {
return Err(Error::InvalidArguments);
}
Error::from_raw(unsafe { sys::tvg_picture_load_raw(raw, data.as_ptr(), w, h, cs.to_raw(), copy) })
}
unsafe extern "C" fn resolver_trampoline<F>(
paint: sys::Tvg_Paint,
src: *const core::ffi::c_char,
data: *mut core::ffi::c_void,
) -> bool
where
F: FnMut(&str) -> Option<(alloc::vec::Vec<u8>, MimeType)> + Send + 'static,
{
if data.is_null() || src.is_null() {
return false;
}
let f = unsafe { &mut *data.cast::<F>() };
let src_str = unsafe { core::ffi::CStr::from_ptr(src) }.to_string_lossy();
let resolved = invoke_resolver::<F>(f, &src_str);
let Some((bytes, mime)) = resolved else {
return false;
};
#[allow(clippy::cast_possible_truncation)]
let r = unsafe {
sys::tvg_picture_load_data(
paint,
bytes.as_ptr().cast::<core::ffi::c_char>(),
bytes.len() as u32,
mime.as_c_str().as_ptr(),
core::ptr::null(),
true,
)
};
r == sys::Tvg_Result::TVG_RESULT_SUCCESS
}
#[cfg(feature = "std")]
fn invoke_resolver<F>(f: &mut F, src: &str) -> Option<(alloc::vec::Vec<u8>, MimeType)>
where
F: FnMut(&str) -> Option<(alloc::vec::Vec<u8>, MimeType)> + Send + 'static,
{
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(src))).unwrap_or(None)
}
#[cfg(not(feature = "std"))]
fn invoke_resolver<F>(f: &mut F, src: &str) -> Option<(alloc::vec::Vec<u8>, MimeType)>
where
F: FnMut(&str) -> Option<(alloc::vec::Vec<u8>, MimeType)> + Send + 'static,
{
f(src)
}
impl core::fmt::Debug for Picture<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Picture").finish_non_exhaustive()
}
}