Repository: typst/typst
File: crates/typst-pdf/src/embed.rs (Rust)

Code before:
fn embed_file(ctx: &WithGlobalRefs, chunk: &mut PdfChunk, embed: &Embed) -> Ref {
    let embedded_file_stream_ref = chunk.alloc.bump();
    let file_spec_dict_ref = chunk.alloc.bump();

    let length = embed.data().len();

    let mut embedded_file =
        chunk.embedded_file(embedded_file_stream_ref, embed.data().as_ref());
    embedded_file.pair(Name(b"Length"), length as i32);
    if let Some(mime_type) = embed.mime_type() {
        embedded_file.subtype(Name(mime_type.as_bytes()));
    }
    let date = ctx
        .document
        .info
        .date
        .unwrap_or(ctx.options.timestamp)
        .and_then(|date| pdf_date(date, ctx.document.info.date.is_auto()));
    if let Some(pdf_date) = date {
        embedded_file.params().modification_date(pdf_date).finish();
    }
    embedded_file.finish();

    let mut file_spec = chunk.file_spec(file_spec_dict_ref);
    file_spec
        .path(Str(embed.path().as_bytes()))
        .unic_file(TextStr(embed.path().as_str()))
        .insert(Name(b"EF"))
        .dict()
        .pair(Name(b"F"), embedded_file_stream_ref)
        .pair(Name(b"UF"), embedded_file_stream_ref)
        .finish();
    if let Some(relationship) = embed.relationship() {
        if Some(PdfAConformanceLevel::A_3) == ctx.options.standards.pdfa {
            let name = match relationship {
                EmbeddedFileRelationship::Source => "Source",
                EmbeddedFileRelationship::Data => "Data",
                EmbeddedFileRelationship::Alternative => "Alternative",
                EmbeddedFileRelationship::Supplement => "Supplement",
                EmbeddedFileRelationship::EncryptedPayload => "EncryptedPayload",
                EmbeddedFileRelationship::FormData => "FormData",
                EmbeddedFileRelationship::Schema => "Schema",
                EmbeddedFileRelationship::Unspecified => "Unspecified",
            };
            // PDF 2.0, but ISO 19005-3 (PDF/A-3) Annex E allows it for PDF/A-3
            file_spec.pair(Name(b"AFRelationship"), Name(name.as_bytes()));
        }
    }
    if let Some(description) = embed.description() {
        file_spec.description(TextStr(description));
    }

Review comment (suggestion):
Not sure how much sense it makes to use the raw path here since that one is relative to the file and not to the project root.

Code after:
        if embed.resolved_path.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.resolved_path.clone(), id).is_some() {
            bail!(
                elem.span(),
                "duplicate embedded file for path `{}`", embed.resolved_path;
                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";