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