use std::path::PathBuf;
use std::sync::{Arc, Mutex, OnceLock};
use cosmic_text::{fontdb, FontSystem, SwashCache};
use crate::error::{Error, Result};
const BUNDLED: &[&[u8]] = &[
include_bytes!("../assets/fonts/NotoSansSC.ttf.zst"),
include_bytes!("../assets/fonts/JetBrainsMono.ttf.zst"),
include_bytes!("../assets/fonts/JetBrainsMono-Italic.ttf.zst"),
];
const ZSTD_MAGIC: [u8; 4] = [0x28, 0xb5, 0x2f, 0xfd];
#[derive(Clone)]
pub struct FontHandle(Arc<Inner>);
struct Inner {
system: Mutex<FontSystem>,
cache: Mutex<SwashCache>,
}
impl FontHandle {
pub fn builder() -> FontStackBuilder {
FontStackBuilder::new()
}
pub fn shared_default() -> FontHandle {
static DEFAULT: OnceLock<FontHandle> = OnceLock::new();
DEFAULT
.get_or_init(|| {
FontHandle::builder()
.bundled()
.system()
.build()
.unwrap_or_else(|_| FontHandle::from_db(fontdb::Database::new()))
})
.clone()
}
fn from_db(db: fontdb::Database) -> FontHandle {
let system = FontSystem::new_with_locale_and_db("zh-CN".to_string(), db);
FontHandle(Arc::new(Inner {
system: Mutex::new(system),
cache: Mutex::new(SwashCache::new()),
}))
}
pub(crate) fn with_system<R>(&self, f: impl FnOnce(&mut FontSystem) -> R) -> R {
let mut sys = self.0.system.lock().unwrap_or_else(|e| e.into_inner());
f(&mut sys)
}
pub(crate) fn with_cache<R>(&self, f: impl FnOnce(&mut SwashCache, &mut FontSystem) -> R) -> R {
let mut cache = self.0.cache.lock().unwrap_or_else(|e| e.into_inner());
let mut sys = self.0.system.lock().unwrap_or_else(|e| e.into_inner());
f(&mut cache, &mut sys)
}
}
pub struct FontStackBuilder {
bundled: bool,
datas: Vec<Vec<u8>>,
dirs: Vec<PathBuf>,
system: bool,
}
impl FontStackBuilder {
fn new() -> Self {
Self { bundled: true, datas: Vec::new(), dirs: Vec::new(), system: false }
}
pub fn bundled(mut self) -> Self {
self.bundled = true;
self
}
pub fn no_bundled(mut self) -> Self {
self.bundled = false;
self
}
pub fn data(mut self, bytes: impl Into<Vec<u8>>) -> Self {
self.datas.push(bytes.into());
self
}
pub fn dir(mut self, p: impl Into<PathBuf>) -> Self {
self.dirs.push(p.into());
self
}
pub fn system(mut self) -> Self {
self.system = true;
self
}
pub fn build(self) -> Result<FontHandle> {
let mut db = fontdb::Database::new();
if self.bundled {
for z in BUNDLED {
db.load_font_data(unzstd(z)?);
}
}
for d in self.datas {
if d.starts_with(&ZSTD_MAGIC) {
db.load_font_data(unzstd(&d)?);
} else {
db.load_font_data(d);
}
}
for d in &self.dirs {
db.load_fonts_dir(d);
}
if self.system {
db.load_system_fonts();
}
if db.is_empty() {
return Err(Error::FontLoad("字体栈为空(未启用任何字体来源)".into()));
}
Ok(FontHandle::from_db(db))
}
}
fn unzstd(data: &[u8]) -> Result<Vec<u8>> {
use std::io::Read;
let mut dec = ruzstd::decoding::StreamingDecoder::new(data)
.map_err(|e| Error::FontLoad(format!("内置字体解压失败:{e}")))?;
let mut out = Vec::new();
dec.read_to_end(&mut out).map_err(|e| Error::FontLoad(format!("内置字体解压失败:{e}")))?;
Ok(out)
}