Skip to main content

mathtex_engine/
resource_font.rs

1use std::cell::RefCell;
2use std::collections::BTreeMap;
3
4use mathtex_font::{FontData, FontError, FontKey, FontLoader, FontSpec};
5
6use crate::resource::{ResourceError, ResourceKind, ResourceProvider};
7
8/// Loads font files through a [`ResourceProvider`], one key per file in load order.
9#[derive(Debug)]
10pub struct ResourceFontLoader<R> {
11    resources: R,
12    loaded_fonts: RefCell<BTreeMap<String, FontData>>,
13}
14
15impl<R> ResourceFontLoader<R> {
16    /// Creates a loader backed by the given resource provider.
17    #[must_use]
18    pub fn new(resources: R) -> Self {
19        Self {
20            resources,
21            loaded_fonts: RefCell::new(BTreeMap::new()),
22        }
23    }
24
25    /// Returns a reference to the underlying resource provider.
26    #[must_use]
27    pub fn resources(&self) -> &R {
28        &self.resources
29    }
30
31    /// Number of font files loaded so far.
32    #[must_use]
33    pub fn cached_font_count(&self) -> usize {
34        self.loaded_fonts.borrow().len()
35    }
36
37    /// Returns the loaded font with the given key, so hosts can draw what the engine laid out.
38    #[must_use]
39    pub fn font(&self, key: FontKey) -> Option<FontData> {
40        self.loaded_fonts
41            .borrow()
42            .values()
43            .find(|font| font.key == key)
44            .cloned()
45    }
46}
47
48impl<R> FontLoader for ResourceFontLoader<R>
49where
50    R: ResourceProvider,
51{
52    fn load(&self, spec: &FontSpec) -> Result<FontData, FontError> {
53        let mut last_error = None;
54        for name in spec.file_candidates() {
55            if let Some(font) = self.loaded_fonts.borrow().get(&name) {
56                return Ok(font.clone());
57            }
58            match self.resources.read(&name, ResourceKind::Font) {
59                Ok(resource) => {
60                    let mut fonts = self.loaded_fonts.borrow_mut();
61                    let key = FontKey(fonts.len() as u64 + 1);
62                    let font = FontData::new(key, resource.bytes);
63                    fonts.insert(name, font.clone());
64                    return Ok(font);
65                }
66                Err(error) => last_error = Some(error),
67            }
68        }
69        Err(match last_error {
70            Some(ResourceError::NotFound { .. }) | None => FontError::NotFound {
71                name: spec.name().into(),
72            },
73            Some(error) => FontError::Invalid {
74                name: spec.name().into(),
75                message: error.to_string(),
76            },
77        })
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::InMemoryResourceProvider;
85    use mathtex_ir::Length;
86
87    #[test]
88    fn resource_font_loader_caches_files_and_resolves_extensionless_names() {
89        let resources = InMemoryResourceProvider::new()
90            .with_resource("latinmodern-math.otf", ResourceKind::Font, b"math")
91            .with_resource("lmroman10-regular.otf", ResourceKind::Font, b"text");
92        let fonts = ResourceFontLoader::new(resources);
93        let spec = |name: &str| FontSpec::parse(name, Length(10 * 65_536));
94
95        let math = fonts
96            .load(&spec("[latinmodern-math.otf]:script=math"))
97            .expect("file spec");
98        assert_eq!(&**math.bytes().expect("owned bytes"), b"math");
99        let again = fonts
100            .load(&spec("[latinmodern-math.otf]:script=math;+ssty=0"))
101            .expect("cached file");
102        assert_eq!(again, math);
103        let text = fonts
104            .load(&spec("lmroman10-regular:mapping=tex-text"))
105            .expect("extensionless name");
106        assert_ne!(text.key, math.key);
107        assert_eq!(fonts.cached_font_count(), 2);
108        assert_eq!(fonts.font(text.key), Some(text));
109
110        assert_eq!(
111            fonts.load(&spec("[missing.otf]")),
112            Err(FontError::NotFound {
113                name: "missing.otf".into()
114            })
115        );
116    }
117}