tinymist_world/font/
resolver.rs

1use core::fmt;
2use std::{num::NonZeroUsize, path::PathBuf, sync::Arc};
3
4use typst::text::{Font, FontBook, FontInfo};
5use typst::utils::LazyHash;
6
7use super::FontSlot;
8use crate::debug_loc::DataSource;
9
10/// A [`FontResolver`] can resolve a font by index.
11/// It also provides FontBook for typst to query fonts.
12pub trait FontResolver {
13    /// An optionally implemented revision function for users, e.g. the `World`.
14    ///
15    /// A user of [`FontResolver`] will differentiate the `prev` and `next`
16    /// revisions to determine if the underlying state of fonts has changed.
17    ///
18    /// - If either `prev` or `next` is `None`, the world's revision is always
19    ///   increased.
20    /// - Otherwise, the world's revision is increased if `prev != next`.
21    ///
22    /// If the revision of fonts is changed, the world will invalidate all
23    /// related caches and increase its revision.
24    fn revision(&self) -> Option<NonZeroUsize> {
25        None
26    }
27
28    /// The font book interface for typst.
29    fn font_book(&self) -> &LazyHash<FontBook>;
30
31    /// Gets the font slot by index.
32    /// The index parameter is the index of the font in the `FontBook.infos`.
33    fn slot(&self, index: usize) -> Option<&FontSlot>;
34
35    /// Gets the font by index.
36    /// The index parameter is the index of the font in the `FontBook.infos`.
37    fn font(&self, index: usize) -> Option<Font>;
38
39    /// Gets a font by its info.
40    fn get_by_info(&self, info: &FontInfo) -> Option<Font> {
41        self.default_get_by_info(info)
42    }
43
44    /// The default implementation of [`FontResolver::get_by_info`].
45    fn default_get_by_info(&self, info: &FontInfo) -> Option<Font> {
46        // The selected font should at least has the first codepoint in the
47        // coverage. We achieve it by querying the font book with `alternative_text`.
48        // todo: better font alternative
49        let mut alternative_text = 'c';
50        if let Some(codepoint) = info.coverage.iter().next() {
51            alternative_text = std::char::from_u32(codepoint).unwrap();
52        };
53
54        let index = self
55            .font_book()
56            .select_fallback(Some(info), info.variant, &alternative_text.to_string())
57            .unwrap();
58        self.font(index)
59    }
60}
61
62impl<T: FontResolver> FontResolver for Arc<T> {
63    fn font_book(&self) -> &LazyHash<FontBook> {
64        self.as_ref().font_book()
65    }
66
67    fn slot(&self, index: usize) -> Option<&FontSlot> {
68        self.as_ref().slot(index)
69    }
70
71    fn font(&self, index: usize) -> Option<Font> {
72        self.as_ref().font(index)
73    }
74
75    fn get_by_info(&self, info: &FontInfo) -> Option<Font> {
76        self.as_ref().get_by_info(info)
77    }
78}
79
80/// A reusable font resolver.
81pub trait ReusableFontResolver: FontResolver {
82    /// Reuses the font resolver.
83    fn slots(&self) -> impl Iterator<Item = FontSlot>;
84}
85
86impl<T: ReusableFontResolver> ReusableFontResolver for Arc<T> {
87    fn slots(&self) -> impl Iterator<Item = FontSlot> {
88        self.as_ref().slots()
89    }
90}
91
92/// The default FontResolver implementation.
93///
94/// This is constructed by:
95/// - The [`crate::font::system::SystemFontSearcher`] on operating systems.
96/// - The [`crate::font::web::BrowserFontSearcher`] on browsers.
97/// - Otherwise, [`crate::font::pure::MemoryFontBuilder`] in memory.
98#[derive(Debug)]
99pub struct FontResolverImpl {
100    /// The user-specified font paths.
101    pub(crate) font_paths: Vec<PathBuf>,
102    /// The font book.
103    pub(crate) book: LazyHash<FontBook>,
104    /// The slots of the font resolver.
105    pub(crate) slots: Vec<FontSlot>,
106}
107
108impl FontResolverImpl {
109    /// Creates a new font resolver.
110    pub fn new(font_paths: Vec<PathBuf>, book: FontBook, slots: Vec<FontSlot>) -> Self {
111        Self {
112            font_paths,
113            book: LazyHash::new(book),
114            slots,
115        }
116    }
117
118    /// Creates a new font resolver with fonts.
119    pub fn new_with_fonts(
120        font_paths: Vec<PathBuf>,
121        fonts: impl Iterator<Item = (FontInfo, FontSlot)>,
122    ) -> Self {
123        let mut book = FontBook::new();
124        let mut slots = Vec::<FontSlot>::new();
125
126        for (info, slot) in fonts {
127            book.push(info);
128            slots.push(slot);
129        }
130
131        Self {
132            font_paths,
133            book: LazyHash::new(book),
134            slots,
135        }
136    }
137
138    /// Gets the number of fonts in the resolver.
139    pub fn len(&self) -> usize {
140        self.slots.len()
141    }
142
143    /// Checks if the resolver holds any fonts.
144    pub fn is_empty(&self) -> bool {
145        self.slots.is_empty()
146    }
147
148    /// Gets the user-specified font paths.
149    pub fn font_paths(&self) -> &[PathBuf] {
150        &self.font_paths
151    }
152
153    /// Returns an iterator over all fonts in the resolver.
154    #[deprecated(note = "use `fonts` instead")]
155    pub fn get_fonts(&self) -> impl Iterator<Item = (&FontInfo, &FontSlot)> {
156        self.fonts()
157    }
158
159    /// Returns an iterator over all fonts in the resolver.
160    pub fn fonts(&self) -> impl Iterator<Item = (&FontInfo, &FontSlot)> {
161        self.slots.iter().enumerate().map(|(idx, slot)| {
162            let info = self.book.info(idx).unwrap();
163            (info, slot)
164        })
165    }
166
167    /// Returns an iterator over all loaded fonts in the resolver.
168    pub fn loaded_fonts(&self) -> impl Iterator<Item = (usize, Font)> + '_ {
169        self.slots.iter().enumerate().flat_map(|(idx, slot)| {
170            let maybe_font = slot.get_uninitialized().flatten();
171            maybe_font.map(|font| (idx, font))
172        })
173    }
174
175    /// Describes the source of a font.
176    pub fn describe_font(&self, font: &Font) -> Option<Arc<DataSource>> {
177        let f = Some(Some(font.clone()));
178        for slot in &self.slots {
179            if slot.get_uninitialized() == f {
180                return slot.description.clone();
181            }
182        }
183        None
184    }
185
186    /// Describes the source of a font by index.
187    pub fn describe_font_by_id(&self, index: usize) -> Option<Arc<DataSource>> {
188        self.slot(index)?.description.clone()
189    }
190
191    /// Sets the user-specified font paths.
192    pub fn with_font_paths(mut self, font_paths: Vec<PathBuf>) -> Self {
193        self.font_paths = font_paths;
194        self
195    }
196}
197
198impl FontResolver for FontResolverImpl {
199    fn font_book(&self) -> &LazyHash<FontBook> {
200        &self.book
201    }
202
203    fn slot(&self, idx: usize) -> Option<&FontSlot> {
204        self.slots.get(idx)
205    }
206
207    fn font(&self, index: usize) -> Option<Font> {
208        self.slot(index)?.get_or_init()
209    }
210
211    fn get_by_info(&self, info: &FontInfo) -> Option<Font> {
212        FontResolver::default_get_by_info(self, info)
213    }
214}
215
216impl fmt::Display for FontResolverImpl {
217    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
218        for (idx, slot) in self.slots.iter().enumerate() {
219            writeln!(f, "{:?} -> {:?}", idx, slot.get_uninitialized())?;
220        }
221
222        Ok(())
223    }
224}
225
226impl ReusableFontResolver for FontResolverImpl {
227    /// Returns an iterator over all slots in the resolver.
228    fn slots(&self) -> impl Iterator<Item = FontSlot> {
229        self.slots.iter().cloned()
230    }
231}