tinymist_task/compute/
pdf.rs1use tinymist_std::time::ToUtcDateTime;
4use tinymist_world::args::PdfStandard;
5pub use typst_pdf::PdfStandard as TypstPdfStandard;
6pub use typst_pdf::pdf;
7
8use typst_pdf::{PdfOptions, PdfStandards, Timestamp};
9
10use super::*;
11use crate::model::ExportPdfTask;
12
13pub struct PdfExport;
15
16impl<F: CompilerFeat> ExportComputation<F, TypstPagedDocument> for PdfExport {
17 type Output = Bytes;
18 type Config = ExportPdfTask;
19
20 fn run(
21 _graph: &Arc<WorldComputeGraph<F>>,
22 doc: &Arc<TypstPagedDocument>,
23 config: &ExportPdfTask,
24 ) -> Result<Bytes> {
25 let options = pdf_options(
26 config.pages.as_deref(),
27 &config.pdf_standards,
28 config.no_pdf_tags,
29 config.creation_timestamp,
30 )?;
31
32 Ok(Bytes::new(typst_pdf::pdf(doc, &options)?))
37 }
38}
39
40pub fn pdf_options(
42 pages: Option<&[Pages]>,
43 pdf_standards: &[PdfStandard],
44 no_pdf_tags: bool,
45 creation_timestamp: Option<i64>,
46) -> Result<PdfOptions> {
47 let creation_timestamp = creation_timestamp
48 .map(|ts| ts.to_utc_datetime().context("timestamp is out of range"))
49 .transpose()?
50 .unwrap_or_else(tinymist_std::time::utc_now);
51 let timestamp = Timestamp::new_utc(tinymist_std::time::to_typst_time(creation_timestamp));
54
55 let standards = PdfStandards::new(
56 &pdf_standards
57 .iter()
58 .map(|standard| match standard {
59 PdfStandard::V_1_4 => typst_pdf::PdfStandard::V_1_4,
60 PdfStandard::V_1_5 => typst_pdf::PdfStandard::V_1_5,
61 PdfStandard::V_1_6 => typst_pdf::PdfStandard::V_1_6,
62 PdfStandard::V_1_7 => typst_pdf::PdfStandard::V_1_7,
63 PdfStandard::V_2_0 => typst_pdf::PdfStandard::V_2_0,
64 PdfStandard::A_1b => typst_pdf::PdfStandard::A_1b,
65 PdfStandard::A_1a => typst_pdf::PdfStandard::A_1a,
66 PdfStandard::A_2b => typst_pdf::PdfStandard::A_2b,
67 PdfStandard::A_2u => typst_pdf::PdfStandard::A_2u,
68 PdfStandard::A_2a => typst_pdf::PdfStandard::A_2a,
69 PdfStandard::A_3b => typst_pdf::PdfStandard::A_3b,
70 PdfStandard::A_3u => typst_pdf::PdfStandard::A_3u,
71 PdfStandard::A_3a => typst_pdf::PdfStandard::A_3a,
72 PdfStandard::A_4 => typst_pdf::PdfStandard::A_4,
73 PdfStandard::A_4f => typst_pdf::PdfStandard::A_4f,
74 PdfStandard::A_4e => typst_pdf::PdfStandard::A_4e,
75 PdfStandard::Ua_1 => typst_pdf::PdfStandard::Ua_1,
76 })
77 .collect::<Vec<_>>(),
78 )
79 .map_err(|err| err.message().clone())
80 .context("prepare pdf standards")?;
81
82 let tagged = !no_pdf_tags && pages.is_none();
83 if pages.is_some() && !no_pdf_tags {
85 log::warn!(
86 "the resulting PDF will be inaccessible because using --pages implies --no-pdf-tags"
87 );
88 }
89 if !tagged {
90 const ACCESSIBLE: &[(PdfStandard, &str)] = &[
91 (PdfStandard::A_1a, "PDF/A-1a"),
92 (PdfStandard::A_2a, "PDF/A-2a"),
93 (PdfStandard::A_3a, "PDF/A-3a"),
94 (PdfStandard::Ua_1, "PDF/UA-1"),
95 ];
96
97 for (standard, name) in ACCESSIBLE {
98 if pdf_standards.contains(standard) {
99 if no_pdf_tags {
100 log::warn!("cannot disable PDF tags when exporting a {name} document");
101 } else {
102 log::warn!(
103 "cannot disable PDF tags when exporting a {name} document. Hint: using --pages implies --no-pdf-tags"
104 );
105 }
106 }
107 }
108 }
109
110 Ok(PdfOptions {
111 page_ranges: pages.map(exported_page_ranges),
112 timestamp: Some(timestamp),
113 standards,
114 tagged,
115 ..Default::default()
116 })
117}