typst_as_lib/lib.rs
1#![warn(missing_docs)]
2//! Small wrapper around [Typst](https://github.com/typst/typst) that makes it easier to use it as a templating engine.
3//!
4//! See the [repository README](https://github.com/Relacibo/typst-as-lib) for usage examples.
5//!
6//! Inspired by <https://github.com/tfachmann/typst-as-library/blob/main/src/lib.rs>
7use std::borrow::Cow;
8use std::ops::Deref;
9use std::path::PathBuf;
10
11use cached_file_resolver::IntoCachedFileResolver;
12use chrono::{DateTime, Datelike, Duration, Utc};
13use conversions::{IntoBytes, IntoFileId, IntoFonts, IntoSource};
14use ecow::EcoVec;
15use file_resolver::{
16 FileResolver, FileSystemResolver, MainSourceFileResolver, StaticFileResolver,
17 StaticSourceFileResolver,
18};
19use thiserror::Error;
20use typst::diag::{FileError, FileResult, HintedString, SourceDiagnostic, Warned};
21use typst::foundations::{Bytes, Datetime, Dict, Module, Scope, Value};
22use typst::syntax::{FileId, Source};
23use typst::text::{Font, FontBook};
24use typst::utils::LazyHash;
25use typst::{Document, Library, LibraryExt};
26use util::not_found;
27
28/// Caching wrapper for file resolvers.
29pub mod cached_file_resolver;
30/// Type conversion traits for Typst types.
31pub mod conversions;
32/// File resolution for Typst sources and binaries.
33pub mod file_resolver;
34pub(crate) mod util;
35
36#[cfg(all(feature = "packages", any(feature = "ureq", feature = "reqwest")))]
37/// Package resolution and downloading from the Typst package repository.
38pub mod package_resolver;
39
40#[cfg(feature = "typst-kit-fonts")]
41/// Configuration options for `typst-kit` font searching.
42pub mod typst_kit_options;
43
44/// Main entry point for compiling Typst documents.
45///
46/// Use [`TypstEngine::builder()`] to construct an instance. You can optionally set a
47/// main file with [`main_file()`](TypstTemplateEngineBuilder::main_file), which allows
48/// compiling without specifying the file ID each time.
49///
50/// # Examples
51///
52/// With main file (compile without file ID):
53///
54/// ```rust,no_run
55/// # use typst_as_lib::TypstEngine;
56/// # use typst::layout::PagedDocument;
57/// static TEMPLATE: &str = "Hello World!";
58/// static FONT: &[u8] = include_bytes!("../examples/fonts/texgyrecursor-regular.otf");
59///
60/// let engine = TypstEngine::builder()
61/// .main_file(TEMPLATE)
62/// .fonts([FONT])
63/// .build();
64///
65/// // Compile the main file directly
66/// let doc: PagedDocument = engine.compile().output.expect("Compilation failed");
67/// ```
68///
69/// Without main file (must provide file ID):
70///
71/// ```rust,no_run
72/// # use typst_as_lib::TypstEngine;
73/// # use typst::layout::PagedDocument;
74/// static TEMPLATE: &str = "Hello World!";
75/// static FONT: &[u8] = include_bytes!("../examples/fonts/texgyrecursor-regular.otf");
76///
77/// let engine = TypstEngine::builder()
78/// .fonts([FONT])
79/// .with_static_source_file_resolver([("template.typ", TEMPLATE)])
80/// .build();
81///
82/// // Must specify file ID for each compile
83/// let doc: PagedDocument = engine.compile("template.typ").output.expect("Compilation failed");
84/// ```
85///
86/// See also: [Examples directory](https://github.com/Relacibo/typst-as-lib/tree/main/examples)
87pub struct TypstEngine<T = TypstTemplateCollection> {
88 template: T,
89 book: LazyHash<FontBook>,
90 inject_location: Option<InjectLocation>,
91 file_resolvers: Vec<Box<dyn FileResolver + Send + Sync + 'static>>,
92 library: LazyHash<Library>,
93 comemo_evict_max_age: Option<usize>,
94 fonts: Vec<FontEnum>,
95}
96
97/// Type state indicating no main file is set.
98#[derive(Debug, Clone, Copy)]
99pub struct TypstTemplateCollection;
100
101/// Type state indicating a main file has been set.
102#[derive(Debug, Clone, Copy)]
103pub struct TypstTemplateMainFile {
104 source_id: FileId,
105}
106
107impl<T> TypstEngine<T> {
108 fn do_compile<Doc>(
109 &self,
110 main_source_id: FileId,
111 inputs: Option<Dict>,
112 ) -> Warned<Result<Doc, TypstAsLibError>>
113 where
114 Doc: Document,
115 {
116 let library = if let Some(inputs) = inputs {
117 let lib = self.create_injected_library(inputs);
118 match lib {
119 Ok(lib) => Cow::Owned(lib),
120 Err(err) => {
121 return Warned {
122 output: Err(err),
123 warnings: Default::default(),
124 };
125 }
126 }
127 } else {
128 Cow::Borrowed(&self.library)
129 };
130 let world = TypstWorld {
131 main_source_id,
132 library,
133 now: Utc::now(),
134 file_resolvers: &self.file_resolvers,
135 book: &self.book,
136 fonts: &self.fonts,
137 };
138 let Warned { output, warnings } = typst::compile(&world);
139
140 if let Some(comemo_evict_max_age) = self.comemo_evict_max_age {
141 comemo::evict(comemo_evict_max_age);
142 }
143
144 Warned {
145 output: output.map_err(Into::into),
146 warnings,
147 }
148 }
149
150 fn create_injected_library<D>(&self, input: D) -> Result<LazyHash<Library>, TypstAsLibError>
151 where
152 D: Into<Dict>,
153 {
154 let Self {
155 inject_location,
156 library,
157 ..
158 } = self;
159 let mut lib = library.deref().clone();
160 let (module_name, value_name) = if let Some(InjectLocation {
161 module_name,
162 value_name,
163 }) = inject_location
164 {
165 (*module_name, *value_name)
166 } else {
167 ("sys", "inputs")
168 };
169 {
170 let global = lib.global.scope_mut();
171 let input_dict: Dict = input.into();
172 if let Some(module_value) = global.get_mut(module_name) {
173 let module_value = module_value.write()?;
174 if let Value::Module(module) = module_value {
175 let scope = module.scope_mut();
176 if let Some(target) = scope.get_mut(value_name) {
177 // Override existing field
178 *target.write()? = Value::Dict(input_dict);
179 } else {
180 // Write new field into existing module scope
181 scope.define(value_name, input_dict);
182 }
183 } else {
184 // Override existing non module value
185 let mut scope = Scope::deduplicating();
186 scope.define(value_name, input_dict);
187 let module = Module::new(module_name, scope);
188 *module_value = Value::Module(module);
189 }
190 } else {
191 // Create new module and field
192 let mut scope = Scope::deduplicating();
193 scope.define(value_name, input_dict);
194 let module = Module::new(module_name, scope);
195 global.define(module_name, module);
196 }
197 }
198 Ok(LazyHash::new(lib))
199 }
200}
201
202impl TypstEngine<TypstTemplateCollection> {
203 /// Creates a new builder for configuring a [`TypstEngine`].
204 ///
205 /// # Example
206 ///
207 /// ```rust,no_run
208 /// # use typst_as_lib::TypstEngine;
209 /// static FONT: &[u8] = include_bytes!("../examples/fonts/texgyrecursor-regular.otf");
210 ///
211 /// let engine = TypstEngine::builder()
212 /// .fonts([FONT])
213 /// .build();
214 /// ```
215 pub fn builder() -> TypstTemplateEngineBuilder {
216 TypstTemplateEngineBuilder::default()
217 }
218}
219
220impl TypstEngine<TypstTemplateCollection> {
221 /// Compiles a Typst document with input data injected as `sys.inputs`.
222 ///
223 /// The input will be available in Typst scripts via `#import sys: inputs`.
224 ///
225 /// To change the injection location, use [`custom_inject_location()`](TypstTemplateEngineBuilder::custom_inject_location).
226 ///
227 /// # Example
228 ///
229 /// ```rust,no_run
230 /// # use typst_as_lib::TypstEngine;
231 /// # use typst::foundations::{Dict, IntoValue};
232 /// # use typst::layout::PagedDocument;
233 /// static TEMPLATE: &str = "#import sys: inputs\n#inputs.name";
234 /// static FONT: &[u8] = include_bytes!("../examples/fonts/texgyrecursor-regular.otf");
235 ///
236 /// let engine = TypstEngine::builder()
237 /// .fonts([FONT])
238 /// .with_static_source_file_resolver([("main.typ", TEMPLATE)])
239 /// .build();
240 ///
241 /// let mut inputs = Dict::new();
242 /// inputs.insert("name".into(), "World".into_value());
243 ///
244 /// let doc: PagedDocument = engine.compile_with_input("main.typ", inputs)
245 /// .output
246 /// .expect("Compilation failed");
247 /// ```
248 ///
249 /// See also: [resolve_static.rs](https://github.com/Relacibo/typst-as-lib/blob/main/examples/resolve_static.rs)
250 pub fn compile_with_input<F, D, Doc>(
251 &self,
252 main_source_id: F,
253 inputs: D,
254 ) -> Warned<Result<Doc, TypstAsLibError>>
255 where
256 F: IntoFileId,
257 D: Into<Dict>,
258 Doc: Document,
259 {
260 self.do_compile(main_source_id.into_file_id(), Some(inputs.into()))
261 }
262
263 /// Compiles a Typst document without input data.
264 pub fn compile<F, Doc>(&self, main_source_id: F) -> Warned<Result<Doc, TypstAsLibError>>
265 where
266 F: IntoFileId,
267 Doc: Document,
268 {
269 self.do_compile(main_source_id.into_file_id(), None)
270 }
271}
272
273impl TypstEngine<TypstTemplateMainFile> {
274 /// Compiles the main file with input data injected as `sys.inputs`.
275 ///
276 /// The input will be available in Typst scripts via `#import sys: inputs`.
277 ///
278 /// To change the injection location, use [`custom_inject_location()`](TypstTemplateEngineBuilder::custom_inject_location).
279 ///
280 /// # Example
281 ///
282 /// ```rust,no_run
283 /// # use typst_as_lib::TypstEngine;
284 /// # use typst::foundations::{Dict, IntoValue};
285 /// # use typst::layout::PagedDocument;
286 /// static TEMPLATE: &str = "#import sys: inputs\nHello #inputs.name!";
287 /// static FONT: &[u8] = include_bytes!("../examples/fonts/texgyrecursor-regular.otf");
288 ///
289 /// let engine = TypstEngine::builder()
290 /// .main_file(TEMPLATE)
291 /// .fonts([FONT])
292 /// .build();
293 ///
294 /// let mut inputs = Dict::new();
295 /// inputs.insert("name".into(), "World".into_value());
296 ///
297 /// let doc: PagedDocument = engine.compile_with_input(inputs)
298 /// .output
299 /// .expect("Compilation failed");
300 /// ```
301 ///
302 /// See also: [small_example.rs](https://github.com/Relacibo/typst-as-lib/blob/main/examples/small_example.rs)
303 pub fn compile_with_input<D, Doc>(&self, inputs: D) -> Warned<Result<Doc, TypstAsLibError>>
304 where
305 D: Into<Dict>,
306 Doc: Document,
307 {
308 let TypstTemplateMainFile { source_id } = self.template;
309 self.do_compile(source_id, Some(inputs.into()))
310 }
311
312 /// Compiles the main file without input data.
313 pub fn compile<Doc>(&self) -> Warned<Result<Doc, TypstAsLibError>>
314 where
315 Doc: Document,
316 {
317 let TypstTemplateMainFile { source_id } = self.template;
318 self.do_compile(source_id, None)
319 }
320}
321
322/// Builder for constructing a [`TypstEngine`].
323pub struct TypstTemplateEngineBuilder<T = TypstTemplateCollection> {
324 template: T,
325 inject_location: Option<InjectLocation>,
326 file_resolvers: Vec<Box<dyn FileResolver + Send + Sync + 'static>>,
327 comemo_evict_max_age: Option<usize>,
328 fonts: Option<Vec<Font>>,
329 #[cfg(feature = "typst-kit-fonts")]
330 typst_kit_font_options: Option<typst_kit_options::TypstKitFontOptions>,
331}
332
333impl Default for TypstTemplateEngineBuilder {
334 fn default() -> Self {
335 Self {
336 template: TypstTemplateCollection,
337 inject_location: Default::default(),
338 file_resolvers: Default::default(),
339 comemo_evict_max_age: Some(0),
340 fonts: Default::default(),
341 #[cfg(feature = "typst-kit-fonts")]
342 typst_kit_font_options: None,
343 }
344 }
345}
346
347impl TypstTemplateEngineBuilder<TypstTemplateCollection> {
348 /// Sets the main file for compilation.
349 ///
350 /// This is optional. If not set, you must provide a file ID on each compile call.
351 ///
352 /// # Example
353 ///
354 /// ```rust,no_run
355 /// # use typst_as_lib::TypstEngine;
356 /// # use typst::layout::PagedDocument;
357 /// static TEMPLATE: &str = "Hello World!";
358 /// static FONT: &[u8] = include_bytes!("../examples/fonts/texgyrecursor-regular.otf");
359 ///
360 /// let engine = TypstEngine::builder()
361 /// .main_file(TEMPLATE)
362 /// .fonts([FONT])
363 /// .build();
364 ///
365 /// let doc: PagedDocument = engine.compile().output.expect("Compilation failed");
366 /// ```
367 ///
368 /// See also: [small_example.rs](https://github.com/Relacibo/typst-as-lib/blob/main/examples/small_example.rs)
369 pub fn main_file<S: IntoSource>(
370 self,
371 source: S,
372 ) -> TypstTemplateEngineBuilder<TypstTemplateMainFile> {
373 let source = source.into_source();
374 let source_id = source.id();
375 let template = TypstTemplateMainFile { source_id };
376 let TypstTemplateEngineBuilder {
377 inject_location,
378 mut file_resolvers,
379 comemo_evict_max_age,
380 fonts,
381 #[cfg(feature = "typst-kit-fonts")]
382 typst_kit_font_options,
383 ..
384 } = self;
385 file_resolvers.push(Box::new(MainSourceFileResolver::new(source)));
386 TypstTemplateEngineBuilder {
387 template,
388 inject_location,
389 file_resolvers,
390 comemo_evict_max_age,
391 fonts,
392 #[cfg(feature = "typst-kit-fonts")]
393 typst_kit_font_options,
394 }
395 }
396}
397
398impl<T> TypstTemplateEngineBuilder<T> {
399 /// Customizes where input data is injected in the Typst environment.
400 ///
401 /// By default, inputs are available as `sys.inputs`.
402 pub fn custom_inject_location(
403 mut self,
404 module_name: &'static str,
405 value_name: &'static str,
406 ) -> Self {
407 self.inject_location = Some(InjectLocation {
408 module_name,
409 value_name,
410 });
411 self
412 }
413
414 /// Adds fonts for rendering.
415 ///
416 /// Accepts font data as `&[u8]`, `Vec<u8>`, `Bytes`, or `Font`.
417 ///
418 /// For automatic system font discovery, see `typst-kit-fonts` feature.
419 ///
420 /// # Example
421 ///
422 /// ```rust,no_run
423 /// # use typst_as_lib::TypstEngine;
424 /// static FONT: &[u8] = include_bytes!("../examples/fonts/texgyrecursor-regular.otf");
425 ///
426 /// let engine = TypstEngine::builder()
427 /// .fonts([FONT])
428 /// .build();
429 /// ```
430 pub fn fonts<I, F>(mut self, fonts: I) -> Self
431 where
432 I: IntoIterator<Item = F>,
433 F: IntoFonts,
434 {
435 let fonts = fonts
436 .into_iter()
437 .flat_map(IntoFonts::into_fonts)
438 .collect::<Vec<_>>();
439 self.fonts = Some(fonts);
440 self
441 }
442
443 /// Enables system font discovery using `typst-kit`.
444 ///
445 /// See [`typst_kit_options::TypstKitFontOptions`] for configuration.
446 ///
447 /// # Example
448 ///
449 /// ```rust,no_run
450 /// # use typst_as_lib::TypstEngine;
451 /// # use typst_as_lib::typst_kit_options::TypstKitFontOptions;
452 /// let engine = TypstEngine::builder()
453 /// .search_fonts_with(TypstKitFontOptions::default())
454 /// .build();
455 /// ```
456 ///
457 /// See also: [font_searcher.rs](https://github.com/Relacibo/typst-as-lib/blob/main/examples/font_searcher.rs)
458 #[cfg(feature = "typst-kit-fonts")]
459 pub fn search_fonts_with(mut self, options: typst_kit_options::TypstKitFontOptions) -> Self {
460 self.typst_kit_font_options = Some(options);
461 self
462 }
463
464 /// Adds a custom file resolver.
465 ///
466 /// Resolvers are tried in order until one successfully resolves the file.
467 pub fn add_file_resolver<F>(mut self, file_resolver: F) -> Self
468 where
469 F: FileResolver + Send + Sync + 'static,
470 {
471 self.file_resolvers.push(Box::new(file_resolver));
472 self
473 }
474
475 /// Adds static source files embedded in memory.
476 ///
477 /// Accepts sources as `&str`, `String`, `(&str, &str)` (path, content),
478 /// `(FileId, &str)`, or `Source`.
479 ///
480 /// # Example
481 ///
482 /// ```rust,no_run
483 /// # use typst_as_lib::TypstEngine;
484 /// # use typst::layout::PagedDocument;
485 /// static MAIN: &str = "#import \"lib.typ\": greet\n#greet()";
486 /// static LIB: &str = "#let greet() = [Hello World!]";
487 /// static FONT: &[u8] = include_bytes!("../examples/fonts/texgyrecursor-regular.otf");
488 ///
489 /// let engine = TypstEngine::builder()
490 /// .fonts([FONT])
491 /// .with_static_source_file_resolver([
492 /// ("main.typ", MAIN),
493 /// ("lib.typ", LIB),
494 /// ])
495 /// .build();
496 ///
497 /// let doc: PagedDocument = engine.compile("main.typ").output.expect("Compilation failed");
498 /// ```
499 ///
500 /// See also: [resolve_static.rs](https://github.com/Relacibo/typst-as-lib/blob/main/examples/resolve_static.rs)
501 pub fn with_static_source_file_resolver<IS, S>(self, sources: IS) -> Self
502 where
503 IS: IntoIterator<Item = S>,
504 S: IntoSource,
505 {
506 self.add_file_resolver(StaticSourceFileResolver::new(sources))
507 }
508
509 /// Adds static binary files embedded in memory (e.g., images).
510 ///
511 /// # Example
512 ///
513 /// ```rust,no_run
514 /// # use typst_as_lib::TypstEngine;
515 /// static TEMPLATE: &str = r#"#image("logo.png")"#;
516 /// static LOGO: &[u8] = include_bytes!("../examples/templates/images/typst.png");
517 /// static FONT: &[u8] = include_bytes!("../examples/fonts/texgyrecursor-regular.otf");
518 ///
519 /// let engine = TypstEngine::builder()
520 /// .main_file(TEMPLATE)
521 /// .fonts([FONT])
522 /// .with_static_file_resolver([("logo.png", LOGO)])
523 /// .build();
524 /// ```
525 ///
526 /// See also: [resolve_static.rs](https://github.com/Relacibo/typst-as-lib/blob/main/examples/resolve_static.rs)
527 pub fn with_static_file_resolver<IB, F, B>(self, binaries: IB) -> Self
528 where
529 IB: IntoIterator<Item = (F, B)>,
530 F: IntoFileId,
531 B: IntoBytes,
532 {
533 self.add_file_resolver(StaticFileResolver::new(binaries))
534 }
535
536 /// Enables loading files from the file system.
537 ///
538 /// Files are resolved relative to `root`. Files outside of `root` cannot be accessed.
539 ///
540 /// # Example
541 ///
542 /// ```rust,no_run
543 /// # use typst_as_lib::TypstEngine;
544 /// static TEMPLATE: &str = r#"#include "header.typ""#;
545 /// static FONT: &[u8] = include_bytes!("../examples/fonts/texgyrecursor-regular.otf");
546 ///
547 /// let engine = TypstEngine::builder()
548 /// .main_file(TEMPLATE)
549 /// .fonts([FONT])
550 /// .with_file_system_resolver("./templates")
551 /// .build();
552 /// ```
553 ///
554 /// See also: [resolve_packages.rs](https://github.com/Relacibo/typst-as-lib/blob/main/examples/resolve_packages.rs)
555 pub fn with_file_system_resolver<P>(self, root: P) -> Self
556 where
557 P: Into<PathBuf>,
558 {
559 self.add_file_resolver(FileSystemResolver::new(root.into()).into_cached())
560 }
561
562 /// Sets the maximum age for comemo cache eviction after compilation.
563 ///
564 /// Default is `Some(0)`, which evicts after each compilation.
565 pub fn comemo_evict_max_age(&mut self, comemo_evict_max_age: Option<usize>) -> &mut Self {
566 self.comemo_evict_max_age = comemo_evict_max_age;
567 self
568 }
569
570 /// Enables downloading packages from the Typst package repository.
571 ///
572 /// Packages are cached on the file system for reuse.
573 ///
574 /// # Example
575 ///
576 /// ```rust,no_run
577 /// # use typst_as_lib::TypstEngine;
578 /// static TEMPLATE: &str = r#"#import "@preview/example:0.1.0": *"#;
579 /// static FONT: &[u8] = include_bytes!("../examples/fonts/texgyrecursor-regular.otf");
580 ///
581 /// let engine = TypstEngine::builder()
582 /// .main_file(TEMPLATE)
583 /// .fonts([FONT])
584 /// .with_package_file_resolver()
585 /// .build();
586 /// ```
587 ///
588 /// See also: [resolve_packages.rs](https://github.com/Relacibo/typst-as-lib/blob/main/examples/resolve_packages.rs)
589 #[cfg(all(feature = "packages", any(feature = "ureq", feature = "reqwest")))]
590 pub fn with_package_file_resolver(self) -> Self {
591 use package_resolver::PackageResolver;
592 let file_resolver = PackageResolver::builder()
593 .with_file_system_cache()
594 .build()
595 .into_cached();
596 self.add_file_resolver(file_resolver)
597 }
598
599 /// Builds the [`TypstEngine`] with the configured options.
600 pub fn build(self) -> TypstEngine<T> {
601 let TypstTemplateEngineBuilder {
602 template,
603 inject_location,
604 file_resolvers,
605 comemo_evict_max_age,
606 fonts,
607 #[cfg(feature = "typst-kit-fonts")]
608 typst_kit_font_options,
609 } = self;
610
611 let mut book = FontBook::new();
612 if let Some(fonts) = &fonts {
613 for f in fonts {
614 book.push(f.info().clone());
615 }
616 }
617
618 #[allow(unused_mut)]
619 let mut fonts: Vec<_> = fonts.into_iter().flatten().map(FontEnum::Font).collect();
620
621 #[cfg(feature = "typst-kit-fonts")]
622 if let Some(typst_kit_font_options) = typst_kit_font_options {
623 let typst_kit_options::TypstKitFontOptions {
624 include_system_fonts,
625 include_dirs,
626 #[cfg(feature = "typst-kit-embed-fonts")]
627 include_embedded_fonts,
628 } = typst_kit_font_options;
629 let mut searcher = typst_kit::fonts::Fonts::searcher();
630 #[cfg(feature = "typst-kit-embed-fonts")]
631 searcher.include_embedded_fonts(include_embedded_fonts);
632 let typst_kit::fonts::Fonts {
633 book: typst_kit_book,
634 fonts: typst_kit_fonts,
635 } = searcher
636 .include_system_fonts(include_system_fonts)
637 .search_with(include_dirs);
638 let len = typst_kit_fonts.len();
639 let font_slots = typst_kit_fonts.into_iter().map(FontEnum::FontSlot);
640 if fonts.is_empty() {
641 book = typst_kit_book;
642 fonts = font_slots.collect();
643 } else {
644 for i in 0..len {
645 let Some(info) = typst_kit_book.info(i) else {
646 break;
647 };
648 book.push(info.clone());
649 }
650 fonts.extend(font_slots);
651 }
652 }
653
654 #[cfg(not(feature = "typst-html"))]
655 let library = typst::Library::builder().build();
656
657 #[cfg(feature = "typst-html")]
658 let library = typst::Library::builder()
659 .with_features([typst::Feature::Html].into_iter().collect())
660 .build();
661
662 TypstEngine {
663 template,
664 inject_location,
665 file_resolvers,
666 comemo_evict_max_age,
667 library: LazyHash::new(library),
668 book: LazyHash::new(book),
669 fonts,
670 }
671 }
672}
673
674struct TypstWorld<'a> {
675 library: Cow<'a, LazyHash<Library>>,
676 main_source_id: FileId,
677 now: DateTime<Utc>,
678 book: &'a LazyHash<FontBook>,
679 file_resolvers: &'a [Box<dyn FileResolver + Send + Sync + 'static>],
680 fonts: &'a [FontEnum],
681}
682
683impl typst::World for TypstWorld<'_> {
684 fn library(&self) -> &LazyHash<Library> {
685 self.library.as_ref()
686 }
687
688 fn book(&self) -> &LazyHash<FontBook> {
689 self.book
690 }
691
692 fn main(&self) -> FileId {
693 self.main_source_id
694 }
695
696 fn source(&self, id: FileId) -> FileResult<Source> {
697 let Self { file_resolvers, .. } = *self;
698 let mut last_error = not_found(id);
699 for file_resolver in file_resolvers {
700 match file_resolver.resolve_source(id) {
701 Ok(source) => return Ok(source.into_owned()),
702 Err(error) => last_error = error,
703 }
704 }
705 Err(last_error)
706 }
707
708 fn file(&self, id: FileId) -> FileResult<Bytes> {
709 let Self { file_resolvers, .. } = *self;
710 let mut last_error = not_found(id);
711 for file_resolver in file_resolvers {
712 match file_resolver.resolve_binary(id) {
713 Ok(file) => return Ok(file.into_owned()),
714 Err(error) => last_error = error,
715 }
716 }
717 Err(last_error)
718 }
719
720 fn font(&self, id: usize) -> Option<Font> {
721 self.fonts[id].get()
722 }
723
724 fn today(&self, offset: Option<i64>) -> Option<Datetime> {
725 let mut now = self.now;
726 if let Some(offset) = offset {
727 now += Duration::hours(offset);
728 }
729 let date = now.date_naive();
730 let year = date.year();
731 let month = (date.month0() + 1) as u8;
732 let day = (date.day0() + 1) as u8;
733 Datetime::from_ymd(year, month, day)
734 }
735}
736
737#[derive(Debug, Clone)]
738struct InjectLocation {
739 module_name: &'static str,
740 value_name: &'static str,
741}
742
743/// Errors that can occur when using typst-as-lib.
744#[derive(Debug, Clone, Error)]
745pub enum TypstAsLibError {
746 /// Errors from Typst source compilation.
747 #[error("Typst source error: {0:?}")]
748 TypstSource(EcoVec<SourceDiagnostic>),
749 /// Errors from file operations.
750 #[error("Typst file error: {0}")]
751 TypstFile(#[from] FileError),
752 /// The specified main source file was not found.
753 #[error("Source file does not exist in collection: {0:?}")]
754 MainSourceFileDoesNotExist(FileId),
755 /// Errors with additional hints from Typst.
756 #[error("Typst hinted String: {0:?}")]
757 HintedString(HintedString),
758 /// Other unspecified errors.
759 #[error("Unspecified: {0}!")]
760 Unspecified(ecow::EcoString),
761}
762
763impl From<HintedString> for TypstAsLibError {
764 fn from(value: HintedString) -> Self {
765 TypstAsLibError::HintedString(value)
766 }
767}
768
769impl From<ecow::EcoString> for TypstAsLibError {
770 fn from(value: ecow::EcoString) -> Self {
771 TypstAsLibError::Unspecified(value)
772 }
773}
774
775impl From<EcoVec<SourceDiagnostic>> for TypstAsLibError {
776 fn from(value: EcoVec<SourceDiagnostic>) -> Self {
777 TypstAsLibError::TypstSource(value)
778 }
779}
780
781/// Wrapper for different font types.
782#[derive(Debug)]
783pub enum FontEnum {
784 /// A directly loaded font.
785 Font(Font),
786 /// A lazy font slot from typst-kit.
787 #[cfg(feature = "typst-kit-fonts")]
788 FontSlot(typst_kit::fonts::FontSlot),
789}
790
791impl FontEnum {
792 /// Retrieves the font, loading it if necessary.
793 pub fn get(&self) -> Option<Font> {
794 match self {
795 FontEnum::Font(font) => Some(font.clone()),
796 #[cfg(feature = "typst-kit-fonts")]
797 FontEnum::FontSlot(font_slot) => font_slot.get(),
798 }
799 }
800}