1use std::path::PathBuf;
17use std::sync::{Arc, Mutex, OnceLock};
18
19use cosmic_text::{fontdb, FontSystem, SwashCache};
20
21use crate::error::{Error, Result};
22
23const BUNDLED: &[&[u8]] = &[
25 include_bytes!("../assets/fonts/NotoSansSC.ttf.zst"),
26 include_bytes!("../assets/fonts/JetBrainsMono.ttf.zst"),
27 include_bytes!("../assets/fonts/JetBrainsMono-Italic.ttf.zst"),
28];
29
30const ZSTD_MAGIC: [u8; 4] = [0x28, 0xb5, 0x2f, 0xfd];
32
33#[derive(Clone)]
36pub struct FontHandle(Arc<Inner>);
37
38struct Inner {
39 system: Mutex<FontSystem>,
40 cache: Mutex<SwashCache>,
41}
42
43impl FontHandle {
44 pub fn builder() -> FontStackBuilder {
46 FontStackBuilder::new()
47 }
48
49 pub fn shared_default() -> FontHandle {
51 static DEFAULT: OnceLock<FontHandle> = OnceLock::new();
52 DEFAULT
53 .get_or_init(|| {
54 FontHandle::builder()
55 .bundled()
56 .system()
57 .build()
58 .unwrap_or_else(|_| FontHandle::from_db(fontdb::Database::new()))
59 })
60 .clone()
61 }
62
63 fn from_db(db: fontdb::Database) -> FontHandle {
64 let system = FontSystem::new_with_locale_and_db("zh-CN".to_string(), db);
65 FontHandle(Arc::new(Inner {
66 system: Mutex::new(system),
67 cache: Mutex::new(SwashCache::new()),
68 }))
69 }
70
71 pub(crate) fn with_system<R>(&self, f: impl FnOnce(&mut FontSystem) -> R) -> R {
76 let mut sys = self.0.system.lock().unwrap_or_else(|e| e.into_inner());
77 f(&mut sys)
78 }
79
80 pub(crate) fn with_cache<R>(&self, f: impl FnOnce(&mut SwashCache, &mut FontSystem) -> R) -> R {
83 let mut cache = self.0.cache.lock().unwrap_or_else(|e| e.into_inner());
84 let mut sys = self.0.system.lock().unwrap_or_else(|e| e.into_inner());
85 f(&mut cache, &mut sys)
86 }
87}
88
89pub struct FontStackBuilder {
91 bundled: bool,
92 datas: Vec<Vec<u8>>,
93 dirs: Vec<PathBuf>,
94 system: bool,
95}
96
97impl FontStackBuilder {
98 fn new() -> Self {
99 Self { bundled: true, datas: Vec::new(), dirs: Vec::new(), system: false }
100 }
101
102 pub fn bundled(mut self) -> Self {
104 self.bundled = true;
105 self
106 }
107
108 pub fn no_bundled(mut self) -> Self {
110 self.bundled = false;
111 self
112 }
113
114 pub fn data(mut self, bytes: impl Into<Vec<u8>>) -> Self {
117 self.datas.push(bytes.into());
118 self
119 }
120
121 pub fn dir(mut self, p: impl Into<PathBuf>) -> Self {
123 self.dirs.push(p.into());
124 self
125 }
126
127 pub fn system(mut self) -> Self {
129 self.system = true;
130 self
131 }
132
133 pub fn build(self) -> Result<FontHandle> {
135 let mut db = fontdb::Database::new();
136 if self.bundled {
137 for z in BUNDLED {
138 db.load_font_data(unzstd(z)?);
139 }
140 }
141 for d in self.datas {
142 if d.starts_with(&ZSTD_MAGIC) {
143 db.load_font_data(unzstd(&d)?);
144 } else {
145 db.load_font_data(d);
146 }
147 }
148 for d in &self.dirs {
149 db.load_fonts_dir(d);
150 }
151 if self.system {
152 db.load_system_fonts();
153 }
154 if db.is_empty() {
155 return Err(Error::FontLoad("字体栈为空(未启用任何字体来源)".into()));
156 }
157 Ok(FontHandle::from_db(db))
158 }
159}
160
161fn unzstd(data: &[u8]) -> Result<Vec<u8>> {
163 use std::io::Read;
164 let mut dec = ruzstd::decoding::StreamingDecoder::new(data)
165 .map_err(|e| Error::FontLoad(format!("内置字体解压失败:{e}")))?;
166 let mut out = Vec::new();
167 dec.read_to_end(&mut out).map_err(|e| Error::FontLoad(format!("内置字体解压失败:{e}")))?;
168 Ok(out)
169}
170