tinymist_world/font/
system.rs1use std::borrow::Cow;
4use std::path::{Path, PathBuf};
5
6use fontdb::Database;
7use rayon::iter::{IntoParallelIterator, ParallelIterator};
8use tinymist_std::error::prelude::*;
9use tinymist_vfs::system::LazyFile;
10use typst::diag::{FileError, FileResult};
11use typst::foundations::Bytes;
12use typst::text::FontInfo;
13
14use super::memory::MemoryFontSearcher;
15use super::{FontResolverImpl, FontSlot, LazyBufferFontLoader};
16use crate::config::CompileFontOpts;
17use crate::debug_loc::{DataSource, FsDataSource};
18
19#[derive(Debug)]
21pub struct SystemFontSearcher {
22 base: MemoryFontSearcher,
24 pub font_paths: Vec<PathBuf>,
27 db: Database,
29}
30
31impl SystemFontSearcher {
32 pub fn new() -> Self {
34 Self {
35 base: MemoryFontSearcher::default(),
36 font_paths: vec![],
37 db: Database::new(),
38 }
39 }
40
41 pub fn build(self) -> FontResolverImpl {
43 self.base.build().with_font_paths(self.font_paths)
44 }
45}
46
47impl SystemFontSearcher {
48 pub fn resolve_opts(&mut self, opts: CompileFontOpts) -> Result<()> {
50 for path in opts.font_paths {
54 if path.is_dir() {
55 self.search_dir(&path);
56 } else {
57 let _ = self.search_file(&path);
58 }
59 }
60
61 if !opts.no_system_fonts {
63 self.search_system();
64 }
65
66 self.flush();
68
69 self.add_memory_fonts(opts.with_embedded_fonts.into_par_iter().map(|font_data| {
71 match font_data {
72 Cow::Borrowed(data) => Bytes::new(data),
73 Cow::Owned(data) => Bytes::new(data),
74 }
75 }));
76
77 Ok(())
78 }
79
80 pub fn flush(&mut self) {
82 use fontdb::Source;
83
84 let face = self.db.faces().collect::<Vec<_>>();
85 let info = face.into_par_iter().flat_map(|face| {
86 let path = match &face.source {
87 Source::File(path) | Source::SharedFile(path, _) => path,
88 Source::Binary(_) => unreachable!(),
91 };
92
93 let info = self.db.with_face_data(face.id, FontInfo::new)??;
94 let slot = FontSlot::new(LazyBufferFontLoader::new(
95 LazyFile::new(path.clone()),
96 face.index,
97 ))
98 .with_describe(DataSource::Fs(FsDataSource {
99 path: path.to_str().unwrap_or_default().to_owned(),
100 }));
101
102 Some((info, slot))
103 });
104
105 self.base.extend(info.collect::<Vec<_>>());
106 self.db = Database::new();
107 }
108
109 pub fn add_memory_font(&mut self, data: Bytes) {
111 if !self.db.is_empty() {
112 panic!("dirty font search state, please flush the searcher before adding memory fonts");
113 }
114
115 self.base.add_memory_font(data);
116 }
117
118 pub fn add_memory_fonts(&mut self, data: impl ParallelIterator<Item = Bytes>) {
120 if !self.db.is_empty() {
121 panic!("dirty font search state, please flush the searcher before adding memory fonts");
122 }
123
124 self.base.add_memory_fonts(data);
125 }
126
127 pub fn search_system(&mut self) {
129 self.db.load_system_fonts();
130 }
131
132 fn record_path(&mut self, path: &Path) {
134 self.font_paths.push(if !path.is_relative() {
135 path.to_owned()
136 } else {
137 let current_dir = std::env::current_dir();
138 match current_dir {
139 Ok(current_dir) => current_dir.join(path),
140 Err(_) => path.to_owned(),
141 }
142 });
143 }
144
145 pub fn search_dir(&mut self, path: impl AsRef<Path>) {
147 self.record_path(path.as_ref());
148
149 self.db.load_fonts_dir(path);
150 }
151
152 pub fn search_file(&mut self, path: impl AsRef<Path>) -> FileResult<()> {
154 self.record_path(path.as_ref());
155
156 self.db
157 .load_font_file(path.as_ref())
158 .map_err(|e| FileError::from_io(e, path.as_ref()))
159 }
160}
161
162impl Default for SystemFontSearcher {
163 fn default() -> Self {
164 Self::new()
165 }
166}
167
168#[cfg(test)]
169mod tests {
170
171 #[test]
172 fn edit_fonts() {
173 use clap::Parser as _;
174
175 use crate::args::CompileOnceArgs;
176
177 let args = CompileOnceArgs::parse_from(["tinymist", "main.typ"]);
178 let mut verse = args
179 .resolve_system()
180 .expect("failed to resolve system universe");
181
182 let new_fonts = verse.font_resolver.clone();
184
185 verse.increment_revision(|verse| verse.set_fonts(new_fonts));
186 }
187}