reflexo_world/font/
resolver.rs

1use core::fmt;
2use std::{
3    collections::HashMap,
4    path::PathBuf,
5    sync::{Arc, Mutex},
6};
7
8use reflexo::debug_loc::DataSource;
9use typst::text::{Font, FontBook, FontInfo};
10use typst::utils::LazyHash;
11
12use super::{BufferFontLoader, FontProfile, FontSlot, PartialFontBook};
13use crate::Bytes;
14
15/// A FontResolver can resolve a font by index.
16/// It also reuse FontBook for font-related query.
17/// The index is the index of the font in the `FontBook.infos`.
18pub trait FontResolver {
19    fn font_book(&self) -> &LazyHash<FontBook>;
20    fn font(&self, idx: usize) -> Option<Font>;
21
22    fn default_get_by_info(&self, info: &FontInfo) -> Option<Font> {
23        // todo: font alternative
24        let mut alternative_text = 'c';
25        if let Some(codepoint) = info.coverage.iter().next() {
26            alternative_text = std::char::from_u32(codepoint).unwrap();
27        };
28
29        let idx = self
30            .font_book()
31            .select_fallback(Some(info), info.variant, &alternative_text.to_string())
32            .unwrap();
33        self.font(idx)
34    }
35    fn get_by_info(&self, info: &FontInfo) -> Option<Font> {
36        self.default_get_by_info(info)
37    }
38}
39
40#[derive(Debug)]
41/// The default FontResolver implementation.
42pub struct FontResolverImpl {
43    font_paths: Vec<PathBuf>,
44    book: LazyHash<FontBook>,
45    partial_book: Arc<Mutex<PartialFontBook>>,
46    fonts: Vec<FontSlot>,
47    profile: FontProfile,
48}
49
50impl FontResolverImpl {
51    pub fn new(
52        font_paths: Vec<PathBuf>,
53        book: FontBook,
54        partial_book: Arc<Mutex<PartialFontBook>>,
55        fonts: Vec<FontSlot>,
56        profile: FontProfile,
57    ) -> Self {
58        Self {
59            font_paths,
60            book: LazyHash::new(book),
61            partial_book,
62            fonts,
63            profile,
64        }
65    }
66
67    pub fn len(&self) -> usize {
68        self.fonts.len()
69    }
70
71    pub fn is_empty(&self) -> bool {
72        self.fonts.is_empty()
73    }
74
75    pub fn profile(&self) -> &FontProfile {
76        &self.profile
77    }
78
79    pub fn font_paths(&self) -> &[PathBuf] {
80        &self.font_paths
81    }
82
83    pub fn partial_resolved(&self) -> bool {
84        self.partial_book.lock().unwrap().partial_hit
85    }
86
87    pub fn loaded_fonts(&self) -> impl Iterator<Item = (usize, Font)> + '_ {
88        let slots_with_index = self.fonts.iter().enumerate();
89
90        slots_with_index.flat_map(|(idx, slot)| {
91            let maybe_font = slot.get_uninitialized().flatten();
92            maybe_font.map(|font| (idx, font))
93        })
94    }
95
96    pub fn describe_font(&self, font: &Font) -> Option<Arc<DataSource>> {
97        let f = Some(Some(font.clone()));
98        for slot in &self.fonts {
99            if slot.get_uninitialized() == f {
100                return slot.description.clone();
101            }
102        }
103        None
104    }
105
106    pub fn modify_font_data(&mut self, idx: usize, buffer: Bytes) {
107        let mut font_book = self.partial_book.lock().unwrap();
108        for (i, info) in FontInfo::iter(buffer.as_slice()).enumerate() {
109            let buffer = buffer.clone();
110            let modify_idx = if i > 0 { None } else { Some(idx) };
111
112            font_book.push((
113                modify_idx,
114                info,
115                FontSlot::new(Box::new(BufferFontLoader {
116                    buffer: Some(buffer),
117                    index: i as u32,
118                })),
119            ));
120        }
121    }
122
123    pub fn append_font(&mut self, info: FontInfo, slot: FontSlot) {
124        let mut font_book = self.partial_book.lock().unwrap();
125        font_book.push((None, info, slot));
126    }
127
128    pub fn rebuild(&mut self) {
129        let mut partial_book = self.partial_book.lock().unwrap();
130        if !partial_book.partial_hit {
131            return;
132        }
133        partial_book.revision += 1;
134
135        let mut book = FontBook::default();
136
137        let mut font_changes = HashMap::new();
138        let mut new_fonts = vec![];
139        for (idx, info, slot) in partial_book.changes.drain(..) {
140            if let Some(idx) = idx {
141                font_changes.insert(idx, (info, slot));
142            } else {
143                new_fonts.push((info, slot));
144            }
145        }
146        partial_book.changes.clear();
147        partial_book.partial_hit = false;
148
149        let mut font_slots = Vec::new();
150        font_slots.append(&mut self.fonts);
151        self.fonts.clear();
152
153        for (i, slot_ref) in font_slots.iter_mut().enumerate() {
154            let (info, slot) = if let Some((_, v)) = font_changes.remove_entry(&i) {
155                v
156            } else {
157                book.push(self.book.info(i).unwrap().clone());
158                continue;
159            };
160
161            book.push(info);
162            *slot_ref = slot;
163        }
164
165        for (info, slot) in new_fonts.drain(..) {
166            book.push(info);
167            font_slots.push(slot);
168        }
169
170        self.book = LazyHash::new(book);
171        self.fonts = font_slots;
172    }
173
174    pub fn add_glyph_packs(&mut self) {
175        todo!()
176    }
177}
178
179impl FontResolver for FontResolverImpl {
180    fn font_book(&self) -> &LazyHash<FontBook> {
181        &self.book
182    }
183
184    fn font(&self, idx: usize) -> Option<Font> {
185        self.fonts[idx].get_or_init()
186    }
187
188    fn get_by_info(&self, info: &FontInfo) -> Option<Font> {
189        FontResolver::default_get_by_info(self, info)
190    }
191}
192
193impl fmt::Display for FontResolverImpl {
194    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
195        for (idx, slot) in self.fonts.iter().enumerate() {
196            writeln!(f, "{:?} -> {:?}", idx, slot.get_uninitialized())?;
197        }
198
199        Ok(())
200    }
201}