1use std::collections::HashMap;
18use std::path::PathBuf;
19use std::sync::{Arc, Mutex, OnceLock};
20
21use parley::fontique::{Blob, Collection, CollectionOptions};
22use parley::{FontContext, LayoutContext};
23use swash::scale::image::Content;
24use swash::scale::{Render, ScaleContext, Source, StrikeWith};
25use swash::zeno::{Angle, Format, Transform};
26
27use crate::error::{Error, Result};
28use crate::layout::{GlyphFont, Ink};
29
30const BUNDLED: &[&[u8]] = &[
32 include_bytes!("../assets/fonts/NotoSansSC.ttf.zst"),
33 include_bytes!("../assets/fonts/JetBrainsMono.ttf.zst"),
34 include_bytes!("../assets/fonts/JetBrainsMono-Italic.ttf.zst"),
35];
36
37const ZSTD_MAGIC: [u8; 4] = [0x28, 0xb5, 0x2f, 0xfd];
39
40#[derive(Clone)]
44pub struct FontHandle(Arc<Inner>);
45
46struct Inner {
47 fonts: Mutex<FontContext>,
48 layouts: Mutex<LayoutContext<Ink>>,
49 raster: Mutex<RasterState>,
50}
51
52struct RasterState {
54 scale: ScaleContext,
55 cache: HashMap<GlyphKey, Option<GlyphImage>>,
56}
57
58#[derive(Clone, PartialEq, Eq, Hash)]
61struct GlyphKey {
62 blob: u64,
63 index: u32,
64 glyph: u16,
65 size: u32,
66 coords: Vec<i16>,
67 skew: u32,
68 embolden: bool,
69}
70
71pub(crate) struct GlyphImage {
73 pub left: i32,
74 pub top: i32,
75 pub width: u32,
76 pub height: u32,
77 pub color: bool,
79 pub data: Vec<u8>,
80}
81
82pub(crate) struct GlyphRaster<'a>(&'a mut RasterState);
84
85impl GlyphRaster<'_> {
86 pub(crate) fn image(&mut self, gf: &GlyphFont, glyph: u16) -> Option<&GlyphImage> {
88 let key = GlyphKey {
89 blob: gf.font.data.id(),
90 index: gf.font.index,
91 glyph,
92 size: gf.size.to_bits(),
93 coords: gf.coords.clone(),
94 skew: gf.skew.map_or(0, f32::to_bits),
95 embolden: gf.embolden,
96 };
97 if !self.0.cache.contains_key(&key) {
98 let img = raster(&mut self.0.scale, gf, glyph);
99 self.0.cache.insert(key.clone(), img);
100 }
101 self.0.cache.get(&key).and_then(|o| o.as_ref())
102 }
103}
104
105fn raster(cx: &mut ScaleContext, gf: &GlyphFont, glyph: u16) -> Option<GlyphImage> {
109 let font_ref = swash::FontRef::from_index(gf.font.data.as_ref(), gf.font.index as usize)?;
110 let mut scaler =
111 cx.builder(font_ref).size(gf.size).hint(false).normalized_coords(&gf.coords).build();
112 let mut render =
113 Render::new(&[Source::ColorOutline(0), Source::ColorBitmap(StrikeWith::BestFit), Source::Outline]);
114 render.format(Format::Alpha);
115 if let Some(deg) = gf.skew {
116 render.transform(Some(Transform::skew(Angle::from_degrees(deg), Angle::ZERO)));
117 }
118 if gf.embolden {
119 render.embolden(gf.size * 0.02);
120 }
121 let img = render.render(&mut scaler, glyph)?;
122 let (width, height) = (img.placement.width, img.placement.height);
123 let (color, data) = match img.content {
124 Content::Color => (true, img.data),
125 Content::Mask => (false, img.data),
126 Content::SubpixelMask => {
128 (false, img.data.chunks_exact(3).map(|c| ((c[0] as u16 + c[1] as u16 + c[2] as u16) / 3) as u8).collect())
129 }
130 };
131 Some(GlyphImage { left: img.placement.left, top: img.placement.top, width, height, color, data })
132}
133
134impl FontHandle {
135 pub fn builder() -> FontStackBuilder {
137 FontStackBuilder::new()
138 }
139
140 pub fn shared_default() -> FontHandle {
142 static DEFAULT: OnceLock<FontHandle> = OnceLock::new();
143 DEFAULT
144 .get_or_init(|| {
145 FontHandle::builder().bundled().system().build().unwrap_or_else(|_| {
146 FontHandle::from_collection(Collection::new(CollectionOptions {
147 shared: false,
148 system_fonts: true,
149 }))
150 })
151 })
152 .clone()
153 }
154
155 fn from_collection(collection: Collection) -> FontHandle {
156 let fonts = FontContext { collection, source_cache: Default::default() };
157 FontHandle(Arc::new(Inner {
158 fonts: Mutex::new(fonts),
159 layouts: Mutex::new(LayoutContext::new()),
160 raster: Mutex::new(RasterState { scale: ScaleContext::new(), cache: HashMap::new() }),
161 }))
162 }
163
164 pub(crate) fn with_layout<R>(
170 &self,
171 f: impl FnOnce(&mut FontContext, &mut LayoutContext<Ink>) -> R,
172 ) -> R {
173 let mut fonts = self.0.fonts.lock().unwrap_or_else(|e| e.into_inner());
174 let mut layouts = self.0.layouts.lock().unwrap_or_else(|e| e.into_inner());
175 f(&mut fonts, &mut layouts)
176 }
177
178 pub(crate) fn with_raster<R>(&self, f: impl FnOnce(&mut GlyphRaster) -> R) -> R {
180 let mut raster = self.0.raster.lock().unwrap_or_else(|e| e.into_inner());
181 f(&mut GlyphRaster(&mut raster))
182 }
183}
184
185pub struct FontStackBuilder {
187 bundled: bool,
188 datas: Vec<Vec<u8>>,
189 dirs: Vec<PathBuf>,
190 system: bool,
191}
192
193impl FontStackBuilder {
194 fn new() -> Self {
195 Self { bundled: true, datas: Vec::new(), dirs: Vec::new(), system: false }
196 }
197
198 pub fn bundled(mut self) -> Self {
200 self.bundled = true;
201 self
202 }
203
204 pub fn no_bundled(mut self) -> Self {
206 self.bundled = false;
207 self
208 }
209
210 pub fn data(mut self, bytes: impl Into<Vec<u8>>) -> Self {
213 self.datas.push(bytes.into());
214 self
215 }
216
217 pub fn dir(mut self, p: impl Into<PathBuf>) -> Self {
219 self.dirs.push(p.into());
220 self
221 }
222
223 pub fn system(mut self) -> Self {
225 self.system = true;
226 self
227 }
228
229 pub fn build(self) -> Result<FontHandle> {
231 let mut collection =
232 Collection::new(CollectionOptions { shared: false, system_fonts: self.system });
233 let mut registered = false;
234 let mut register = |collection: &mut Collection, raw: Vec<u8>| {
235 registered |= !collection.register_fonts(Blob::from(raw), None).is_empty();
236 };
237 if self.bundled {
238 for z in BUNDLED {
239 register(&mut collection, unzstd(z)?);
240 }
241 }
242 for d in self.datas {
243 if d.starts_with(&ZSTD_MAGIC) {
244 register(&mut collection, unzstd(&d)?);
245 } else {
246 register(&mut collection, d);
247 }
248 }
249 let mut stack: Vec<PathBuf> = self.dirs.clone();
251 while let Some(dir) = stack.pop() {
252 let Ok(entries) = std::fs::read_dir(&dir) else { continue };
253 for e in entries.flatten() {
254 let p = e.path();
255 if p.is_dir() {
256 stack.push(p);
257 continue;
258 }
259 let is_font = p
260 .extension()
261 .and_then(|x| x.to_str())
262 .is_some_and(|x| matches!(x.to_ascii_lowercase().as_str(), "ttf" | "otf" | "ttc" | "otc"));
263 if is_font {
264 if let Ok(raw) = std::fs::read(&p) {
265 register(&mut collection, raw);
266 }
267 }
268 }
269 }
270 if !registered && !self.system {
271 return Err(Error::FontLoad("字体栈为空(未启用任何字体来源)".into()));
272 }
273 Ok(FontHandle::from_collection(collection))
274 }
275}
276
277fn unzstd(data: &[u8]) -> Result<Vec<u8>> {
279 use std::io::Read;
280 let mut dec = ruzstd::decoding::StreamingDecoder::new(data)
281 .map_err(|e| Error::FontLoad(format!("内置字体解压失败:{e}")))?;
282 let mut out = Vec::new();
283 dec.read_to_end(&mut out).map_err(|e| Error::FontLoad(format!("内置字体解压失败:{e}")))?;
284 Ok(out)
285}