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::registry::FcFontRegistry;
use crate::{FcFontPath, FcPattern, FontId};
#[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 = 3;
}
#[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<()> {
self.load_from_disk_cache_at(&get_font_cache_path()?)
}
#[cfg(not(target_family = "wasm"))]
pub fn load_from_disk_cache_at(&self, cache_path: &std::path::Path) -> Option<()> {
use std::io::Read;
let mut file = std::fs::File::open(cache_path).ok()?;
let mut data = Vec::new();
file.read_to_end(&mut data).ok()?;
let manifest: FontManifest = bincode::deserialize(&data).ok()?;
if manifest.version != FontManifest::CURRENT_VERSION {
return None;
}
let mut state = self.cache.state_write();
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)| {
state.insert_disk_font(
idx_entry.pattern.clone(),
FontId::new(),
FcFontPath {
path: path_str.clone(),
font_index: idx_entry.font_index,
bytes_hash,
},
);
});
drop(state);
self.cache_loaded.store(true, Ordering::Release);
Some(())
}
#[cfg(target_family = "wasm")]
pub fn load_from_disk_cache(&self) -> Option<()> {
None
}
#[cfg(target_family = "wasm")]
pub fn load_from_disk_cache_at(&self, _cache_path: &std::path::Path) -> Option<()> {
None
}
#[cfg(not(target_family = "wasm"))]
pub fn save_to_disk_cache(&self) -> Option<()> {
self.save_to_disk_cache_at(&get_font_cache_path()?)
}
#[cfg(not(target_family = "wasm"))]
pub fn save_to_disk_cache_at(&self, cache_path: &std::path::Path) -> Option<()> {
std::fs::create_dir_all(cache_path.parent()?).ok()?;
let state = self.cache.state_read();
let mut entries: BTreeMap<String, FontCacheEntry> = BTreeMap::new();
state
.disk_fonts
.iter()
.filter_map(|(id, font_path)| {
state.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,
};
drop(state);
let data = bincode::serialize(&manifest).ok()?;
let mut tmp_name = cache_path.file_name()?.to_os_string();
tmp_name.push(alloc::format!(".tmp-{}", std::process::id()));
let tmp_path = cache_path.with_file_name(tmp_name);
use std::io::Write;
let mut file = match std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&tmp_path)
{
Ok(f) => f,
Err(_) => return None,
};
if file.write_all(&data).is_err() || file.sync_all().is_err() {
drop(file);
let _ = std::fs::remove_file(&tmp_path);
return None;
}
drop(file);
if std::fs::rename(&tmp_path, cache_path).is_err() {
let _ = std::fs::remove_file(&tmp_path);
return None;
}
Some(())
}
#[cfg(target_family = "wasm")]
pub fn save_to_disk_cache(&self) -> Option<()> {
None
}
#[cfg(target_family = "wasm")]
pub fn save_to_disk_cache_at(&self, _cache_path: &std::path::Path) -> Option<()> {
None
}
}
pub fn get_file_metadata(path: &str) -> Option<(u64, u64)> {
let meta = std::fs::File::open(path).and_then(|f| f.metadata()).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
}