mathtex_engine/
resource_font.rs1use 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#[derive(Debug)]
10pub struct ResourceFontLoader<R> {
11 resources: R,
12 loaded_fonts: RefCell<BTreeMap<String, FontData>>,
13}
14
15impl<R> ResourceFontLoader<R> {
16 #[must_use]
18 pub fn new(resources: R) -> Self {
19 Self {
20 resources,
21 loaded_fonts: RefCell::new(BTreeMap::new()),
22 }
23 }
24
25 #[must_use]
27 pub fn resources(&self) -> &R {
28 &self.resources
29 }
30
31 #[must_use]
33 pub fn cached_font_count(&self) -> usize {
34 self.loaded_fonts.borrow().len()
35 }
36
37 #[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}