use std::cell::Cell;
use std::num::NonZeroUsize;
use std::sync::{Mutex, PoisonError};
use crate::media::ImageData;
pub use crate::media::{image_pixels, rendered_image};
use crate::{Svgr, usvgr};
const STATIC_IMAGES: NonZeroUsize = NonZeroUsize::new(64).unwrap();
pub trait SvgrRasterizer: Sync {
fn rasterize(&self, tree: &usvgr::Tree, width: u32, height: u32) -> Option<ImageData<'static>>;
}
pub struct SvgrImages<'a> {
rasterizer: Option<Box<dyn SvgrRasterizer + 'a>>,
static_images: Mutex<lru::LruCache<(u64, u32, u32), ImageData<'static>>>,
}
struct Scope<'s> {
images: &'s SvgrImages<'s>,
options: &'s usvgr::Options<'s>,
font_db: &'s usvgr::fontdb::Database,
}
thread_local! {
static ACTIVE: Cell<*const ()> = const { Cell::new(std::ptr::null()) };
}
impl<'a> SvgrImages<'a> {
pub fn new(rasterizer: Option<Box<dyn SvgrRasterizer + 'a>>) -> Self {
Self {
rasterizer,
static_images: Mutex::new(lru::LruCache::new(STATIC_IMAGES)),
}
}
pub fn enter<R>(
&self,
options: &usvgr::Options,
font_db: &usvgr::fontdb::Database,
f: impl FnOnce() -> R,
) -> R {
struct Restore(*const ());
impl Drop for Restore {
fn drop(&mut self) {
ACTIVE.set(self.0);
}
}
let scope = Scope {
images: self,
options,
font_db,
};
let _restore = Restore(ACTIVE.replace(std::ptr::from_ref(&scope).cast()));
f()
}
}
impl Scope<'_> {
fn render(&self, svgr: Svgr<'_>, width: u32, height: u32) -> Option<ImageData<'static>> {
let images = self.images;
let rasterizer = images.rasterizer.as_deref()?;
let key = static_hash(&svgr).map(|hash| (hash, width, height));
let cached = |key| {
images
.static_images
.lock()
.unwrap_or_else(PoisonError::into_inner)
.get(&key)
.cloned()
};
if let Some(image) = key.and_then(cached) {
return Some(image);
}
let tree = svgr
.into_svg_tree(self.options, &mut usvgr::Cache::default(), self.font_db)
.inspect_err(|e| log::warn!("fframes: render_svgr can not convert the tree: {e}"))
.ok()?;
let image = rasterizer.rasterize(&tree, width, height)?;
if let Some(key) = key {
images
.static_images
.lock()
.unwrap_or_else(PoisonError::into_inner)
.put(key, image.clone());
}
Some(image)
}
}
impl std::fmt::Debug for SvgrImages<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SvgrImages").finish_non_exhaustive()
}
}
pub(crate) fn render_svgr(svgr: Svgr<'_>, width: u32, height: u32) -> Option<ImageData<'static>> {
if width == 0 || height == 0 {
return None;
}
let scope = ACTIVE.get();
if scope.is_null() {
return None;
}
let scope = unsafe { &*scope.cast::<Scope<'_>>() };
scope.render(svgr, width, height)
}
fn static_hash(svgr: &Svgr<'_>) -> Option<u64> {
#[cfg(feature = "compile-time-svgtree")]
if let [Some(root)] = svgr.svg_tree.nodes.as_slice() {
return root.static_hash;
}
let _ = svgr;
None
}