1pub extern crate comemo;
33pub extern crate ecow;
34
35pub use typst_library::*;
36#[doc(inline)]
37pub use typst_syntax as syntax;
38#[doc(inline)]
39pub use typst_utils as utils;
40
41use std::sync::LazyLock;
42
43use comemo::{Track, Tracked};
44use ecow::{EcoString, EcoVec, eco_format, eco_vec};
45use rustc_hash::FxHashSet;
46use typst_html::HtmlDocument;
47use typst_library::diag::{
48 FileError, SourceDiagnostic, SourceResult, Warned, bail, warning,
49};
50use typst_library::engine::{Engine, Route, Sink, Traced};
51use typst_library::foundations::{NativeRuleMap, StyleChain, Styles, Value};
52use typst_library::introspection::Introspector;
53use typst_library::layout::PagedDocument;
54use typst_library::routines::Routines;
55use typst_syntax::{FileId, Span};
56use typst_timing::{TimingScope, timed};
57
58use crate::foundations::{Target, TargetElem};
59use crate::model::DocumentInfo;
60
61#[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#[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
89fn 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::target.set(D::TARGET).wrap();
103 let styles = base.chain(&target);
104 let empty_introspector = Introspector::default();
105
106 let main = world.main();
108 let main = world
109 .source(main)
110 .map_err(|err| hint_invalid_main_file(world, err, main))?;
111
112 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 loop {
131 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 = comemo::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 document = D::create(&mut engine, &content, styles)?;
150 introspector = document.introspector();
151 iter += 1;
152
153 if timed!("check stabilized", constraint.validate(introspector)) {
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 let delayed = sink.delayed();
170 if !delayed.is_empty() {
171 return Err(delayed);
172 }
173
174 Ok(document)
175}
176
177fn deduplicate(mut diags: EcoVec<SourceDiagnostic>) -> EcoVec<SourceDiagnostic> {
179 let mut unique = FxHashSet::default();
180 diags.retain(|diag| {
181 let hash = typst_utils::hash128(&(&diag.span, &diag.message));
182 unique.insert(hash)
183 });
184 diags
185}
186
187fn 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 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 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
232fn 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
257pub trait Document: sealed::Sealed {
259 fn info(&self) -> &DocumentInfo;
261
262 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
326pub trait LibraryExt {
328 fn default() -> Library;
330
331 fn builder() -> LibraryBuilder;
333}
334
335impl LibraryExt for Library {
336 fn default() -> Library {
337 Self::builder().build()
338 }
339
340 fn builder() -> LibraryBuilder {
341 LibraryBuilder::from_routines(&ROUTINES)
342 }
343}
344
345pub static ROUTINES: LazyLock<Routines> = LazyLock::new(|| Routines {
350 rules: {
351 let mut rules = NativeRuleMap::new();
352 typst_layout::register(&mut rules);
353 typst_html::register(&mut rules);
354 rules
355 },
356 eval_string: typst_eval::eval_string,
357 eval_closure: typst_eval::eval_closure,
358 realize: typst_realize::realize,
359 layout_frame: typst_layout::layout_frame,
360 html_module: typst_html::module,
361 html_span_filled: typst_html::html_span_filled,
362});