Skip to main content

typst_library/
lib.rs

1//! Typst's standard library.
2//!
3//! This crate also contains all of the compiler's central type definitions as
4//! these are interwoven with the standard library types.
5//!
6//! In contrast to the _types,_ most of the compilation _behaviour_ is split out
7//! into separate crates (`typst-eval`, `typst-realize`, `typst-layout`, etc.)
8//!
9//! Note that, unless you are working on the compiler itself, you will rarely
10//! need to interact with this crate, as it is fully reexported by the `typst`
11//! crate.
12
13extern crate self as typst_library;
14
15pub mod diag;
16pub mod engine;
17pub mod foundations;
18pub mod introspection;
19pub mod layout;
20pub mod loading;
21pub mod math;
22pub mod model;
23pub mod pdf;
24pub mod routines;
25pub mod symbols;
26pub mod text;
27pub mod visualize;
28
29use std::ops::{Deref, Range};
30
31use serde::{Deserialize, Serialize};
32use typst_syntax::{DiagSpan, DiagSpanKind, FileId, Source};
33use typst_utils::{LazyHash, SmallBitSet};
34
35use crate::diag::FileResult;
36use crate::foundations::{
37    Array, Binding, Bytes, Datetime, Dict, Duration, Module, NativeRuleMap, Scope, Styles,
38};
39use crate::layout::{Alignment, Dir};
40use crate::routines::Routines;
41use crate::text::{Font, FontBook};
42use crate::visualize::Color;
43
44/// The environment in which typesetting occurs.
45///
46/// All loading functions (`main`, `source`, `file`, `font`) should perform
47/// internal caching so that they are relatively cheap on repeated invocations
48/// with the same argument. [`Source`], [`Bytes`], and [`Font`] are
49/// all reference-counted and thus cheap to clone.
50///
51/// The compiler doesn't do the caching itself because the world has much more
52/// information on when something can change. For example, fonts typically don't
53/// change and can thus even be cached across multiple compilations (for
54/// long-running applications like `typst watch`). Source files on the other
55/// hand can change and should thus be cleared after each compilation. Advanced
56/// clients like language servers can also retain the source files and
57/// [edit](Source::edit) them in-place to benefit from better incremental
58/// performance.
59#[comemo::track]
60pub trait World: Send + Sync {
61    /// The standard library.
62    ///
63    /// Can be created through `Library::build()`.
64    fn library(&self) -> &LazyHash<Library>;
65
66    /// Metadata about all known fonts.
67    fn book(&self) -> &LazyHash<FontBook>;
68
69    /// Get the file id of the main source file.
70    fn main(&self) -> FileId;
71
72    /// Try to access the specified file location as a source file.
73    fn source(&self, id: FileId) -> FileResult<Source>;
74
75    /// Try to access the specified file.
76    ///
77    /// For file locations for which [`source`](Self::source) succeeds, this
78    /// should also succeed. The [`Bytes`] can be cheaply created as a view into
79    /// an existing [`Source`] through [`Bytes::from_string`].
80    fn file(&self, id: FileId) -> FileResult<Bytes>;
81
82    /// Try to access the font with the given index in the font book.
83    ///
84    /// Note that the index is not guaranteed to be in bounds of the font book
85    /// returned by this world's `book()` function. This is the case because
86    /// this function may be invoked with indices from an outdated or different
87    /// font book during incremental compilation validation.
88    fn font(&self, index: usize) -> Option<Font>;
89
90    /// Get the current date.
91    ///
92    /// If no offset is specified, the local date should be chosen. Otherwise,
93    /// the UTC date should be chosen with the corresponding offset.
94    ///
95    /// If this function returns `None`, Typst's `datetime` function will
96    /// return an error.
97    fn today(&self, offset: Option<Duration>) -> Option<Datetime>;
98}
99
100macro_rules! world_impl {
101    ($W:ident for $ptr:ty) => {
102        impl<$W: World> World for $ptr {
103            fn library(&self) -> &LazyHash<Library> {
104                self.deref().library()
105            }
106
107            fn book(&self) -> &LazyHash<FontBook> {
108                self.deref().book()
109            }
110
111            fn main(&self) -> FileId {
112                self.deref().main()
113            }
114
115            fn source(&self, id: FileId) -> FileResult<Source> {
116                self.deref().source(id)
117            }
118
119            fn file(&self, id: FileId) -> FileResult<Bytes> {
120                self.deref().file(id)
121            }
122
123            fn font(&self, index: usize) -> Option<Font> {
124                self.deref().font(index)
125            }
126
127            fn today(&self, offset: Option<Duration>) -> Option<Datetime> {
128                self.deref().today(offset)
129            }
130        }
131    };
132}
133
134world_impl!(W for std::boxed::Box<W>);
135world_impl!(W for std::sync::Arc<W>);
136world_impl!(W for &W);
137
138/// Helper methods on [`World`] implementations.
139pub trait WorldExt {
140    /// Get the byte range for a span.
141    ///
142    /// Returns `None` if the `Span` does not point into any file.
143    fn range(&self, span: impl Into<DiagSpan>) -> Option<Range<usize>>;
144}
145
146impl<T: World + ?Sized> WorldExt for T {
147    fn range(&self, span: impl Into<DiagSpan>) -> Option<Range<usize>> {
148        match span.into().get() {
149            DiagSpanKind::Detached => None,
150            DiagSpanKind::Number { id, num, sub_range } => {
151                self.source(id).ok()?.range(num, sub_range)
152            }
153            DiagSpanKind::Range { id: _, range } => Some(range),
154        }
155    }
156}
157
158/// Definition of Typst's standard library.
159///
160/// To create and configure the standard library, use the `LibraryExt` trait
161/// and call
162/// - `Library::default()` for a standard configuration
163/// - `Library::builder().build()` if you want to customize the library
164#[derive(Debug, Clone, Hash)]
165#[non_exhaustive]
166pub struct Library {
167    /// Defines implementation of various Typst compiler routines as a table of
168    /// function pointers.
169    pub routines: &'static Routines,
170    /// The module that contains the definitions that are available everywhere.
171    pub global: Module,
172    /// The module that contains the definitions available in math mode.
173    pub math: Module,
174    /// The default style properties (for page size, font selection, and
175    /// everything else configurable via set and show rules).
176    pub styles: Styles,
177    /// The built-in show rules.
178    pub rules: NativeRuleMap,
179    /// The standard library as a value. Used to provide the `std` module.
180    pub std: Binding,
181    /// In-development features that were enabled.
182    pub features: Features,
183}
184
185/// Configurable builder for the standard library.
186///
187/// Constructed via the `LibraryExt` trait.
188#[derive(Debug, Clone)]
189pub struct LibraryBuilder {
190    routines: &'static Routines,
191    inputs: Option<Dict>,
192    features: Features,
193}
194
195impl LibraryBuilder {
196    /// Creates a new builder.
197    #[doc(hidden)]
198    pub fn from_routines(routines: &'static Routines) -> Self {
199        Self {
200            routines,
201            inputs: None,
202            features: Features::default(),
203        }
204    }
205
206    /// Configure the inputs visible through `sys.inputs`.
207    pub fn with_inputs(mut self, inputs: Dict) -> Self {
208        self.inputs = Some(inputs);
209        self
210    }
211
212    /// Configure in-development features that should be enabled.
213    ///
214    /// No guarantees whatsover!
215    pub fn with_features(mut self, features: Features) -> Self {
216        self.features = features;
217        self
218    }
219
220    /// Consumes the builder and returns a `Library`.
221    pub fn build(self) -> Library {
222        let math = math::module();
223        let inputs = self.inputs.unwrap_or_default();
224        let global = global(self.routines, math.clone(), inputs, &self.features);
225        Library {
226            routines: self.routines,
227            global: global.clone(),
228            math,
229            styles: Styles::new(),
230            rules: (self.routines.rules)(),
231            std: Binding::detached(global),
232            features: self.features,
233        }
234    }
235}
236
237/// A selection of in-development features that should be enabled.
238///
239/// Can be collected from an iterator of [`Feature`]s.
240#[derive(Debug, Default, Clone, Hash)]
241pub struct Features(SmallBitSet);
242
243impl Features {
244    /// Creates an instance where all features are enabled.
245    pub fn all() -> Self {
246        Feature::all().collect()
247    }
248
249    /// Creates an instance where no features are enabled.
250    pub fn none() -> Self {
251        Self::default()
252    }
253
254    /// Check whether the given feature is enabled.
255    pub fn is_enabled(&self, feature: Feature) -> bool {
256        self.0.contains(feature as usize)
257    }
258}
259
260impl FromIterator<Feature> for Features {
261    fn from_iter<T: IntoIterator<Item = Feature>>(iter: T) -> Self {
262        let mut set = SmallBitSet::default();
263        for feature in iter {
264            set.insert(feature as usize);
265        }
266        Self(set)
267    }
268}
269
270/// An in-development feature that should be enabled.
271#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
272#[non_exhaustive]
273pub enum Feature {
274    Html,
275    Bundle,
276    A11yExtras,
277}
278
279impl Feature {
280    /// Iterates over all available features.
281    pub fn all() -> impl Iterator<Item = Self> {
282        [Self::Html, Self::Bundle, Self::A11yExtras].into_iter()
283    }
284}
285
286/// A group of related standard library definitions.
287#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
288#[serde(rename_all = "kebab-case")]
289pub enum Category {
290    Foundations,
291    Introspection,
292    Layout,
293    DataLoading,
294    Math,
295    Model,
296    Symbols,
297    Text,
298    Visualize,
299    Pdf,
300    Html,
301    Svg,
302    Png,
303    Bundle,
304}
305
306impl Category {
307    /// The kebab-case name of the category.
308    pub fn name(&self) -> &'static str {
309        match self {
310            Self::Foundations => "foundations",
311            Self::Introspection => "introspection",
312            Self::Layout => "layout",
313            Self::DataLoading => "data-loading",
314            Self::Math => "math",
315            Self::Model => "model",
316            Self::Symbols => "symbols",
317            Self::Text => "text",
318            Self::Visualize => "visualize",
319            Self::Pdf => "pdf",
320            Self::Html => "html",
321            Self::Svg => "svg",
322            Self::Png => "png",
323            Self::Bundle => "bundle",
324        }
325    }
326}
327
328/// Construct the module with global definitions.
329fn global(
330    routines: &Routines,
331    math: Module,
332    inputs: Dict,
333    features: &Features,
334) -> Module {
335    let mut global = Scope::deduplicating();
336
337    self::foundations::define(&mut global, inputs);
338    self::model::define(&mut global, features);
339    self::text::define(&mut global);
340    self::layout::define(&mut global);
341    self::visualize::define(&mut global);
342    self::introspection::define(&mut global);
343    self::loading::define(&mut global);
344    self::symbols::define(&mut global);
345
346    global.define("math", math);
347    global.define("pdf", self::pdf::module(features));
348    if features.is_enabled(Feature::Html) {
349        global.define("html", (routines.html_module)());
350    }
351
352    prelude(&mut global);
353
354    Module::new("global", global)
355}
356
357/// Defines scoped values that are globally available, too.
358fn prelude(global: &mut Scope) {
359    global.define("black", Color::BLACK);
360    global.define("gray", Color::GRAY);
361    global.define("silver", Color::SILVER);
362    global.define("white", Color::WHITE);
363    global.define("navy", Color::NAVY);
364    global.define("blue", Color::BLUE);
365    global.define("aqua", Color::AQUA);
366    global.define("teal", Color::TEAL);
367    global.define("eastern", Color::EASTERN);
368    global.define("purple", Color::PURPLE);
369    global.define("fuchsia", Color::FUCHSIA);
370    global.define("maroon", Color::MAROON);
371    global.define("red", Color::RED);
372    global.define("orange", Color::ORANGE);
373    global.define("yellow", Color::YELLOW);
374    global.define("olive", Color::OLIVE);
375    global.define("green", Color::GREEN);
376    global.define("lime", Color::LIME);
377    global.define("luma", Color::luma_data());
378    global.define("oklab", Color::oklab_data());
379    global.define("oklch", Color::oklch_data());
380    global.define("rgb", Color::rgb_data());
381    global.define("cmyk", Color::cmyk_data());
382    global.define("range", Array::range_data());
383    global.define("ltr", Dir::LTR);
384    global.define("rtl", Dir::RTL);
385    global.define("ttb", Dir::TTB);
386    global.define("btt", Dir::BTT);
387    global.define("start", Alignment::START);
388    global.define("left", Alignment::LEFT);
389    global.define("center", Alignment::CENTER);
390    global.define("right", Alignment::RIGHT);
391    global.define("end", Alignment::END);
392    global.define("top", Alignment::TOP);
393    global.define("horizon", Alignment::HORIZON);
394    global.define("bottom", Alignment::BOTTOM);
395}