fontkit/
lib.rs

1use arc_swap::ArcSwap;
2use std::collections::HashSet;
3#[cfg(feature = "parse")]
4use std::path::Path;
5use std::sync::atomic::{AtomicU32, Ordering};
6use std::sync::Arc;
7pub use ttf_parser::LineMetrics;
8
9#[cfg(all(target_arch = "wasm32", feature = "wit"))]
10mod bindings;
11mod conv;
12mod error;
13mod font;
14#[cfg(feature = "metrics")]
15mod metrics;
16#[cfg(feature = "ras")]
17mod ras;
18#[cfg(all(target_arch = "wasm32", feature = "wit"))]
19mod wit;
20
21pub use error::Error;
22pub use font::*;
23#[cfg(feature = "metrics")]
24pub use metrics::*;
25#[cfg(feature = "ras")]
26pub use ras::*;
27pub use tiny_skia_path::{self, PathSegment};
28
29#[cfg(all(target_arch = "wasm32", feature = "wit"))]
30pub use bindings::exports::alibaba::fontkit::fontkit_interface::TextMetrics;
31
32#[cfg(target_arch = "wasm32")]
33#[global_allocator]
34static ALLOCATOR: talc::TalckWasm = unsafe { talc::TalckWasm::new_global() };
35
36struct Config {
37    pub lru_limit: u32,
38    pub cache_path: Option<String>,
39}
40
41pub struct FontKit {
42    fonts: dashmap::DashMap<font::FontKey, Font>,
43    fallback_font_key: Box<dyn Fn(font::FontKey) -> Option<font::FontKey> + Send + Sync>,
44    pub(crate) config: ArcSwap<Config>,
45    hit_counter: Arc<AtomicU32>,
46}
47
48impl FontKit {
49    /// Create a font registry
50    pub fn new() -> Self {
51        FontKit {
52            fonts: dashmap::DashMap::new(),
53            fallback_font_key: Box::new(|_| None),
54            config: ArcSwap::new(Arc::new(Config {
55                lru_limit: 0,
56                cache_path: None,
57            })),
58            hit_counter: Arc::default(),
59        }
60    }
61
62    pub fn len(&self) -> usize {
63        self.fonts.len()
64    }
65
66    /// Setup a font as fallback. When measure fails, FontKit will use this
67    /// fallback to measure, if possible
68    pub fn set_fallback(
69        &mut self,
70        callback: impl Fn(font::FontKey) -> Option<font::FontKey> + Send + Sync + 'static,
71    ) {
72        self.fallback_font_key = Box::new(callback);
73    }
74
75    #[cfg(feature = "metrics")]
76    pub fn measure(&self, font_key: &font::FontKey, text: &str) -> Option<metrics::TextMetrics> {
77        let mut used_keys = HashSet::new();
78        let mut current_key = font_key.clone();
79        let mut current_metrics: Option<metrics::TextMetrics> = None;
80        used_keys.insert(current_key.clone());
81        loop {
82            match self
83                .query(&current_key)
84                .and_then(|font| font.measure(text).ok())
85            {
86                Some(metrics) => {
87                    if let Some(m) = current_metrics.as_ref() {
88                        m.replace(metrics, true);
89                    } else {
90                        current_metrics = Some(metrics);
91                    }
92                }
93                None => {}
94            }
95            if !current_metrics
96                .as_ref()
97                .map(|m| m.has_missing())
98                .unwrap_or(true)
99            {
100                break;
101            }
102            let callback = self.fallback_font_key.as_ref();
103            match (callback)(current_key) {
104                Some(next_key) => {
105                    if used_keys.contains(&next_key) {
106                        break;
107                    } else {
108                        used_keys.insert(next_key.clone());
109                        current_key = next_key;
110                    }
111                }
112                None => break,
113            }
114        }
115        current_metrics
116    }
117
118    pub fn remove(&self, key: font::FontKey) {
119        self.fonts.remove(&key);
120    }
121
122    pub fn buffer_size(&self) -> usize {
123        self.fonts
124            .iter()
125            .map(|font| font.buffer_size())
126            .sum::<usize>()
127    }
128
129    pub fn check_lru(&self) {
130        let limit = self.config.load().lru_limit as usize * 1024;
131        if limit == 0 {
132            return;
133        }
134        let mut current_size = self.buffer_size();
135        let mut loaded_fonts = self.fonts.iter().filter(|f| f.buffer_size() > 0).count();
136        while current_size > limit && loaded_fonts > 1 {
137            let font = self
138                .fonts
139                .iter()
140                .filter(|f| f.buffer_size() > 0)
141                .min_by(|a, b| {
142                    b.hit_index
143                        .load(Ordering::SeqCst)
144                        .cmp(&a.hit_index.load(Ordering::SeqCst))
145                });
146
147            let hit_index = font
148                .as_ref()
149                .map(|f| f.hit_index.load(Ordering::SeqCst))
150                .unwrap_or(0);
151            if let Some(f) = font {
152                f.unload();
153            }
154            if current_size == self.buffer_size() {
155                break;
156            }
157            current_size = self.buffer_size();
158            self.hit_counter.fetch_sub(hit_index, Ordering::SeqCst);
159            for f in self.fonts.iter() {
160                f.hit_index.fetch_sub(hit_index, Ordering::SeqCst);
161            }
162            loaded_fonts = self.fonts.iter().filter(|f| f.buffer_size() > 0).count();
163        }
164    }
165
166    /// Add fonts from a buffer. This will load the fonts and store the buffer
167    /// in FontKit. Type information is inferred from the magic number using
168    /// `infer` crate.
169    #[cfg(feature = "parse")]
170    pub fn add_font_from_buffer(&self, buffer: Vec<u8>) -> Result<(), Error> {
171        use std::fs::File;
172        use std::io::Write;
173        use std::path::PathBuf;
174        use std::str::FromStr;
175
176        let mut font = Font::from_buffer(buffer.clone(), self.hit_counter.clone())?;
177        let key = font.first_key();
178        if let Some(v) = self.fonts.get(&key) {
179            if let Some(path) = v.path().cloned() {
180                font.set_path(path);
181            }
182        }
183        let cache_path = self.config.load().cache_path.clone();
184        if let Some(mut path) = cache_path.and_then(|p| PathBuf::from_str(&p).ok()) {
185            if let Some(original_path) = font.path() {
186                let relative_path = if original_path.is_absolute()
187                    && !std::fs::exists(original_path.clone()).unwrap_or(false)
188                {
189                    format!(".{}", original_path.display())
190                } else {
191                    format!("{}", original_path.display())
192                };
193                path.push(relative_path);
194                let mut dir = path.clone();
195                dir.pop();
196                std::fs::create_dir_all(dir)?;
197            } else {
198                path.push(format!(
199                    "{}_{}_{}_{}.ttf",
200                    key.family.replace(['.', ' '], "_"),
201                    key.italic.unwrap_or_default(),
202                    key.stretch.unwrap_or(5),
203                    key.weight.unwrap_or(400)
204                ));
205            }
206            let mut f = File::create(&path)?;
207            f.write_all(&buffer)?;
208            font.set_path(path);
209            font.unload();
210        }
211        self.fonts.insert(key, font);
212        self.check_lru();
213        Ok(())
214    }
215
216    /// Recursively scan a local path for fonts, this method will not store the
217    /// font buffer to reduce memory consumption
218    #[cfg(feature = "parse")]
219    pub fn search_fonts_from_path(&self, path: impl AsRef<Path>) -> Result<(), Error> {
220        use std::io::Read;
221        // if path.as_ref().is_dir() {
222        //     let mut result = vec![];
223        //     if let Ok(data) = fs::read_dir(path) {
224        //         for entry in data {
225        //             if let Ok(entry) = entry {
226        //
227        // result.extend(load_font_from_path(&entry.path()).into_iter().flatten());
228        //             }
229        //         }
230        //     }
231        //     return Some(result);
232        // }
233
234        let mut buffer = Vec::new();
235        let path = path.as_ref();
236        let ext = path
237            .extension()
238            .and_then(|s| s.to_str())
239            .map(|s| s.to_lowercase());
240        let ext = ext.as_deref();
241        let ext = match ext {
242            Some(e) => e,
243            None => return Ok(()),
244        };
245        match ext {
246            "ttf" | "otf" | "ttc" | "woff2" | "woff" => {
247                let mut file = std::fs::File::open(&path).unwrap();
248                buffer.clear();
249                file.read_to_end(&mut buffer).unwrap();
250                let mut font = match Font::from_buffer(buffer, self.hit_counter.clone()) {
251                    Ok(f) => f,
252                    Err(e) => {
253                        log::warn!("Failed loading font {:?}: {:?}", path, e);
254                        return Ok(());
255                    }
256                };
257                font.set_path(path.to_path_buf());
258                font.unload();
259                self.fonts.insert(font.first_key(), font);
260                self.check_lru();
261            }
262            _ => {}
263        }
264        Ok(())
265    }
266
267    pub fn exact_match(&self, key: &font::FontKey) -> Option<StaticFace> {
268        let face = self.query(key)?;
269        let mut patched_key = key.clone();
270        if patched_key.weight.is_none() {
271            patched_key.weight = Some(400);
272        }
273        if patched_key.stretch.is_none() {
274            patched_key.stretch = Some(5);
275        }
276        if patched_key.italic.is_none() {
277            patched_key.italic = Some(false);
278        }
279        if face.key() == patched_key {
280            return Some(face);
281        } else {
282            return None;
283        }
284    }
285
286    pub fn query(&self, key: &font::FontKey) -> Option<StaticFace> {
287        let result = self.fonts.get(&self.query_font(key)?)?.face(key).ok();
288        self.check_lru();
289        result
290    }
291
292    pub(crate) fn query_font(&self, key: &font::FontKey) -> Option<font::FontKey> {
293        let mut search_results = self
294            .fonts
295            .iter()
296            .map(|item| item.key().clone())
297            .collect::<HashSet<_>>();
298        let filters = Filter::from_key(key);
299        for filter in filters {
300            let mut s = search_results.clone();
301            let is_family = if let Filter::Family(_) = filter {
302                true
303            } else {
304                false
305            };
306            s.retain(|key| {
307                let font = self.fonts.get(key).unwrap();
308                font.fulfils(&filter)
309            });
310            match s.len() {
311                1 => return s.iter().next().cloned(),
312                0 if is_family => return None,
313                0 => {}
314                _ => search_results = s,
315            }
316        }
317        None
318    }
319
320    pub fn keys(&self) -> Vec<FontKey> {
321        self.fonts
322            .iter()
323            .flat_map(|i| {
324                i.value()
325                    .variants()
326                    .iter()
327                    .map(|i| i.key.clone())
328                    .collect::<Vec<_>>()
329            })
330            .collect()
331    }
332}
333
334enum Filter<'a> {
335    Family(&'a str),
336    Italic(bool),
337    Weight(u16),
338    Stretch(u16),
339    Variations(&'a Vec<(String, f32)>),
340}
341
342impl<'a> Filter<'a> {
343    pub fn from_key(key: &'a FontKey) -> Vec<Filter<'a>> {
344        let mut filters = vec![Filter::Family(&key.family)];
345        if let Some(italic) = key.italic {
346            filters.push(Filter::Italic(italic));
347        }
348        if let Some(weight) = key.weight {
349            filters.push(Filter::Weight(weight));
350        }
351        if let Some(stretch) = key.stretch {
352            filters.push(Filter::Stretch(stretch));
353        }
354
355        filters.push(Filter::Variations(&key.variations));
356
357        // Fallback weight logic
358        filters.push(Filter::Weight(0));
359        filters
360    }
361}