tinymist_task/compute/
pdf.rs

1//! The computation for pdf export.
2
3use 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
13/// The computation for pdf export.
14pub 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        // log::info!("used options for pdf export: {options:?}");
33
34        // todo: Some(pdf_uri.as_str())
35        // todo: ident option
36        Ok(Bytes::new(typst_pdf::pdf(doc, &options)?))
37    }
38}
39
40/// Creates PDF options from shared project export arguments.
41pub 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    // todo: this seems different from `Timestamp::new_local` which also embeds the
52    // timezone information.
53    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    // todo: emit warning diag
84    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}