typst/
lib.rs

1//! The compiler for the _Typst_ markup language.
2//!
3//! # Steps
4//! - **Parsing:**
5//!   The compiler first transforms a plain string into an iterator of [tokens].
6//!   This token stream is [parsed] into a [syntax tree]. The tree itself is
7//!   untyped, but the [AST] module provides a typed layer over it.
8//! - **Evaluation:**
9//!   The next step is to [evaluate] the markup. This produces a [module],
10//!   consisting of a scope of values that were exported by the code and
11//!   [content], a hierarchical, styled representation of what was written in
12//!   the source file. The elements of the content tree are well structured and
13//!   order-independent and thus much better suited for further processing than
14//!   the raw markup.
15//! - **Layouting:**
16//!   Next, the content is [laid out] into a [`PagedDocument`] containing one
17//!   [frame] per page with items at fixed positions.
18//! - **Exporting:**
19//!   These frames can finally be exported into an output format (currently PDF,
20//!   PNG, SVG, and HTML).
21//!
22//! [tokens]: typst_syntax::SyntaxKind
23//! [parsed]: typst_syntax::parse
24//! [syntax tree]: typst_syntax::SyntaxNode
25//! [AST]: typst_syntax::ast
26//! [evaluate]: typst_eval::eval
27//! [module]: crate::foundations::Module
28//! [content]: crate::foundations::Content
29//! [laid out]: typst_layout::layout_document
30//! [document]: crate::model::Document
31//! [frame]: crate::layout::Frame
32
33pub extern crate comemo;
34pub extern crate ecow;
35
36pub use typst_library::*;
37#[doc(inline)]
38pub use typst_syntax as syntax;
39#[doc(inline)]
40pub use typst_utils as utils;
41
42use std::collections::HashSet;
43
44use comemo::{Track, Tracked, Validate};
45use ecow::{eco_format, eco_vec, EcoString, EcoVec};
46use typst_library::diag::{
47    bail, warning, FileError, SourceDiagnostic, SourceResult, Warned,
48};
49use typst_library::engine::{Engine, Route, Sink, Traced};
50use typst_library::foundations::{StyleChain, Styles, Value};
51use typst_library::html::HtmlDocument;
52use typst_library::introspection::Introspector;
53use typst_library::layout::PagedDocument;
54use typst_library::routines::Routines;
55use typst_syntax::{FileId, Span};
56use typst_timing::{timed, TimingScope};
57
58use crate::foundations::{Target, TargetElem};
59use crate::model::DocumentInfo;
60
61/// Compile sources into a fully layouted document.
62///
63/// - Returns `Ok(document)` if there were no fatal errors.
64/// - Returns `Err(errors)` if there were fatal errors.
65#[typst_macros::time]
66pub fn compile<D>(world: &dyn World) -> Warned<SourceResult<D>>
67where
68    D: Document,
69{
70    let mut sink = Sink::new();
71    let output = compile_impl::<D>(world.track(), Traced::default().track(), &mut sink)
72        .map_err(deduplicate);
73    Warned { output, warnings: sink.warnings() }
74}
75
76/// Compiles sources and returns all values and styles observed at the given
77/// `span` during compilation.
78#[typst_macros::time]
79pub fn trace<D>(world: &dyn World, span: Span) -> EcoVec<(Value, Option<Styles>)>
80where
81    D: Document,
82{
83    let mut sink = Sink::new();
84    let traced = Traced::new(span);
85    compile_impl::<D>(world.track(), traced.track(), &mut sink).ok();
86    sink.values()
87}
88
89/// The internal implementation of `compile` with a bit lower-level interface
90/// that is also used by `trace`.
91fn compile_impl<D: Document>(
92    world: Tracked<dyn World + '_>,
93    traced: Tracked<Traced>,
94    sink: &mut Sink,
95) -> SourceResult<D> {
96    if D::TARGET == Target::Html {
97        warn_or_error_for_html(world, sink)?;
98    }
99
100    let library = world.library();
101    let base = StyleChain::new(&library.styles);
102    let target = TargetElem::set_target(D::TARGET).wrap();
103    let styles = base.chain(&target);
104    let empty_introspector = Introspector::default();
105
106    // Fetch the main source file once.
107    let main = world.main();
108    let main = world
109        .source(main)
110        .map_err(|err| hint_invalid_main_file(world, err, main))?;
111
112    // First evaluate the main source file into a module.
113    let content = typst_eval::eval(
114        &ROUTINES,
115        world,
116        traced,
117        sink.track_mut(),
118        Route::default().track(),
119        &main,
120    )?
121    .content();
122
123    let mut iter = 0;
124    let mut subsink;
125    let mut introspector = &empty_introspector;
126    let mut document: D;
127
128    // Relayout until all introspections stabilize.
129    // If that doesn't happen within five attempts, we give up.
130    loop {
131        // The name of the iterations for timing scopes.
132        const ITER_NAMES: &[&str] =
133            &["layout (1)", "layout (2)", "layout (3)", "layout (4)", "layout (5)"];
134        let _scope = TimingScope::new(ITER_NAMES[iter]);
135
136        subsink = Sink::new();
137
138        let constraint = <Introspector as Validate>::Constraint::new();
139        let mut engine = Engine {
140            world,
141            introspector: introspector.track_with(&constraint),
142            traced,
143            sink: subsink.track_mut(),
144            route: Route::default(),
145            routines: &ROUTINES,
146        };
147
148        // Layout!
149        document = D::create(&mut engine, &content, styles)?;
150        introspector = document.introspector();
151        iter += 1;
152
153        if timed!("check stabilized", introspector.validate(&constraint)) {
154            break;
155        }
156
157        if iter >= 5 {
158            subsink.warn(warning!(
159                Span::detached(), "layout did not converge within 5 attempts";
160                hint: "check if any states or queries are updating themselves"
161            ));
162            break;
163        }
164    }
165
166    sink.extend_from_sink(subsink);
167
168    // Promote delayed errors.
169    let delayed = sink.delayed();
170    if !delayed.is_empty() {
171        return Err(delayed);
172    }
173
174    Ok(document)
175}
176
177/// Deduplicate diagnostics.
178fn deduplicate(mut diags: EcoVec<SourceDiagnostic>) -> EcoVec<SourceDiagnostic> {
179    let mut unique = HashSet::new();
180    diags.retain(|diag| {
181        let hash = typst_utils::hash128(&(&diag.span, &diag.message));
182        unique.insert(hash)
183    });
184    diags
185}
186
187/// Adds useful hints when the main source file couldn't be read
188/// and returns the final diagnostic.
189fn hint_invalid_main_file(
190    world: Tracked<dyn World + '_>,
191    file_error: FileError,
192    input: FileId,
193) -> EcoVec<SourceDiagnostic> {
194    let is_utf8_error = matches!(file_error, FileError::InvalidUtf8);
195    let mut diagnostic =
196        SourceDiagnostic::error(Span::detached(), EcoString::from(file_error));
197
198    // Attempt to provide helpful hints for UTF-8 errors. Perhaps the user
199    // mistyped the filename. For example, they could have written "file.pdf"
200    // instead of "file.typ".
201    if is_utf8_error {
202        let path = input.vpath();
203        let extension = path.as_rootless_path().extension();
204        if extension.is_some_and(|extension| extension == "typ") {
205            // No hints if the file is already a .typ file.
206            // The file is indeed just invalid.
207            return eco_vec![diagnostic];
208        }
209
210        match extension {
211            Some(extension) => {
212                diagnostic.hint(eco_format!(
213                    "a file with the `.{}` extension is not usually a Typst file",
214                    extension.to_string_lossy()
215                ));
216            }
217
218            None => {
219                diagnostic
220                    .hint("a file without an extension is not usually a Typst file");
221            }
222        };
223
224        if world.source(input.with_extension("typ")).is_ok() {
225            diagnostic.hint("check if you meant to use the `.typ` extension instead");
226        }
227    }
228
229    eco_vec![diagnostic]
230}
231
232/// HTML export will warn or error depending on whether the feature flag is enabled.
233fn warn_or_error_for_html(
234    world: Tracked<dyn World + '_>,
235    sink: &mut Sink,
236) -> SourceResult<()> {
237    const ISSUE: &str = "https://github.com/typst/typst/issues/5512";
238    if world.library().features.is_enabled(Feature::Html) {
239        sink.warn(warning!(
240            Span::detached(),
241            "html export is under active development and incomplete";
242            hint: "its behaviour may change at any time";
243            hint: "do not rely on this feature for production use cases";
244            hint: "see {ISSUE} for more information"
245        ));
246    } else {
247        bail!(
248            Span::detached(),
249            "html export is only available when `--features html` is passed";
250            hint: "html export is under active development and incomplete";
251            hint: "see {ISSUE} for more information"
252        );
253    }
254    Ok(())
255}
256
257/// A document is what results from compilation.
258pub trait Document: sealed::Sealed {
259    /// Get the document's metadata.
260    fn info(&self) -> &DocumentInfo;
261
262    /// Get the document's introspector.
263    fn introspector(&self) -> &Introspector;
264}
265
266impl Document for PagedDocument {
267    fn info(&self) -> &DocumentInfo {
268        &self.info
269    }
270
271    fn introspector(&self) -> &Introspector {
272        &self.introspector
273    }
274}
275
276impl Document for HtmlDocument {
277    fn info(&self) -> &DocumentInfo {
278        &self.info
279    }
280
281    fn introspector(&self) -> &Introspector {
282        &self.introspector
283    }
284}
285
286mod sealed {
287    use typst_library::foundations::{Content, Target};
288
289    use super::*;
290
291    pub trait Sealed: Sized {
292        const TARGET: Target;
293
294        fn create(
295            engine: &mut Engine,
296            content: &Content,
297            styles: StyleChain,
298        ) -> SourceResult<Self>;
299    }
300
301    impl Sealed for PagedDocument {
302        const TARGET: Target = Target::Paged;
303
304        fn create(
305            engine: &mut Engine,
306            content: &Content,
307            styles: StyleChain,
308        ) -> SourceResult<Self> {
309            typst_layout::layout_document(engine, content, styles)
310        }
311    }
312
313    impl Sealed for HtmlDocument {
314        const TARGET: Target = Target::Html;
315
316        fn create(
317            engine: &mut Engine,
318            content: &Content,
319            styles: StyleChain,
320        ) -> SourceResult<Self> {
321            typst_html::html_document(engine, content, styles)
322        }
323    }
324}
325
326/// Defines implementation of various Typst compiler routines as a table of
327/// function pointers.
328///
329/// This is essentially dynamic linking and done to allow for crate splitting.
330pub static ROUTINES: Routines = Routines {
331    eval_string: typst_eval::eval_string,
332    eval_closure: typst_eval::eval_closure,
333    realize: typst_realize::realize,
334    layout_fragment: typst_layout::layout_fragment,
335    layout_frame: typst_layout::layout_frame,
336    layout_list: typst_layout::layout_list,
337    layout_enum: typst_layout::layout_enum,
338    layout_grid: typst_layout::layout_grid,
339    layout_table: typst_layout::layout_table,
340    layout_stack: typst_layout::layout_stack,
341    layout_columns: typst_layout::layout_columns,
342    layout_move: typst_layout::layout_move,
343    layout_rotate: typst_layout::layout_rotate,
344    layout_scale: typst_layout::layout_scale,
345    layout_skew: typst_layout::layout_skew,
346    layout_repeat: typst_layout::layout_repeat,
347    layout_pad: typst_layout::layout_pad,
348    layout_line: typst_layout::layout_line,
349    layout_curve: typst_layout::layout_curve,
350    layout_path: typst_layout::layout_path,
351    layout_polygon: typst_layout::layout_polygon,
352    layout_rect: typst_layout::layout_rect,
353    layout_square: typst_layout::layout_square,
354    layout_ellipse: typst_layout::layout_ellipse,
355    layout_circle: typst_layout::layout_circle,
356    layout_image: typst_layout::layout_image,
357    layout_equation_block: typst_layout::layout_equation_block,
358    layout_equation_inline: typst_layout::layout_equation_inline,
359};