pub extern crate comemo;
pub extern crate ecow;
pub use typst_library::*;
#[doc(inline)]
pub use typst_syntax as syntax;
#[doc(inline)]
pub use typst_utils as utils;
use std::sync::LazyLock;
use arrayvec::ArrayVec;
use comemo::{Track, Tracked};
use ecow::{EcoString, EcoVec, eco_format, eco_vec};
use rustc_hash::FxHashSet;
use typst_library::diag::{
FileError, SourceDiagnostic, SourceResult, Warned, bail, warning,
};
use typst_library::engine::{Engine, Route, Sink, Traced};
use typst_library::foundations::{
NativeRuleMap, Output, StyleChain, Styles, Target, TargetElem, Value,
};
use typst_library::introspection::{
EmptyIntrospector, ITER_NAMES, Introspector, MAX_ITERS,
};
use typst_library::routines::Routines;
use typst_syntax::{FileId, Span};
use typst_timing::{TimingScope, timed};
use typst_utils::Protected;
#[typst_macros::time]
pub fn compile<T>(world: &dyn World) -> Warned<SourceResult<T>>
where
T: Output,
{
let mut sink = Sink::new();
let output = compile_impl::<T>(world.track(), Traced::default().track(), &mut sink)
.map_err(deduplicate);
Warned { output, warnings: sink.warnings() }
}
#[typst_macros::time]
pub fn trace<T>(world: &dyn World, span: Span) -> EcoVec<(Value, Option<Styles>)>
where
T: Output,
{
let mut sink = Sink::new();
let traced = Traced::new(span);
compile_impl::<T>(world.track(), traced.track(), &mut sink).ok();
sink.values()
}
fn compile_impl<T: Output>(
world: Tracked<dyn World + '_>,
traced: Tracked<Traced>,
sink: &mut Sink,
) -> SourceResult<T> {
let library = world.library();
match T::target() {
Target::Paged => {}
Target::Html => warn_or_error_for_html(&library.features, sink)?,
Target::Bundle => warn_or_error_for_bundle(&library.features, sink)?,
}
let base = StyleChain::new(&library.styles);
let target = TargetElem::target.set(T::target()).wrap();
let styles = base.chain(&target);
let empty_introspector = EmptyIntrospector;
let main = world.main();
let main = world
.source(main)
.map_err(|err| hint_invalid_main_file(world, err, main))?;
let content = typst_eval::eval(
world,
library,
traced,
sink.track_mut(),
Route::default().track(),
&main,
)?
.content();
let mut history: ArrayVec<T, { MAX_ITERS - 1 }> = ArrayVec::new();
let mut document: T;
loop {
let _scope = TimingScope::new(ITER_NAMES[history.len()]);
let introspector = history
.last()
.map(|doc| doc.introspector())
.unwrap_or(&empty_introspector);
let constraint = comemo::Constraint::new();
let mut subsink = Sink::new();
let mut engine = Engine {
library,
world,
introspector: Protected::new(introspector.track_with(&constraint)),
traced,
sink: subsink.track_mut(),
route: Route::default(),
};
document = T::create(&mut engine, &content, styles)?;
if timed!("check stabilized", constraint.validate(document.introspector())) {
sink.extend_from_sink(subsink);
break;
}
if history.is_full() {
let mut introspectors =
[&empty_introspector as &dyn Introspector; MAX_ITERS + 1];
for i in 1..MAX_ITERS {
introspectors[i] = history[i - 1].introspector();
}
introspectors[MAX_ITERS] = document.introspector();
let warnings = typst_library::introspection::analyze(
world,
introspectors,
subsink.introspections(),
);
sink.extend_from_sink(subsink);
for warning in warnings {
sink.warn(warning);
}
break;
}
history.push(document);
}
let delayed = sink.delayed();
if !delayed.is_empty() {
return Err(delayed);
}
Ok(document)
}
fn deduplicate(mut diags: EcoVec<SourceDiagnostic>) -> EcoVec<SourceDiagnostic> {
let mut unique = FxHashSet::default();
diags.retain(|diag| {
let hash = typst_utils::hash128(&(&diag.span, &diag.message));
unique.insert(hash)
});
diags
}
fn hint_invalid_main_file(
world: Tracked<dyn World + '_>,
file_error: FileError,
input: FileId,
) -> EcoVec<SourceDiagnostic> {
let is_utf8_error = matches!(file_error, FileError::InvalidUtf8);
let mut diagnostic =
SourceDiagnostic::error(Span::detached(), EcoString::from(file_error));
if is_utf8_error {
match input.vpath().extension() {
Some("typ") => return eco_vec![diagnostic],
Some(ext) => {
diagnostic.hint(eco_format!(
"a file with the `.{ext}` extension is not usually a Typst file",
));
}
None => {
diagnostic
.hint("a file without an extension is not usually a Typst file");
}
};
if world.source(input.map(|p| p.with_extension("typ")).intern()).is_ok() {
diagnostic.hint("check if you meant to use the `.typ` extension instead");
}
}
eco_vec![diagnostic]
}
fn warn_or_error_for_html(features: &Features, sink: &mut Sink) -> SourceResult<()> {
const ISSUE: &str = "https://github.com/typst/typst/issues/5512";
if features.is_enabled(Feature::Html) {
sink.warn(warning!(
Span::detached(),
"html export is under active development and incomplete";
hint: "its behaviour may change at any time";
hint: "do not rely on this feature for production use cases";
hint: "see {ISSUE} for more information";
));
} else {
bail!(
Span::detached(),
"html export is only available when `--features html` is passed";
hint: "html export is under active development and incomplete";
hint: "see {ISSUE} for more information";
);
}
Ok(())
}
fn warn_or_error_for_bundle(features: &Features, sink: &mut Sink) -> SourceResult<()> {
if features.is_enabled(Feature::Bundle) {
sink.warn(warning!(
Span::detached(),
"bundle export is experimental";
hint: "its behaviour may change at any time";
hint: "do not rely on this feature for production use cases";
));
} else {
bail!(
Span::detached(),
"bundle export is only available when `--features bundle` is passed";
hint: "bundle export is experimental";
);
}
Ok(())
}
pub trait LibraryExt {
fn default() -> Library;
fn builder() -> LibraryBuilder;
}
impl LibraryExt for Library {
fn default() -> Library {
Self::builder().build()
}
fn builder() -> LibraryBuilder {
LibraryBuilder::from_routines(&ROUTINES)
}
}
static ROUTINES: LazyLock<Routines> = LazyLock::new(|| Routines {
rules: || {
let mut rules = NativeRuleMap::new();
typst_layout::register(&mut rules);
typst_html::register(&mut rules);
rules
},
eval_string: typst_eval::eval_string,
eval_closure: typst_eval::eval_closure,
realize: typst_realize::realize,
layout_frame: typst_layout::layout_frame,
html_module: typst_html::module,
html_mathml_body: typst_html::html_mathml_body,
html_span_filled: typst_html::html_span_filled,
});