use std::collections::HashMap;
use std::rc::Rc;
use teksilo_canvas::RasterIcon;
use teksilo_text::text_document::{ResourceType, TextDocument};
pub type ImageResolver = Rc<dyn Fn(&str) -> Option<(String, Vec<u8>)>>;
pub const MAX_DISPLAY_EDGE: u32 = 2048;
struct Entry {
icon: RasterIcon,
registered: bool,
}
#[derive(Default)]
pub struct ImageCache {
entries: HashMap<String, Option<Entry>>,
}
impl std::fmt::Debug for ImageCache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ImageCache")
.field("len", &self.entries.len())
.finish()
}
}
impl ImageCache {
pub fn new() -> Self {
Self::default()
}
fn resolve(
&mut self,
document: &TextDocument,
name: &str,
resolver: Option<&ImageResolver>,
) -> Option<&mut Entry> {
if !self.entries.contains_key(name) {
let mut bytes = document.resource(name).ok().flatten();
if bytes.is_none()
&& let Some(resolve) = resolver
&& let Some((mime, supplied)) = resolve(name)
{
let _ = document.add_resource(ResourceType::Image, name, &mime, &supplied);
bytes = Some(supplied);
}
let decoded = bytes
.and_then(|bytes| RasterIcon::decode(&bytes).ok())
.map(|icon| {
let icon = icon.downsample_to_max(MAX_DISPLAY_EDGE).unwrap_or(icon);
Entry {
icon,
registered: false,
}
});
self.entries.insert(name.to_string(), decoded);
}
self.entries.get_mut(name).and_then(|e| e.as_mut())
}
pub fn ensure_registered(
&mut self,
canvas: &mut teksilo_canvas::Canvas,
document: &TextDocument,
name: &str,
resolver: Option<&ImageResolver>,
) -> bool {
let Some(entry) = self.resolve(document, name, resolver) else {
return false;
};
if !entry.registered && !canvas.has_pending_image(name) {
canvas.ensure_image_registered(
name,
entry.icon.width(),
entry.icon.height(),
std::borrow::Cow::Owned(entry.icon.pixels().to_vec()),
);
}
entry.registered = true;
true
}
pub fn size_of(&self, name: &str) -> Option<(u32, u32)> {
self.entries
.get(name)
.and_then(|e| e.as_ref())
.map(|e| (e.icon.width(), e.icon.height()))
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn invalidate_registrations(&mut self) {
for entry in self.entries.values_mut().flatten() {
entry.registered = false;
}
}
pub fn retain_only<'a>(&mut self, live: impl IntoIterator<Item = &'a str>) -> Vec<String> {
let live: std::collections::HashSet<&str> = live.into_iter().collect();
let dropped: Vec<String> = self
.entries
.keys()
.filter(|k| !live.contains(k.as_str()))
.cloned()
.collect();
for name in &dropped {
self.entries.remove(name);
}
dropped
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}