diff options
Diffstat (limited to 'crates/typst-pdf/src/embed.rs')
| -rw-r--r-- | crates/typst-pdf/src/embed.rs | 152 |
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(()) } |
