1use crate::resolver::EmbeddedResolver;
4use crate::stats::EmbedStats;
5use include_dir::Dir;
6use std::io::Cursor;
7use typst::foundations::Dict;
8use typst_as_lib::TypstEngine;
9
10pub struct Document {
14 templates: &'static Dir<'static>,
15 packages: &'static Dir<'static>,
16 fonts: &'static Dir<'static>,
17 entry: String,
18 inputs: Option<Dict>,
19 stats: EmbedStats,
20}
21
22impl Document {
23 #[doc(hidden)]
26 pub fn __new(
27 templates: &'static Dir<'static>,
28 packages: &'static Dir<'static>,
29 fonts: &'static Dir<'static>,
30 entry: &str,
31 stats: EmbedStats,
32 ) -> Self {
33 Self {
34 templates,
35 packages,
36 fonts,
37 entry: entry.to_string(),
38 inputs: None,
39 stats,
40 }
41 }
42
43 pub fn with_inputs<T: Into<Dict>>(mut self, inputs: T) -> Self {
60 self.inputs = Some(inputs.into());
61 self
62 }
63
64 pub fn stats(&self) -> &EmbedStats {
66 &self.stats
67 }
68
69 pub fn to_pdf(self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
77 let main_file = self
79 .templates
80 .get_file(&self.entry)
81 .ok_or_else(|| format!("Entry file not found: {}", self.entry))?;
82
83 let main_bytes = decompress(main_file.contents())?;
85 let main_content =
86 std::str::from_utf8(&main_bytes).map_err(|_| "Entry file is not valid UTF-8")?;
87
88 let resolver = EmbeddedResolver::new(self.templates, self.packages);
90
91 let font_data: Vec<Vec<u8>> = self
93 .fonts
94 .files()
95 .map(|f| decompress(f.contents()).expect("Font decompression failed"))
96 .collect();
97
98 let font_refs: Vec<&[u8]> = font_data.iter().map(|v| v.as_slice()).collect();
99
100 let builder = TypstEngine::builder()
102 .main_file(main_content)
103 .add_file_resolver(resolver)
104 .fonts(font_refs);
105
106 let engine = builder.build();
107
108 use typst::layout::PagedDocument;
111 let warned_result = if let Some(inputs) = self.inputs {
112 engine.compile_with_input::<_, PagedDocument>(inputs)
113 } else {
114 engine.compile::<PagedDocument>()
115 };
116
117 let compiled = warned_result
119 .output
120 .map_err(|e| format!("Compilation failed: {:?}", e))?;
121
122 let pdf_bytes = typst_pdf::pdf(&compiled, &typst_pdf::PdfOptions::default())
124 .map_err(|e| format!("PDF generation failed: {:?}", e))?;
125
126 Ok(pdf_bytes)
127 }
128}
129
130fn decompress(data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
132 let decompressed = zstd::decode_all(Cursor::new(data))?;
133 Ok(decompressed)
134}