diff options
| author | Niklas Eicker <git@nikl.me> | 2025-01-08 10:38:34 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-01-08 09:38:34 +0000 |
| commit | 0a374d238016c0101d11cbc3f4bc621f3895ad36 (patch) | |
| tree | 5799e3c279e70a371fe7d737ac3ff37655827910 /crates/typst-library/src | |
| parent | 265df6c29f4d142a372917dd708bfba780f7cfbc (diff) | |
Embed files associated with the document as a whole (#5221)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
Diffstat (limited to 'crates/typst-library/src')
| -rw-r--r-- | crates/typst-library/src/lib.rs | 2 | ||||
| -rw-r--r-- | crates/typst-library/src/pdf/embed.rs | 131 | ||||
| -rw-r--r-- | crates/typst-library/src/pdf/mod.rs | 24 |
3 files changed, 157 insertions, 0 deletions
diff --git a/crates/typst-library/src/lib.rs b/crates/typst-library/src/lib.rs index 87b2fcb4..2ea77eaa 100644 --- a/crates/typst-library/src/lib.rs +++ b/crates/typst-library/src/lib.rs @@ -21,6 +21,7 @@ pub mod layout; pub mod loading; pub mod math; pub mod model; +pub mod pdf; pub mod routines; pub mod symbols; pub mod text; @@ -249,6 +250,7 @@ fn global(math: Module, inputs: Dict, features: &Features) -> Module { self::introspection::define(&mut global); self::loading::define(&mut global); self::symbols::define(&mut global); + self::pdf::define(&mut global); global.reset_category(); if features.is_enabled(Feature::Html) { global.define_module(self::html::module()); diff --git a/crates/typst-library/src/pdf/embed.rs b/crates/typst-library/src/pdf/embed.rs new file mode 100644 index 00000000..db498622 --- /dev/null +++ b/crates/typst-library/src/pdf/embed.rs @@ -0,0 +1,131 @@ +use ecow::EcoString; +use typst_syntax::{Span, Spanned}; + +use crate::diag::{At, SourceResult, StrResult}; +use crate::engine::Engine; +use crate::foundations::{ + elem, func, scope, Cast, Content, NativeElement, Packed, Show, StyleChain, +}; +use crate::introspection::Locatable; +use crate::loading::Readable; +use crate::World; + +/// A file that will be embedded into the output PDF. +/// +/// This can be used to distribute additional files that are related to the PDF +/// within it. PDF readers will display the files in a file listing. +/// +/// Some international standards use this mechanism to embed machine-readable +/// data (e.g., ZUGFeRD/Factur-X for invoices) that mirrors the visual content +/// of the PDF. +/// +/// # Example +/// ```typ +/// #pdf.embed( +/// "experiment.csv", +/// relationship: "supplement", +/// mime-type: "text/csv", +/// description: "Raw Oxygen readings from the Arctic experiment", +/// ) +/// ``` +/// +/// # Notes +/// - This element is ignored if exporting to a format other than PDF. +/// - File embeddings are not currently supported for PDF/A-2, even if the +/// embedded file conforms to PDF/A-1 or PDF/A-2. +#[elem(scope, Show, Locatable)] +pub struct EmbedElem { + /// Path to a file to be embedded. + /// + /// For more details, see the [Paths section]($syntax/#paths). + #[required] + #[parse( + let Spanned { v: path, span } = + args.expect::<Spanned<EcoString>>("path to the file to be embedded")?; + let id = span.resolve_path(&path).at(span)?; + let data = engine.world.file(id).at(span)?; + path + )] + #[borrowed] + pub path: EcoString, + + /// The resolved project-relative path. + #[internal] + #[required] + #[parse(id.vpath().as_rootless_path().to_string_lossy().replace("\\", "/").into())] + pub resolved_path: EcoString, + + /// The raw file data. + #[internal] + #[required] + #[parse(Readable::Bytes(data))] + pub data: Readable, + + /// The relationship of the embedded file to the document. + /// + /// Ignored if export doesn't target PDF/A-3. + pub relationship: Option<EmbeddedFileRelationship>, + + /// The MIME type of the embedded file. + #[borrowed] + pub mime_type: Option<EcoString>, + + /// A description for the embedded file. + #[borrowed] + pub description: Option<EcoString>, +} + +#[scope] +impl EmbedElem { + /// Decode a file embedding from bytes or a string. + #[func(title = "Embed Data")] + fn decode( + /// The call span of this function. + span: Span, + /// The path that will be written into the PDF. Typst will not read from + /// this path since the data is provided in the following argument. + path: EcoString, + /// The data to embed as a file. + data: Readable, + /// The relationship of the embedded file to the document. + #[named] + relationship: Option<Option<EmbeddedFileRelationship>>, + /// The MIME type of the embedded file. + #[named] + mime_type: Option<Option<EcoString>>, + /// A description for the embedded file. + #[named] + description: Option<Option<EcoString>>, + ) -> StrResult<Content> { + let mut elem = EmbedElem::new(path.clone(), path, data); + if let Some(description) = description { + elem.push_description(description); + } + if let Some(mime_type) = mime_type { + elem.push_mime_type(mime_type); + } + if let Some(relationship) = relationship { + elem.push_relationship(relationship); + } + Ok(elem.pack().spanned(span)) + } +} + +impl Show for Packed<EmbedElem> { + fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> { + Ok(Content::empty()) + } +} + +/// The relationship of an embedded file with the document. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] +pub enum EmbeddedFileRelationship { + /// The PDF document was created from the source file. + Source, + /// The file was used to derive a visual presentation in the PDF. + Data, + /// An alternative representation of the document. + Alternative, + /// Additional resources for the document. + Supplement, +} diff --git a/crates/typst-library/src/pdf/mod.rs b/crates/typst-library/src/pdf/mod.rs new file mode 100644 index 00000000..669835d4 --- /dev/null +++ b/crates/typst-library/src/pdf/mod.rs @@ -0,0 +1,24 @@ +//! PDF-specific functionality. + +mod embed; + +pub use self::embed::*; + +use crate::foundations::{category, Category, Module, Scope}; + +/// PDF-specific functionality. +#[category] +pub static PDF: Category; + +/// Hook up the `pdf` module. +pub(super) fn define(global: &mut Scope) { + global.category(PDF); + global.define_module(module()); +} + +/// Hook up all `pdf` definitions. +pub fn module() -> Module { + let mut scope = Scope::deduplicating(); + scope.define_elem::<EmbedElem>(); + Module::new("pdf", scope) +} |
