1use crate::error::{Error, Result};
4use crate::resolver::EmbeddedResolver;
5use crate::stats::EmbedStats;
6use crate::util::decompress;
7use include_dir::Dir;
8use std::sync::{Mutex, MutexGuard};
9use typst::foundations::Dict;
10use typst::layout::PagedDocument;
11use typst_as_lib::{TypstAsLibError, TypstEngine};
12
13pub struct Document {
18 templates: &'static Dir<'static>,
19 packages: &'static Dir<'static>,
20 fonts: &'static Dir<'static>,
21 entry: &'static str,
22 inputs: Mutex<Option<Dict>>,
23 stats: EmbedStats,
24 compiled_cache: Mutex<Option<PagedDocument>>,
25}
26
27impl Document {
28 #[doc(hidden)]
31 pub fn __new(
32 templates: &'static Dir<'static>,
33 packages: &'static Dir<'static>,
34 fonts: &'static Dir<'static>,
35 entry: &'static str,
36 stats: EmbedStats,
37 ) -> Self {
38 Self {
39 templates,
40 packages,
41 fonts,
42 entry,
43 inputs: Mutex::new(None),
44 stats,
45 compiled_cache: Mutex::new(None),
46 }
47 }
48
49 fn lock_inputs(&self) -> MutexGuard<'_, Option<Dict>> {
50 self.inputs.lock().expect("lock poisoned")
51 }
52
53 fn lock_cache(&self) -> MutexGuard<'_, Option<PagedDocument>> {
54 self.compiled_cache.lock().expect("lock poisoned")
55 }
56
57 pub fn with_inputs<T: Into<Dict>>(self, inputs: T) -> Self {
98 *self.lock_inputs() = Some(inputs.into());
99 *self.lock_cache() = None;
100 self
101 }
102
103 pub fn stats(&self) -> &EmbedStats {
105 &self.stats
106 }
107
108 fn compile_cached(&self) -> Result<()> {
110 if self.lock_cache().is_some() {
111 return Ok(());
112 }
113
114 let main_file = self
116 .templates
117 .get_file(self.entry)
118 .ok_or(Error::EntryNotFound(self.entry))?;
119
120 let main_bytes = decompress(main_file.contents())?;
121 let main_content = std::str::from_utf8(&main_bytes).map_err(|_| Error::InvalidUtf8)?;
122
123 let resolver = EmbeddedResolver::new(self.templates, self.packages);
124
125 let font_data: Vec<Vec<u8>> = self
127 .fonts
128 .files()
129 .map(|f| decompress(f.contents()).map_err(Error::from))
130 .collect::<Result<Vec<_>>>()?;
131
132 let font_refs: Vec<&[u8]> = font_data.iter().map(Vec::as_slice).collect();
133
134 let engine = TypstEngine::builder()
135 .main_file(main_content)
136 .add_file_resolver(resolver)
137 .fonts(font_refs)
138 .build();
139
140 let inputs = self.lock_inputs().clone();
142
143 let warned_result = if let Some(inputs) = inputs {
144 engine.compile_with_input::<_, PagedDocument>(inputs)
145 } else {
146 engine.compile::<PagedDocument>()
147 };
148
149 let compiled = warned_result.output.map_err(|e| {
151 let msg = match e {
152 TypstAsLibError::TypstSource(diagnostics) => diagnostics
153 .iter()
154 .map(|d| d.message.as_str())
155 .collect::<Vec<_>>()
156 .join("\n"),
157 other => other.to_string(),
158 };
159 Error::Compilation(msg)
160 })?;
161
162 *self.lock_cache() = Some(compiled);
163
164 Ok(())
165 }
166
167 fn with_compiled<F, T>(&self, f: F) -> Result<T>
169 where
170 F: FnOnce(&PagedDocument) -> Result<T>,
171 {
172 self.compile_cached()?;
173 let cache = self.lock_cache();
174 let compiled = cache
175 .as_ref()
176 .expect("compiled_cache must be Some after successful compile_cached()");
177 f(compiled)
178 }
179
180 #[cfg(feature = "pdf")]
188 #[cfg_attr(docsrs, doc(cfg(feature = "pdf")))]
189 pub fn to_pdf(&self) -> Result<Vec<u8>> {
190 self.with_compiled(|compiled| {
191 typst_pdf::pdf(compiled, &typst_pdf::PdfOptions::default())
192 .map_err(|e| Error::PdfGeneration(format!("{e:?}")))
193 })
194 }
195
196 #[cfg(feature = "svg")]
204 #[cfg_attr(docsrs, doc(cfg(feature = "svg")))]
205 pub fn to_svg(&self) -> Result<Vec<String>> {
206 self.with_compiled(|compiled| Ok(compiled.pages.iter().map(typst_svg::svg).collect()))
207 }
208
209 #[cfg(feature = "png")]
220 #[cfg_attr(docsrs, doc(cfg(feature = "png")))]
221 pub fn to_png(&self, dpi: f32) -> Result<Vec<Vec<u8>>> {
222 self.with_compiled(|compiled| {
223 let pixel_per_pt = dpi / 72.0;
224 compiled
225 .pages
226 .iter()
227 .map(|page| {
228 typst_render::render(page, pixel_per_pt)
229 .encode_png()
230 .map_err(|e| Error::PngEncoding(e.to_string()))
231 })
232 .collect()
233 })
234 }
235}