summaryrefslogtreecommitdiff
path: root/crates/typst-library/src
diff options
context:
space:
mode:
authorNiklas Eicker <git@nikl.me>2025-01-08 10:38:34 +0100
committerGitHub <noreply@github.com>2025-01-08 09:38:34 +0000
commit0a374d238016c0101d11cbc3f4bc621f3895ad36 (patch)
tree5799e3c279e70a371fe7d737ac3ff37655827910 /crates/typst-library/src
parent265df6c29f4d142a372917dd708bfba780f7cfbc (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.rs2
-rw-r--r--crates/typst-library/src/pdf/embed.rs131
-rw-r--r--crates/typst-library/src/pdf/mod.rs24
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)
+}