summaryrefslogtreecommitdiff
path: root/crates/typst-pdf/src/embed.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-pdf/src/embed.rs')
-rw-r--r--crates/typst-pdf/src/embed.rs152
1 files changed, 42 insertions, 110 deletions
diff --git a/crates/typst-pdf/src/embed.rs b/crates/typst-pdf/src/embed.rs
index 597638f4..6ed65a2b 100644
--- a/crates/typst-pdf/src/embed.rs
+++ b/crates/typst-pdf/src/embed.rs
@@ -1,122 +1,54 @@
-use std::collections::BTreeMap;
+use std::sync::Arc;
-use ecow::EcoString;
-use pdf_writer::types::AssociationKind;
-use pdf_writer::{Filter, Finish, Name, Ref, Str, TextStr};
+use krilla::embed::{AssociationKind, EmbeddedFile};
+use krilla::Document;
use typst_library::diag::{bail, SourceResult};
-use typst_library::foundations::{NativeElement, Packed, StyleChain};
+use typst_library::foundations::{NativeElement, StyleChain};
+use typst_library::layout::PagedDocument;
use typst_library::pdf::{EmbedElem, EmbeddedFileRelationship};
-use crate::catalog::{document_date, pdf_date};
-use crate::{deflate, NameExt, PdfChunk, StrExt, WithGlobalRefs};
+pub(crate) fn embed_files(
+ typst_doc: &PagedDocument,
+ document: &mut Document,
+) -> SourceResult<()> {
+ let elements = typst_doc.introspector.query(&EmbedElem::elem().select());
-/// Query for all [`EmbedElem`] and write them and their file specifications.
-///
-/// This returns a map of embedding names and references so that we can later
-/// add them to the catalog's `/Names` dictionary.
-pub fn write_embedded_files(
- ctx: &WithGlobalRefs,
-) -> SourceResult<(PdfChunk, BTreeMap<EcoString, Ref>)> {
- let mut chunk = PdfChunk::new();
- let mut embedded_files = BTreeMap::default();
-
- let elements = ctx.document.introspector.query(&EmbedElem::elem().select());
for elem in &elements {
- if !ctx.options.standards.embedded_files {
- // PDF/A-2 requires embedded files to be PDF/A-1 or PDF/A-2,
- // which we don't currently check.
- bail!(
- elem.span(),
- "file embeddings are not currently supported for PDF/A-2";
- hint: "PDF/A-3 supports arbitrary embedded files"
- );
- }
-
let embed = elem.to_packed::<EmbedElem>().unwrap();
- if embed.path.derived.len() > Str::PDFA_LIMIT {
- bail!(embed.span(), "embedded file path is too long");
- }
-
- let id = embed_file(ctx, &mut chunk, embed)?;
- if embedded_files.insert(embed.path.derived.clone(), id).is_some() {
- bail!(
- elem.span(),
- "duplicate embedded file for path `{}`", embed.path.derived;
- hint: "embedded file paths must be unique",
- );
- }
- }
-
- Ok((chunk, embedded_files))
-}
-
-/// Write the embedded file stream and its file specification.
-fn embed_file(
- ctx: &WithGlobalRefs,
- chunk: &mut PdfChunk,
- embed: &Packed<EmbedElem>,
-) -> SourceResult<Ref> {
- let embedded_file_stream_ref = chunk.alloc.bump();
- let file_spec_dict_ref = chunk.alloc.bump();
-
- let data = embed.data.as_slice();
- let compressed = deflate(data);
-
- let mut embedded_file = chunk.embedded_file(embedded_file_stream_ref, &compressed);
- embedded_file.filter(Filter::FlateDecode);
-
- if let Some(mime_type) = embed.mime_type(StyleChain::default()) {
- if mime_type.len() > Name::PDFA_LIMIT {
- bail!(embed.span(), "embedded file MIME type is too long");
- }
- embedded_file.subtype(Name(mime_type.as_bytes()));
- } else if ctx.options.standards.pdfa {
- bail!(embed.span(), "embedded files must have a MIME type in PDF/A-3");
- }
-
- let mut params = embedded_file.params();
- params.size(data.len() as i32);
-
- let (date, tz) = document_date(ctx.document.info.date, ctx.options.timestamp);
- if let Some(pdf_date) = date.and_then(|date| pdf_date(date, tz)) {
- params.modification_date(pdf_date);
- } else if ctx.options.standards.pdfa {
- bail!(
- embed.span(),
- "the document must have a date when embedding files in PDF/A-3";
- hint: "`set document(date: none)` must not be used in this case"
- );
- }
-
- params.finish();
- embedded_file.finish();
-
- let mut file_spec = chunk.file_spec(file_spec_dict_ref);
- file_spec.path(Str(embed.path.derived.as_bytes()));
- file_spec.unic_file(TextStr(&embed.path.derived));
- file_spec
- .insert(Name(b"EF"))
- .dict()
- .pair(Name(b"F"), embedded_file_stream_ref)
- .pair(Name(b"UF"), embedded_file_stream_ref);
-
- if ctx.options.standards.pdfa {
- // PDF 2.0, but ISO 19005-3 (PDF/A-3) Annex E allows it for PDF/A-3.
- file_spec.association_kind(match embed.relationship(StyleChain::default()) {
- Some(EmbeddedFileRelationship::Source) => AssociationKind::Source,
- Some(EmbeddedFileRelationship::Data) => AssociationKind::Data,
- Some(EmbeddedFileRelationship::Alternative) => AssociationKind::Alternative,
- Some(EmbeddedFileRelationship::Supplement) => AssociationKind::Supplement,
+ let span = embed.span();
+ let derived_path = &embed.path.derived;
+ let path = derived_path.to_string();
+ let mime_type =
+ embed.mime_type(StyleChain::default()).clone().map(|s| s.to_string());
+ let description = embed
+ .description(StyleChain::default())
+ .clone()
+ .map(|s| s.to_string());
+ let association_kind = match embed.relationship(StyleChain::default()) {
None => AssociationKind::Unspecified,
- });
- }
-
- if let Some(description) = embed.description(StyleChain::default()) {
- if description.len() > Str::PDFA_LIMIT {
- bail!(embed.span(), "embedded file description is too long");
+ Some(e) => match e {
+ EmbeddedFileRelationship::Source => AssociationKind::Source,
+ EmbeddedFileRelationship::Data => AssociationKind::Data,
+ EmbeddedFileRelationship::Alternative => AssociationKind::Alternative,
+ EmbeddedFileRelationship::Supplement => AssociationKind::Supplement,
+ },
+ };
+ let data: Arc<dyn AsRef<[u8]> + Send + Sync> = Arc::new(embed.data.clone());
+
+ let file = EmbeddedFile {
+ path,
+ mime_type,
+ description,
+ association_kind,
+ data: data.into(),
+ compress: true,
+ location: Some(span.into_raw().get()),
+ };
+
+ if document.embed_file(file).is_none() {
+ bail!(span, "attempted to embed file {derived_path} twice");
}
- file_spec.description(TextStr(description));
}
- Ok(file_spec_dict_ref)
+ Ok(())
}