use std::sync::Arc;
use typst::diag::{SourceDiagnostic, Tracepoint};
use crate::engine::world::{FontCache, ReportWorld};
use crate::error::{Error, RenderError};
use crate::render::RenderRequest;
pub fn compile_to_pdf(
source: &str,
font_cache: &Arc<FontCache>,
request: &RenderRequest,
) -> crate::Result<Vec<u8>> {
compile_with_files(source, font_cache, request, std::iter::empty())
}
pub(crate) fn compile_with_files<'a>(
source: &str,
font_cache: &Arc<FontCache>,
request: &RenderRequest,
files: impl IntoIterator<Item = (&'a str, &'a [u8])>,
) -> crate::Result<Vec<u8>> {
let world = ReportWorld::new(source.to_string(), font_cache.clone());
for (name, bytes) in files {
world.add_file(name, bytes);
}
for (name, path) in &request.assets {
if let Ok(bytes) = std::fs::read(path) {
world.add_file(name, bytes);
}
}
let result = typst::compile(&world);
match result.output {
Ok(document) => {
let standards =
typst_pdf::PdfStandards::new(&[typst_pdf::PdfStandard::Ua_1]).map_err(|e| {
Error::Render(RenderError::TypstCompilation(e.message().to_string()))
})?;
let options = typst_pdf::PdfOptions {
standards,
..Default::default()
};
let pdf_result = typst_pdf::pdf(&document, &options);
match pdf_result {
Ok(pdf_bytes) => Ok(pdf_bytes),
Err(errors) => Err(Error::Render(RenderError::TypstCompilation(
describe_diagnostics(&errors),
))),
}
}
Err(errors) => Err(Error::Render(RenderError::TypstCompilation(
describe_diagnostics(&errors),
))),
}
}
fn describe_diagnostics(diagnostics: &[SourceDiagnostic]) -> String {
diagnostics
.iter()
.map(|diagnostic| {
let mut line = diagnostic.message.to_string();
if let Some(component) = failing_component(diagnostic) {
line.push_str(&format!(" (in component `{component}`)"));
}
for hint in &diagnostic.hints {
line.push_str(&format!("; hint: {}", hint.v));
}
line
})
.collect::<Vec<_>>()
.join("\n")
}
fn failing_component(diagnostic: &SourceDiagnostic) -> Option<&str> {
let name = diagnostic
.trace
.iter()
.rev()
.find_map(|point| match &point.v {
Tracepoint::Call(Some(name)) => Some(name.as_str()),
_ => None,
})?;
Some(if name == "report-image" {
"image"
} else {
name
})
}