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
80pub trait ReusableFontResolver: FontResolver {
81    /// Reuses the font resolver.
82    fn slots(&self) -> impl Iterator<Item = FontSlot>;
83}
84
85impl<T: ReusableFontResolver> ReusableFontResolver for Arc<T> {
86    fn slots(&self) -> impl Iterator<Item = FontSlot> {
87        self.as_ref().slots()
88    }
89}
90
91/// The default FontResolver implementation.
92///
93/// This is constructed by:
94/// - The [`crate::font::system::SystemFontSearcher`] on operating systems.
95/// - The [`crate::font::web::BrowserFontSearcher`] on browsers.
96/// - Otherwise, [`crate::font::pure::MemoryFontBuilder`] in memory.
97#[derive(Debug)]
98pub struct FontResolverImpl {
99    pub(crate) font_paths: Vec<PathBuf>,
100    pub(crate) book: LazyHash<FontBook>,
101    pub(crate) slots: Vec<FontSlot>,
102}
103
104impl FontResolverImpl {
105    /// Creates a new font resolver.
106    pub fn new(font_paths: Vec<PathBuf>, book: FontBook, slots: Vec<FontSlot>) -> Self {
107        Self {
108            font_paths,
109            book: LazyHash::new(book),
110            slots,
111        }
112    }
113
114    pub fn new_with_fonts(
115        font_paths: Vec<PathBuf>,
116        fonts: impl Iterator<Item = (FontInfo, FontSlot)>,
117    ) -> Self {
118        let mut book = FontBook::new();
119        let mut slots = Vec::<FontSlot>::new();
120
121        for (info, slot) in fonts {
122            book.push(info);
123            slots.push(slot);
124        }
125
126        Self {
127            font_paths,
128            book: LazyHash::new(book),
129            slots,
130        }
131    }
132
133    /// Gets the number of fonts in the resolver.
134    pub fn len(&self) -> usize {
135        self.slots.len()
136    }
137
138    /// Tests whether the resolver doesn't hold any fonts.
139    pub fn is_empty(&self) -> bool {
140        self.slots.is_empty()
141    }
142
143    /// Gets the user-specified font paths.
144    pub fn font_paths(&self) -> &[PathBuf] {
145        &self.font_paths
146    }
147
148    /// Returns an iterator over all fonts in the resolver.
149    #[deprecated(note = "use `fonts` instead")]
150    pub fn get_fonts(&self) -> impl Iterator<Item = (&FontInfo, &FontSlot)> {
151        self.fonts()
152    }
153
154    /// Returns an iterator over all fonts in the resolver.
155    pub fn fonts(&self) -> impl Iterator<Item = (&FontInfo, &FontSlot)> {
156        self.slots.iter().enumerate().map(|(idx, slot)| {
157            let info = self.book.info(idx).unwrap();
158            (info, slot)
159        })
160    }
161
162    /// Returns an iterator over all loaded fonts in the resolver.
163    pub fn loaded_fonts(&self) -> impl Iterator<Item = (usize, Font)> + '_ {
164        self.slots.iter().enumerate().flat_map(|(idx, slot)| {
165            let maybe_font = slot.get_uninitialized().flatten();
166            maybe_font.map(|font| (idx, font))
167        })
168    }
169
170    /// Describes the source of a font.
171    pub fn describe_font(&self, font: &Font) -> Option<Arc<DataSource>> {
172        let f = Some(Some(font.clone()));
173        for slot in &self.slots {
174            if slot.get_uninitialized() == f {
175                return slot.description.clone();
176            }
177        }
178        None
179    }
180
181    /// Describes the source of a font by index.
182    pub fn describe_font_by_id(&self, index: usize) -> Option<Arc<DataSource>> {
183        self.slot(index)?.description.clone()
184    }
185
186    pub fn with_font_paths(mut self, font_paths: Vec<PathBuf>) -> Self {
187        self.font_paths = font_paths;
188        self
189    }
190}
191
192impl FontResolver for FontResolverImpl {
193    fn font_book(&self) -> &LazyHash<FontBook> {
194        &self.book
195    }
196
197    fn slot(&self, idx: usize) -> Option<&FontSlot> {
198        self.slots.get(idx)
199    }
200
201    fn font(&self, index: usize) -> Option<Font> {
202        self.slot(index)?.get_or_init()
203    }
204
205    fn get_by_info(&self, info: &FontInfo) -> Option<Font> {
206        FontResolver::default_get_by_info(self, info)
207    }
208}
209
210impl fmt::Display for FontResolverImpl {
211    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
212        for (idx, slot) in self.slots.iter().enumerate() {
213            writeln!(f, "{:?} -> {:?}", idx, slot.get_uninitialized())?;
214        }
215
216        Ok(())
217    }
218}
219
220impl ReusableFontResolver for FontResolverImpl {
221    fn slots(&self) -> impl Iterator<Item = FontSlot> {
222        self.slots.iter().cloned()
223    }
224}