use alloc::collections::btree_map::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use std::path::PathBuf;
use std::sync::atomic::Ordering;
use crate::{FcFontPath, FcPattern, FontId};
use crate::registry::FcFontRegistry;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FontManifest {
pub version: u32,
pub entries: BTreeMap<String, FontCacheEntry>,
}
impl FontManifest {
pub const CURRENT_VERSION: u32 = 2;
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FontCacheEntry {
pub mtime_secs: u64,
pub file_size: u64,
#[serde(default)]
pub bytes_hash: u64,
pub font_indices: Vec<FontIndexEntry>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FontIndexEntry {
pub pattern: FcPattern,
pub font_index: usize,
}
impl FcFontRegistry {
#[cfg(not(target_family = "wasm"))]
pub fn load_from_disk_cache(&self) -> Option<()> {
let cache_path = get_font_cache_path()?;
let data = std::fs::read(&cache_path).ok()?;
let manifest: FontManifest = bincode::deserialize(&data).ok()?;
if manifest.version != FontManifest::CURRENT_VERSION {
return None;
}
let mut cache = self.cache.write().ok()?;
let mut processed = self.processed_paths.lock().ok()?;
let mut completed = self.completed_paths.lock().ok()?;
manifest.entries.iter()
.flat_map(|(path_str, entry)| {
let pb = PathBuf::from(path_str);
processed.insert(pb.clone());
completed.insert(pb);
let hash = entry.bytes_hash;
entry.font_indices.iter().map(move |idx_entry| (path_str, hash, idx_entry))
})
.for_each(|(path_str, bytes_hash, idx_entry)| {
let id = FontId::new();
cache.index_pattern_tokens(&idx_entry.pattern, id);
cache.patterns.insert(idx_entry.pattern.clone(), id);
cache.disk_fonts.insert(id, FcFontPath {
path: path_str.clone(),
font_index: idx_entry.font_index,
bytes_hash,
});
cache.metadata.insert(id, idx_entry.pattern.clone());
});
self.cache_loaded.store(true, Ordering::Release);
Some(())
}
#[cfg(target_family = "wasm")]
pub fn load_from_disk_cache(&self) -> Option<()> {
None
}
#[cfg(not(target_family = "wasm"))]
pub fn save_to_disk_cache(&self) -> Option<()> {
let cache_path = get_font_cache_path()?;
std::fs::create_dir_all(cache_path.parent()?).ok()?;
let cache = self.cache.read().ok()?;
let mut entries: BTreeMap<String, FontCacheEntry> = BTreeMap::new();
cache.disk_fonts.iter()
.filter_map(|(id, font_path)| {
cache.metadata.get(id).map(|pattern| (font_path, pattern))
})
.for_each(|(font_path, pattern)| {
entries
.entry(font_path.path.clone())
.or_insert_with(|| {
let (mtime_secs, file_size) = get_file_metadata(&font_path.path)
.unwrap_or((0, 0));
FontCacheEntry {
mtime_secs,
file_size,
bytes_hash: font_path.bytes_hash,
font_indices: Vec::new(),
}
})
.font_indices
.push(FontIndexEntry {
pattern: pattern.clone(),
font_index: font_path.font_index,
});
});
let manifest = FontManifest {
version: FontManifest::CURRENT_VERSION,
entries,
};
let data = bincode::serialize(&manifest).ok()?;
std::fs::write(&cache_path, data).ok()?;
Some(())
}
#[cfg(target_family = "wasm")]
pub fn save_to_disk_cache(&self) -> Option<()> {
None
}
}
pub fn get_file_metadata(path: &str) -> Option<(u64, u64)> {
let meta = std::fs::metadata(path).ok()?;
let mtime = meta.modified().ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_secs())
.unwrap_or(0);
Some((mtime, meta.len()))
}
pub fn get_font_cache_path() -> Option<PathBuf> {
let base = get_cache_base_dir()?;
Some(base.join("fonts").join("manifest.bin"))
}
#[cfg(not(target_family = "wasm"))]
pub fn get_cache_base_dir() -> Option<PathBuf> {
dirs::cache_dir().map(|d| d.join("rfc"))
}
#[cfg(target_family = "wasm")]
pub fn get_cache_base_dir() -> Option<PathBuf> {
None
}