typst_library/pdf/
attach.rs

1use ecow::EcoString;
2use typst_syntax::Spanned;
3
4use crate::World;
5use crate::diag::At;
6use crate::foundations::{Bytes, Cast, Derived, elem};
7use crate::introspection::Locatable;
8
9/// A file that will be attached to the output PDF.
10///
11/// This can be used to distribute additional files associated with the PDF
12/// within it. PDF readers will display the files in a file listing.
13///
14/// Some international standards use this mechanism to attach machine-readable
15/// data (e.g., ZUGFeRD/Factur-X for invoices) that mirrors the visual content
16/// of the PDF.
17///
18/// # Example
19/// ```typ
20/// #pdf.attach(
21///   "experiment.csv",
22///   relationship: "supplement",
23///   mime-type: "text/csv",
24///   description: "Raw Oxygen readings from the Arctic experiment",
25/// )
26/// ```
27///
28/// # Notes
29/// - This element is ignored if exporting to a format other than PDF.
30/// - File attachments are not currently supported for PDF/A-2, even if the
31///   attached file conforms to PDF/A-1 or PDF/A-2.
32#[elem(keywords = ["embed"], Locatable)]
33pub struct AttachElem {
34    /// The [path]($syntax/#paths) of the file to be attached.
35    ///
36    /// Must always be specified, but is only read from if no data is provided
37    /// in the following argument.
38    #[required]
39    #[parse(
40        let Spanned { v: path, span } =
41            args.expect::<Spanned<EcoString>>("path")?;
42        let id = span.resolve_path(&path).at(span)?;
43        // The derived part is the project-relative resolved path.
44        let resolved = id.vpath().as_rootless_path().to_string_lossy().replace("\\", "/").into();
45        Derived::new(path.clone(), resolved)
46    )]
47    pub path: Derived<EcoString, EcoString>,
48
49    /// Raw file data, optionally.
50    ///
51    /// If omitted, the data is read from the specified path.
52    #[positional]
53    // Not actually required as an argument, but always present as a field.
54    // We can't distinguish between the two at the moment.
55    #[required]
56    #[parse(
57        match args.eat::<Bytes>()? {
58            Some(data) => data,
59            None => engine.world.file(id).at(span)?,
60        }
61    )]
62    pub data: Bytes,
63
64    /// The relationship of the attached file to the document.
65    ///
66    /// Ignored if export doesn't target PDF/A-3.
67    pub relationship: Option<AttachedFileRelationship>,
68
69    /// The MIME type of the attached file.
70    pub mime_type: Option<EcoString>,
71
72    /// A description for the attached file.
73    pub description: Option<EcoString>,
74}
75
76/// The relationship of an attached file with the document.
77#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
78pub enum AttachedFileRelationship {
79    /// The PDF document was created from the source file.
80    Source,
81    /// The file was used to derive a visual presentation in the PDF.
82    Data,
83    /// An alternative representation of the document.
84    Alternative,
85    /// Additional resources for the document.
86    Supplement,
87}