1pub 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#[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::set_target(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 = <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 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 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 = 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
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 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};