tinymist_world/font/
system.rs1use 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#[derive(Debug)]
19pub struct SystemFontSearcher {
20 base: MemoryFontSearcher,
22 pub font_paths: Vec<PathBuf>,
25 db: Database,
27}
28
29impl SystemFontSearcher {
30 pub fn new() -> Self {
32 Self {
33 base: MemoryFontSearcher::default(),
34 font_paths: vec![],
35 db: Database::new(),
36 }
37 }
38
39 pub fn build(self) -> FontResolverImpl {
41 self.base.build().with_font_paths(self.font_paths)
42 }
43}
44
45impl SystemFontSearcher {
46 pub fn resolve_opts(&mut self, opts: CompileFontOpts) -> Result<()> {
48 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 if !opts.no_system_fonts {
61 self.search_system();
62 }
63
64 self.flush();
66
67 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 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 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 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 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 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 let new_fonts = verse.font_resolver.clone();
179
180 verse.increment_revision(|verse| verse.set_fonts(new_fonts));
181 }
182}