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::sync::LazyLock;
43
44use arrayvec::ArrayVec;
45use comemo::{Track, Tracked};
46use ecow::{EcoString, EcoVec, eco_format, eco_vec};
47use rustc_hash::FxHashSet;
48use typst_library::diag::{
49 FileError, SourceDiagnostic, SourceResult, Warned, bail, warning,
50};
51use typst_library::engine::{Engine, Route, Sink, Traced};
52use typst_library::foundations::{
53 NativeRuleMap, Output, StyleChain, Styles, Target, TargetElem, Value,
54};
55use typst_library::introspection::{
56 EmptyIntrospector, ITER_NAMES, Introspector, MAX_ITERS,
57};
58use typst_library::routines::Routines;
59use typst_syntax::{FileId, Span};
60use typst_timing::{TimingScope, timed};
61use typst_utils::Protected;
62
63#[typst_macros::time]
74pub fn compile<T>(world: &dyn World) -> Warned<SourceResult<T>>
75where
76 T: Output,
77{
78 let mut sink = Sink::new();
79 let output = compile_impl::<T>(world.track(), Traced::default().track(), &mut sink)
80 .map_err(deduplicate);
81 Warned { output, warnings: sink.warnings() }
82}
83
84#[typst_macros::time]
87pub fn trace<T>(world: &dyn World, span: Span) -> EcoVec<(Value, Option<Styles>)>
88where
89 T: Output,
90{
91 let mut sink = Sink::new();
92 let traced = Traced::new(span);
93 compile_impl::<T>(world.track(), traced.track(), &mut sink).ok();
94 sink.values()
95}
96
97fn compile_impl<T: Output>(
100 world: Tracked<dyn World + '_>,
101 traced: Tracked<Traced>,
102 sink: &mut Sink,
103) -> SourceResult<T> {
104 let library = world.library();
105 match T::target() {
106 Target::Paged => {}
107 Target::Html => warn_or_error_for_html(&library.features, sink)?,
108 Target::Bundle => warn_or_error_for_bundle(&library.features, sink)?,
109 }
110
111 let base = StyleChain::new(&library.styles);
112 let target = TargetElem::target.set(T::target()).wrap();
113 let styles = base.chain(&target);
114 let empty_introspector = EmptyIntrospector;
115
116 let main = world.main();
118 let main = world
119 .source(main)
120 .map_err(|err| hint_invalid_main_file(world, err, main))?;
121
122 let content = typst_eval::eval(
124 world,
125 library,
126 traced,
127 sink.track_mut(),
128 Route::default().track(),
129 &main,
130 )?
131 .content();
132
133 let mut history: ArrayVec<T, { MAX_ITERS - 1 }> = ArrayVec::new();
134 let mut document: T;
135
136 loop {
139 let _scope = TimingScope::new(ITER_NAMES[history.len()]);
140 let introspector = history
141 .last()
142 .map(|doc| doc.introspector())
143 .unwrap_or(&empty_introspector);
144 let constraint = comemo::Constraint::new();
145
146 let mut subsink = Sink::new();
147 let mut engine = Engine {
148 library,
149 world,
150 introspector: Protected::new(introspector.track_with(&constraint)),
151 traced,
152 sink: subsink.track_mut(),
153 route: Route::default(),
154 };
155
156 document = T::create(&mut engine, &content, styles)?;
157
158 if timed!("check stabilized", constraint.validate(document.introspector())) {
159 sink.extend_from_sink(subsink);
160 break;
161 }
162
163 if history.is_full() {
164 let mut introspectors =
165 [&empty_introspector as &dyn Introspector; MAX_ITERS + 1];
166 for i in 1..MAX_ITERS {
167 introspectors[i] = history[i - 1].introspector();
168 }
169 introspectors[MAX_ITERS] = document.introspector();
170
171 let warnings = typst_library::introspection::analyze(
172 world,
173 introspectors,
174 subsink.introspections(),
175 );
176
177 sink.extend_from_sink(subsink);
178 for warning in warnings {
179 sink.warn(warning);
180 }
181 break;
182 }
183
184 history.push(document);
185 }
186
187 let delayed = sink.delayed();
189 if !delayed.is_empty() {
190 return Err(delayed);
191 }
192
193 Ok(document)
194}
195
196fn deduplicate(mut diags: EcoVec<SourceDiagnostic>) -> EcoVec<SourceDiagnostic> {
198 let mut unique = FxHashSet::default();
199 diags.retain(|diag| {
200 let hash = typst_utils::hash128(&(&diag.span, &diag.message));
201 unique.insert(hash)
202 });
203 diags
204}
205
206fn hint_invalid_main_file(
209 world: Tracked<dyn World + '_>,
210 file_error: FileError,
211 input: FileId,
212) -> EcoVec<SourceDiagnostic> {
213 let is_utf8_error = matches!(file_error, FileError::InvalidUtf8);
214 let mut diagnostic =
215 SourceDiagnostic::error(Span::detached(), EcoString::from(file_error));
216
217 if is_utf8_error {
221 match input.vpath().extension() {
222 Some("typ") => return eco_vec![diagnostic],
225
226 Some(ext) => {
227 diagnostic.hint(eco_format!(
228 "a file with the `.{ext}` extension is not usually a Typst file",
229 ));
230 }
231
232 None => {
233 diagnostic
234 .hint("a file without an extension is not usually a Typst file");
235 }
236 };
237
238 if world.source(input.map(|p| p.with_extension("typ")).intern()).is_ok() {
239 diagnostic.hint("check if you meant to use the `.typ` extension instead");
240 }
241 }
242
243 eco_vec![diagnostic]
244}
245
246fn warn_or_error_for_html(features: &Features, sink: &mut Sink) -> SourceResult<()> {
248 const ISSUE: &str = "https://github.com/typst/typst/issues/5512";
249 if features.is_enabled(Feature::Html) {
250 sink.warn(warning!(
251 Span::detached(),
252 "html export is under active development and incomplete";
253 hint: "its behaviour may change at any time";
254 hint: "do not rely on this feature for production use cases";
255 hint: "see {ISSUE} for more information";
256 ));
257 } else {
258 bail!(
259 Span::detached(),
260 "html export is only available when `--features html` is passed";
261 hint: "html export is under active development and incomplete";
262 hint: "see {ISSUE} for more information";
263 );
264 }
265 Ok(())
266}
267
268fn warn_or_error_for_bundle(features: &Features, sink: &mut Sink) -> SourceResult<()> {
271 if features.is_enabled(Feature::Bundle) {
272 sink.warn(warning!(
273 Span::detached(),
274 "bundle export is experimental";
275 hint: "its behaviour may change at any time";
276 hint: "do not rely on this feature for production use cases";
277 ));
278 } else {
279 bail!(
280 Span::detached(),
281 "bundle export is only available when `--features bundle` is passed";
282 hint: "bundle export is experimental";
283 );
284 }
285 Ok(())
286}
287
288pub trait LibraryExt {
290 fn default() -> Library;
292
293 fn builder() -> LibraryBuilder;
295}
296
297impl LibraryExt for Library {
298 fn default() -> Library {
299 Self::builder().build()
300 }
301
302 fn builder() -> LibraryBuilder {
303 LibraryBuilder::from_routines(&ROUTINES)
304 }
305}
306
307static ROUTINES: LazyLock<Routines> = LazyLock::new(|| Routines {
312 rules: || {
313 let mut rules = NativeRuleMap::new();
314 typst_layout::register(&mut rules);
315 typst_html::register(&mut rules);
316 rules
317 },
318 eval_string: typst_eval::eval_string,
319 eval_closure: typst_eval::eval_closure,
320 realize: typst_realize::realize,
321 layout_frame: typst_layout::layout_frame,
322 html_module: typst_html::module,
323 html_mathml_body: typst_html::html_mathml_body,
324 html_span_filled: typst_html::html_span_filled,
325});