tinymist_world/font/
system.rs

1use std::borrow::Cow;
2use std::path::{Path, PathBuf};
3
4use fontdb::Database;
5use rayon::iter::{IntoParallelIterator, ParallelIterator};
6use tinymist_std::error::prelude::*;
7use tinymist_vfs::system::LazyFile;
8use typst::diag::{FileError, FileResult};
9use typst::foundations::Bytes;
10use typst::text::FontInfo;
11
12use super::memory::MemoryFontSearcher;
13use super::{FontResolverImpl, FontSlot, LazyBufferFontLoader};
14use crate::config::CompileFontOpts;
15use crate::debug_loc::{DataSource, FsDataSource};
16
17/// Searches for fonts in system.
18#[derive(Debug)]
19pub struct SystemFontSearcher {
20    /// The base font searcher.
21    base: MemoryFontSearcher,
22    /// Records user-specific font path when loading from directory or file for
23    /// debug.
24    pub font_paths: Vec<PathBuf>,
25    /// Stores font data loaded from file
26    db: Database,
27}
28
29impl SystemFontSearcher {
30    /// Creates a system searcher.
31    pub fn new() -> Self {
32        Self {
33            base: MemoryFontSearcher::default(),
34            font_paths: vec![],
35            db: Database::new(),
36        }
37    }
38
39    /// Builds a FontResolverImpl.
40    pub fn build(self) -> FontResolverImpl {
41        self.base.build().with_font_paths(self.font_paths)
42    }
43}
44
45impl SystemFontSearcher {
46    /// Resolve fonts from given options.
47    pub fn resolve_opts(&mut self, opts: CompileFontOpts) -> Result<()> {
48        // Note: the order of adding fonts is important.
49        // See: https://github.com/typst/typst/blob/9c7f31870b4e1bf37df79ebbe1df9a56df83d878/src/font/book.rs#L151-L154
50        // Source1: add the fonts specified by the user.
51        for path in opts.font_paths {
52            if path.is_dir() {
53                self.search_dir(&path);
54            } else {
55                let _ = self.search_file(&path);
56            }
57        }
58
59        // Source2: add the fonts from system paths.
60        if !opts.no_system_fonts {
61            self.search_system();
62        }
63
64        // Flush font db before adding fonts in memory
65        self.flush();
66
67        // Source3: add the fonts in memory.
68        self.add_memory_fonts(opts.with_embedded_fonts.into_par_iter().map(|font_data| {
69            match font_data {
70                Cow::Borrowed(data) => Bytes::new(data),
71                Cow::Owned(data) => Bytes::new(data),
72            }
73        }));
74
75        Ok(())
76    }
77
78    pub fn flush(&mut self) {
79        use fontdb::Source;
80
81        let face = self.db.faces().collect::<Vec<_>>();
82        let info = face.into_par_iter().flat_map(|face| {
83            let path = match &face.source {
84                Source::File(path) | Source::SharedFile(path, _) => path,
85                // We never add binary sources to the database, so there
86                // shouln't be any.
87                Source::Binary(_) => unreachable!(),
88            };
89
90            let info = self.db.with_face_data(face.id, FontInfo::new)??;
91            let slot = FontSlot::new(LazyBufferFontLoader::new(
92                LazyFile::new(path.clone()),
93                face.index,
94            ))
95            .with_describe(DataSource::Fs(FsDataSource {
96                path: path.to_str().unwrap_or_default().to_owned(),
97            }));
98
99            Some((info, slot))
100        });
101
102        self.base.extend(info.collect::<Vec<_>>());
103        self.db = Database::new();
104    }
105
106    /// Add an in-memory font.
107    pub fn add_memory_font(&mut self, data: Bytes) {
108        if !self.db.is_empty() {
109            panic!("dirty font search state, please flush the searcher before adding memory fonts");
110        }
111
112        self.base.add_memory_font(data);
113    }
114
115    /// Adds in-memory fonts.
116    pub fn add_memory_fonts(&mut self, data: impl ParallelIterator<Item = Bytes>) {
117        if !self.db.is_empty() {
118            panic!("dirty font search state, please flush the searcher before adding memory fonts");
119        }
120
121        self.base.add_memory_fonts(data);
122    }
123
124    pub fn search_system(&mut self) {
125        self.db.load_system_fonts();
126    }
127
128    fn record_path(&mut self, path: &Path) {
129        self.font_paths.push(if !path.is_relative() {
130            path.to_owned()
131        } else {
132            let current_dir = std::env::current_dir();
133            match current_dir {
134                Ok(current_dir) => current_dir.join(path),
135                Err(_) => path.to_owned(),
136            }
137        });
138    }
139
140    /// Search for all fonts in a directory recursively.
141    pub fn search_dir(&mut self, path: impl AsRef<Path>) {
142        self.record_path(path.as_ref());
143
144        self.db.load_fonts_dir(path);
145    }
146
147    /// Index the fonts in the file at the given path.
148    pub fn search_file(&mut self, path: impl AsRef<Path>) -> FileResult<()> {
149        self.record_path(path.as_ref());
150
151        self.db
152            .load_font_file(path.as_ref())
153            .map_err(|e| FileError::from_io(e, path.as_ref()))
154    }
155}
156
157impl Default for SystemFontSearcher {
158    fn default() -> Self {
159        Self::new()
160    }
161}
162
163#[cfg(test)]
164mod tests {
165
166    #[test]
167    fn edit_fonts() {
168        use clap::Parser as _;
169
170        use crate::args::CompileOnceArgs;
171
172        let args = CompileOnceArgs::parse_from(["tinymist", "main.typ"]);
173        let mut verse = args
174            .resolve_system()
175            .expect("failed to resolve system universe");
176
177        // todo: a good way to edit fonts
178        let new_fonts = verse.font_resolver.clone();
179
180        verse.increment_revision(|verse| verse.set_fonts(new_fonts));
181    }
182}