tytanic_core/world_builder/
mod.rs1use typst::Library;
2use typst::World;
3use typst::diag::FileResult;
4use typst::foundations::Bytes;
5use typst::foundations::Datetime;
6use typst::syntax::FileId;
7use typst::syntax::Source;
8use typst::text::Font;
9use typst::text::FontBook;
10use typst::utils::LazyHash;
11use typst_kit::download::Progress;
12use typst_kit::download::ProgressSink;
13
14pub mod datetime;
15pub mod file;
16pub mod font;
17pub mod library;
18
19macro_rules! forward_trait {
20 (impl<$pointee:ident> $trait:ident for [$($pointer:ty),+] $funcs:tt) => {
21 $(impl<$pointee: $trait> $trait for $pointer $funcs)+
22 };
23}
24
25pub trait ProvideFile: Send + Sync {
27 fn provide_source(&self, id: FileId, progress: &mut dyn Progress) -> FileResult<Source>;
32
33 fn provide_bytes(&self, id: FileId, progress: &mut dyn Progress) -> FileResult<Bytes>;
38
39 fn reset_all(&self);
41}
42
43forward_trait! {
44 impl<W> ProvideFile for [std::boxed::Box<W>, std::sync::Arc<W>, &W] {
45 fn provide_source(&self, id: FileId, progress: &mut dyn Progress) -> FileResult<Source> {
46 W::provide_source(self, id, progress)
47 }
48
49 fn provide_bytes(&self, id: FileId, progress: &mut dyn Progress) -> FileResult<Bytes> {
50 W::provide_bytes(self, id, progress)
51 }
52
53 fn reset_all(&self) {
54 W::reset_all(self)
55 }
56 }
57}
58
59pub trait ProvideFont: Send + Sync {
61 fn provide_font_book(&self) -> &LazyHash<FontBook>;
63
64 fn provide_font(&self, index: usize) -> Option<Font>;
66}
67
68forward_trait! {
69 impl<W> ProvideFont for [std::boxed::Box<W>, std::sync::Arc<W>, &W] {
70 fn provide_font_book(&self) -> &LazyHash<FontBook> {
71 W::provide_font_book(self)
72 }
73
74 fn provide_font(&self, index: usize) -> Option<Font> {
75 W::provide_font(self, index)
76 }
77 }
78}
79
80pub trait ProvideLibrary: Send + Sync {
82 fn provide_library(&self) -> &LazyHash<Library>;
84}
85
86forward_trait! {
87 impl<W> ProvideLibrary for [std::boxed::Box<W>, std::sync::Arc<W>, &W] {
88 fn provide_library(&self) -> &LazyHash<Library> {
89 W::provide_library(self)
90 }
91 }
92}
93
94pub trait ProvideDatetime: Send + Sync {
96 fn provide_today(&self, offset: Option<i64>) -> Option<Datetime>;
108
109 fn reset_today(&self);
114}
115
116forward_trait! {
117 impl<W> ProvideDatetime for [std::boxed::Box<W>, std::sync::Arc<W>, &W] {
118 fn provide_today(&self, offset: Option<i64>) -> Option<Datetime> {
119 W::provide_today(self, offset)
120 }
121
122 fn reset_today(&self) {
123 W::reset_today(self)
124 }
125 }
126}
127
128pub struct ComposedWorldBuilder<'w> {
130 files: Option<&'w dyn ProvideFile>,
131 fonts: Option<&'w dyn ProvideFont>,
132 library: Option<&'w dyn ProvideLibrary>,
133 datetime: Option<&'w dyn ProvideDatetime>,
134}
135
136impl ComposedWorldBuilder<'_> {
137 pub fn new() -> Self {
139 Self {
140 files: None,
141 fonts: None,
142 library: None,
143 datetime: None,
144 }
145 }
146}
147
148impl<'w> ComposedWorldBuilder<'w> {
149 pub fn file_provider(self, value: &'w dyn ProvideFile) -> Self {
151 Self {
152 files: Some(value),
153 ..self
154 }
155 }
156
157 pub fn font_provider(self, value: &'w dyn ProvideFont) -> Self {
159 Self {
160 fonts: Some(value),
161 ..self
162 }
163 }
164
165 pub fn library_provider(self, value: &'w dyn ProvideLibrary) -> Self {
167 Self {
168 library: Some(value),
169 ..self
170 }
171 }
172
173 pub fn datetime_provider(self, value: &'w dyn ProvideDatetime) -> Self {
175 Self {
176 datetime: Some(value),
177 ..self
178 }
179 }
180
181 pub fn build(self, id: FileId) -> ComposedWorld<'w> {
185 self.try_build(id).unwrap()
186 }
187
188 pub fn try_build(self, id: FileId) -> Option<ComposedWorld<'w>> {
192 Some(ComposedWorld {
193 files: self.files?,
194 fonts: self.fonts?,
195 library: self.library?,
196 datetime: self.datetime?,
197 id,
198 })
199 }
200}
201
202impl Default for ComposedWorldBuilder<'_> {
203 fn default() -> Self {
204 Self::new()
205 }
206}
207
208pub struct ComposedWorld<'w> {
211 files: &'w dyn ProvideFile,
212 fonts: &'w dyn ProvideFont,
213 library: &'w dyn ProvideLibrary,
214 datetime: &'w dyn ProvideDatetime,
215 id: FileId,
216}
217
218impl<'w> ComposedWorld<'w> {
219 pub fn builder() -> ComposedWorldBuilder<'w> {
221 ComposedWorldBuilder::new()
222 }
223}
224
225impl ComposedWorld<'_> {
226 pub fn reset(&self) {
228 self.files.reset_all();
231 self.datetime.reset_today();
232 }
233}
234
235impl World for ComposedWorld<'_> {
236 fn library(&self) -> &LazyHash<Library> {
237 self.library.provide_library()
238 }
239
240 fn book(&self) -> &LazyHash<FontBook> {
241 self.fonts.provide_font_book()
242 }
243
244 fn main(&self) -> FileId {
245 self.id
246 }
247
248 fn source(&self, id: FileId) -> FileResult<Source> {
249 self.files.provide_source(id, &mut ProgressSink)
250 }
251
252 fn file(&self, id: FileId) -> FileResult<Bytes> {
253 self.files.provide_bytes(id, &mut ProgressSink)
254 }
255
256 fn font(&self, index: usize) -> Option<Font> {
257 self.fonts.provide_font(index)
258 }
259
260 fn today(&self, offset: Option<i64>) -> Option<Datetime> {
261 self.datetime.provide_today(offset)
262 }
263}
264
265#[cfg(test)]
266#[allow(dead_code)]
267pub(crate) mod test_utils {
268 use std::collections::HashMap;
269 use std::sync::LazyLock;
270
271 use chrono::DateTime;
272 use datetime::FixedDateProvider;
273 use file::VirtualFileProvider;
274 use font::VirtualFontProvider;
275 use library::LibraryProvider;
276
277 use super::file::VirtualFileSlot;
278 use super::*;
279 use crate::library::augmented_default_library;
280
281 pub(crate) fn test_file_provider(source: Source) -> VirtualFileProvider {
282 let mut map = HashMap::new();
283 map.insert(source.id(), VirtualFileSlot::from_source(source.clone()));
284
285 VirtualFileProvider::from_slots(map)
286 }
287
288 pub(crate) static TEST_FONT_PROVIDER: LazyLock<VirtualFontProvider> = LazyLock::new(|| {
289 let fonts: Vec<_> = typst_assets::fonts()
290 .flat_map(|data| Font::iter(Bytes::new(data)))
291 .collect();
292
293 let book = FontBook::from_fonts(&fonts);
294 VirtualFontProvider::new(book, fonts)
295 });
296
297 pub(crate) static TEST_DEFAULT_LIBRARY_PROVIDER: LazyLock<LibraryProvider> =
298 LazyLock::new(LibraryProvider::new);
299
300 pub(crate) static TEST_AUGMENTED_LIBRARY_PROVIDER: LazyLock<LibraryProvider> =
301 LazyLock::new(|| LibraryProvider::with_library(augmented_default_library()));
302
303 pub(crate) static TEST_DATETIME_PROVIDER: LazyLock<FixedDateProvider> =
304 LazyLock::new(|| FixedDateProvider::new(DateTime::from_timestamp(0, 0).unwrap()));
305
306 pub(crate) fn virtual_world<'w>(
307 source: Source,
308 files: &'w mut VirtualFileProvider,
309 library: &'w LibraryProvider,
310 ) -> ComposedWorld<'w> {
311 files
312 .slots_mut()
313 .insert(source.id(), VirtualFileSlot::from_source(source.clone()));
314
315 ComposedWorld::builder()
316 .file_provider(files)
317 .font_provider(&*TEST_FONT_PROVIDER)
318 .library_provider(library)
319 .datetime_provider(&*TEST_DATETIME_PROVIDER)
320 .build(source.id())
321 }
322}