use std::sync::Arc;
use serde::Serialize;
use typst::World;
use typst::WorldExt;
use typst_pdf::PdfOptions;
use crate::RenderFileSystem;
use crate::error::{CompilationError, PapermakeError, Result};
use crate::typst::PapermakeWorld;
#[derive(Debug, Serialize, Clone)]
pub struct RenderError {
pub message: String,
pub start: usize,
pub end: usize,
pub file: Option<String>,
}
impl std::fmt::Display for RenderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.file {
Some(file) => write!(f, "{}:{}-{}: {}", file, self.start, self.end, self.message),
None => write!(f, "{}:{}: {}", self.start, self.end, self.message),
}
}
}
#[derive(Debug, Serialize)]
pub struct RenderResult {
pub pdf: Option<Vec<u8>>,
pub errors: Vec<RenderError>,
pub success: bool,
}
pub fn render_template(
main_typ: String,
file_system: Arc<dyn RenderFileSystem>,
data: &serde_json::Value,
) -> Result<RenderResult> {
let data_str = serde_json::to_string(&data)?;
let world = PapermakeWorld::with_file_system(main_typ, data_str, file_system);
let compile_result = typst::compile(&world);
let mut errors = Vec::new();
let mut pdf = None;
let mut success = false;
match compile_result.output {
Ok(document) => {
match typst_pdf::pdf(&document, &PdfOptions::default()) {
Ok(pdf_bytes) => {
pdf = Some(pdf_bytes);
success = true;
}
Err(pdf_error) => {
errors.push(RenderError {
message: format!("PDF generation failed: {:?}", pdf_error),
start: 0,
end: 0,
file: None,
});
}
}
}
Err(diagnostics) => {
for diagnostic in diagnostics {
let span = diagnostic.span;
let mut render_error = RenderError {
message: diagnostic.message.to_string(),
start: 0,
end: 0,
file: None,
};
if let Some(id) = span.id() {
if let Ok(_source) = world.source(id) {
render_error.file = Some(format!("{:?}", id));
if let Some(range) = world.range(span) {
render_error.start = range.start;
render_error.end = range.end;
}
}
}
errors.push(render_error);
}
}
}
Ok(RenderResult {
pdf,
errors,
success,
})
}
pub fn render_template_with_cache(
main_typ: String,
file_system: Arc<dyn RenderFileSystem>,
data: serde_json::Value,
world_cache: Option<&mut PapermakeWorld>,
) -> Result<RenderResult> {
let data_str = serde_json::to_string(&data)?;
let world = match world_cache {
Some(cached_world) => {
cached_world.update_data(data_str).map_err(|e| {
PapermakeError::Compilation(CompilationError::DataInjection {
reason: format!("Failed to update cached world data: {}", e),
})
})?;
cached_world
}
None => {
return render_template(main_typ, file_system, &data);
}
};
let compile_result = typst::compile(world as &dyn World);
let mut errors = Vec::new();
let mut pdf = None;
let mut success = false;
match compile_result.output {
Ok(document) => match typst_pdf::pdf(&document, &PdfOptions::default()) {
Ok(pdf_bytes) => {
pdf = Some(pdf_bytes);
success = true;
}
Err(pdf_error) => {
errors.push(RenderError {
message: format!("PDF generation failed: {:?}", pdf_error),
start: 0,
end: 0,
file: None,
});
}
},
Err(diagnostics) => {
for diagnostic in diagnostics {
let span = diagnostic.span;
let mut render_error = RenderError {
message: diagnostic.message.to_string(),
start: 0,
end: 0,
file: None,
};
if let Some(id) = span.id() {
if let Ok(_source) = world.source(id) {
render_error.file = Some(format!("{:?}", id));
if let Some(range) = world.range(span) {
render_error.start = range.start;
render_error.end = range.end;
}
}
}
errors.push(render_error);
}
}
}
Ok(RenderResult {
pdf,
errors,
success,
})
}