tinymist_world/font/
resolver.rs

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