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 = cx.builder(font_ref).size(gf.size).hint(false).normalized_coords(&gf.coords).build();
111 let mut render = Render::new(&[Source::ColorOutline(0), Source::ColorBitmap(StrikeWith::BestFit), Source::Outline]);
112 render.format(Format::Alpha);
113 if let Some(deg) = gf.skew {
114 render.transform(Some(Transform::skew(Angle::from_degrees(deg), Angle::ZERO)));
115 }
116 if gf.embolden {
117 render.embolden(gf.size * 0.02);
118 }
119 let img = render.render(&mut scaler, glyph)?;
120 let (width, height) = (img.placement.width, img.placement.height);
121 let (color, data) = match img.content {
122 Content::Color => (true, img.data),
123 Content::Mask => (false, img.data),
124 Content::SubpixelMask => {
126 (false, img.data.chunks_exact(3).map(|c| ((c[0] as u16 + c[1] as u16 + c[2] as u16) / 3) as u8).collect())
127 }
128 };
129 Some(GlyphImage { left: img.placement.left, top: img.placement.top, width, height, color, data })
130}
131
132impl FontHandle {
133 pub fn builder() -> FontStackBuilder {
135 FontStackBuilder::new()
136 }
137
138 pub fn shared_default() -> FontHandle {
140 static DEFAULT: OnceLock<FontHandle> = OnceLock::new();
141 DEFAULT
142 .get_or_init(|| {
143 FontHandle::builder().bundled().system().build().unwrap_or_else(|_| {
144 FontHandle::from_collection(Collection::new(CollectionOptions {
145 shared: false,
146 system_fonts: true,
147 }))
148 })
149 })
150 .clone()
151 }
152
153 fn from_collection(collection: Collection) -> FontHandle {
154 let fonts = FontContext { collection, source_cache: Default::default() };
155 FontHandle(Arc::new(Inner {
156 fonts: Mutex::new(fonts),
157 layouts: Mutex::new(LayoutContext::new()),
158 raster: Mutex::new(RasterState { scale: ScaleContext::new(), cache: HashMap::new() }),
159 }))
160 }
161
162 pub(crate) fn with_layout<R>(&self, f: impl FnOnce(&mut FontContext, &mut LayoutContext<Ink>) -> R) -> R {
168 let mut fonts = self.0.fonts.lock().unwrap_or_else(|e| e.into_inner());
169 let mut layouts = self.0.layouts.lock().unwrap_or_else(|e| e.into_inner());
170 f(&mut fonts, &mut layouts)
171 }
172
173 pub(crate) fn with_raster<R>(&self, f: impl FnOnce(&mut GlyphRaster) -> R) -> R {
175 let mut raster = self.0.raster.lock().unwrap_or_else(|e| e.into_inner());
176 f(&mut GlyphRaster(&mut raster))
177 }
178}
179
180pub struct FontStackBuilder {
182 bundled: bool,
183 datas: Vec<Vec<u8>>,
184 dirs: Vec<PathBuf>,
185 system: bool,
186}
187
188impl FontStackBuilder {
189 fn new() -> Self {
190 Self { bundled: true, datas: Vec::new(), dirs: Vec::new(), system: false }
191 }
192
193 pub fn bundled(mut self) -> Self {
195 self.bundled = true;
196 self
197 }
198
199 pub fn no_bundled(mut self) -> Self {
201 self.bundled = false;
202 self
203 }
204
205 pub fn data(mut self, bytes: impl Into<Vec<u8>>) -> Self {
208 self.datas.push(bytes.into());
209 self
210 }
211
212 pub fn dir(mut self, p: impl Into<PathBuf>) -> Self {
214 self.dirs.push(p.into());
215 self
216 }
217
218 pub fn system(mut self) -> Self {
220 self.system = true;
221 self
222 }
223
224 pub fn build(self) -> Result<FontHandle> {
226 let mut collection = Collection::new(CollectionOptions { shared: false, system_fonts: self.system });
227 let mut registered = false;
228 let mut register = |collection: &mut Collection, raw: Vec<u8>| {
229 registered |= !collection.register_fonts(Blob::from(raw), None).is_empty();
230 };
231 if self.bundled {
232 for z in BUNDLED {
233 register(&mut collection, unzstd(z)?);
234 }
235 }
236 for d in self.datas {
237 if d.starts_with(&ZSTD_MAGIC) {
238 register(&mut collection, unzstd(&d)?);
239 } else {
240 register(&mut collection, d);
241 }
242 }
243 let mut stack: Vec<PathBuf> = self.dirs.clone();
245 while let Some(dir) = stack.pop() {
246 let Ok(entries) = std::fs::read_dir(&dir) else { continue };
247 for e in entries.flatten() {
248 let p = e.path();
249 if p.is_dir() {
250 stack.push(p);
251 continue;
252 }
253 let is_font = p
254 .extension()
255 .and_then(|x| x.to_str())
256 .is_some_and(|x| matches!(x.to_ascii_lowercase().as_str(), "ttf" | "otf" | "ttc" | "otc"));
257 if is_font {
258 if let Ok(raw) = std::fs::read(&p) {
259 register(&mut collection, raw);
260 }
261 }
262 }
263 }
264 if !registered && !self.system {
265 return Err(Error::FontLoad("字体栈为空(未启用任何字体来源)".into()));
266 }
267 Ok(FontHandle::from_collection(collection))
268 }
269}
270
271fn unzstd(data: &[u8]) -> Result<Vec<u8>> {
273 use std::io::Read;
274 let mut dec =
275 ruzstd::decoding::StreamingDecoder::new(data).map_err(|e| Error::FontLoad(format!("内置字体解压失败:{e}")))?;
276 let mut out = Vec::new();
277 dec.read_to_end(&mut out).map_err(|e| Error::FontLoad(format!("内置字体解压失败:{e}")))?;
278 Ok(out)
279}