Rename pdf.embed to pdf.attach (#6705)

This commit is contained in:
Laurenz 2025-08-07 12:19:47 +02:00 committed by GitHub
parent 1790a27de8
commit c49b9640a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 98 additions and 69 deletions

View File

@ -6,8 +6,8 @@ use ecow::{EcoString, eco_format};
use rustc_hash::FxHashSet;
use serde::{Deserialize, Serialize};
use typst::foundations::{
AutoValue, CastInfo, Func, Label, NoneValue, ParamInfo, Repr, StyleChain, Styles,
Type, Value, fields_on, repr,
AutoValue, CastInfo, Func, Label, NativeElement, NoneValue, ParamInfo, Repr,
StyleChain, Styles, Type, Value, fields_on, repr,
};
use typst::layout::{Alignment, Dir, PagedDocument};
use typst::syntax::ast::AstNode;
@ -852,6 +852,7 @@ fn path_completion(func: &Func, param: &ParamInfo) -> Option<&'static [&'static
(Some("raw"), "syntaxes") => &["sublime-syntax"],
(Some("raw"), "theme") => &["tmtheme"],
(Some("embed"), "path") => &[],
(Some("attach"), "path") if *func == typst::pdf::AttachElem::ELEM => &[],
(None, "path") => &[],
_ => return None,
})
@ -1820,6 +1821,8 @@ mod tests {
.with_source("content/a.typ", "#image()")
.with_source("content/b.typ", "#csv(\"\")")
.with_source("content/c.typ", "#include \"\"")
.with_source("content/d.typ", "#pdf.attach(\"\")")
.with_source("content/e.typ", "#math.attach(\"\")")
.with_asset_at("assets/tiger.jpg", "tiger.jpg")
.with_asset_at("assets/rhino.png", "rhino.png")
.with_asset_at("data/example.csv", "example.csv");
@ -1828,15 +1831,20 @@ mod tests {
.must_include([q!("content/a.typ"), q!("content/b.typ"), q!("utils.typ")])
.must_exclude([q!("assets/tiger.jpg")]);
test(&world, ("content/c.typ", -2))
.must_include([q!("../main.typ"), q!("a.typ"), q!("b.typ")])
.must_exclude([q!("c.typ")]);
test(&world, ("content/a.typ", -2))
.must_include([q!("../assets/tiger.jpg"), q!("../assets/rhino.png")])
.must_exclude([q!("../data/example.csv"), q!("b.typ")]);
test(&world, ("content/b.typ", -3)).must_include([q!("../data/example.csv")]);
test(&world, ("content/c.typ", -2))
.must_include([q!("../main.typ"), q!("a.typ"), q!("b.typ")])
.must_exclude([q!("c.typ")]);
test(&world, ("content/d.typ", -2))
.must_include([q!("../assets/tiger.jpg"), q!("../data/example.csv")]);
test(&world, ("content/e.typ", -2)).must_exclude([q!("data/example.csv")]);
}
#[test]

View File

@ -23,7 +23,7 @@ use typst_library::model::{
LinkElem, ListElem, Outlinable, OutlineElem, OutlineEntry, ParElem, ParbreakElem,
QuoteElem, RefElem, StrongElem, TableCell, TableElem, TermsElem, TitleElem, Works,
};
use typst_library::pdf::EmbedElem;
use typst_library::pdf::AttachElem;
use typst_library::text::{
DecoLine, Decoration, HighlightElem, ItalicToggle, LinebreakElem, LocalName,
OverlineElem, RawElem, RawLine, ScriptKind, ShiftSettings, Smallcaps, SmallcapsElem,
@ -103,7 +103,7 @@ pub fn register(rules: &mut NativeRuleMap) {
rules.register(Paged, EQUATION_RULE);
// PDF.
rules.register(Paged, EMBED_RULE);
rules.register(Paged, ATTACH_RULE);
}
const STRONG_RULE: ShowFn<StrongElem> = |elem, _, styles| {
@ -846,4 +846,4 @@ const EQUATION_RULE: ShowFn<EquationElem> = |elem, _, styles| {
}
};
const EMBED_RULE: ShowFn<EmbedElem> = |_, _, _| Ok(Content::empty());
const ATTACH_RULE: ShowFn<AttachElem> = |_, _, _| Ok(Content::empty());

View File

@ -446,6 +446,15 @@ impl PartialEq<&'static NativeFuncData> for Func {
}
}
impl PartialEq<Element> for Func {
fn eq(&self, other: &Element) -> bool {
match &self.repr {
Repr::Element(elem) => elem == other,
_ => false,
}
}
}
impl From<Repr> for Func {
fn from(repr: Repr) -> Self {
Self { repr, span: Span::detached() }

View File

@ -6,18 +6,18 @@ use crate::diag::At;
use crate::foundations::{Bytes, Cast, Derived, elem};
use crate::introspection::Locatable;
/// A file that will be embedded into the output PDF.
/// A file that will be attached to the output PDF.
///
/// This can be used to distribute additional files that are related to the PDF
/// This can be used to distribute additional files associated with the PDF
/// within it. PDF readers will display the files in a file listing.
///
/// Some international standards use this mechanism to embed machine-readable
/// Some international standards use this mechanism to attach machine-readable
/// data (e.g., ZUGFeRD/Factur-X for invoices) that mirrors the visual content
/// of the PDF.
///
/// # Example
/// ```typ
/// #pdf.embed(
/// #pdf.attach(
/// "experiment.csv",
/// relationship: "supplement",
/// mime-type: "text/csv",
@ -27,11 +27,11 @@ use crate::introspection::Locatable;
///
/// # 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(Locatable)]
pub struct EmbedElem {
/// The [path]($syntax/#paths) of the file to be embedded.
/// - File attachments are not currently supported for PDF/A-2, even if the
/// attached file conforms to PDF/A-1 or PDF/A-2.
#[elem(keywords = ["embed"], Locatable)]
pub struct AttachElem {
/// The [path]($syntax/#paths) of the file to be attached.
///
/// Must always be specified, but is only read from if no data is provided
/// in the following argument.
@ -61,21 +61,21 @@ pub struct EmbedElem {
)]
pub data: Bytes,
/// The relationship of the embedded file to the document.
/// The relationship of the attached file to the document.
///
/// Ignored if export doesn't target PDF/A-3.
pub relationship: Option<EmbeddedFileRelationship>,
pub relationship: Option<AttachedFileRelationship>,
/// The MIME type of the embedded file.
/// The MIME type of the attached file.
pub mime_type: Option<EcoString>,
/// A description for the embedded file.
/// A description for the attached file.
pub description: Option<EcoString>,
}
/// The relationship of an embedded file with the document.
/// The relationship of an attached file with the document.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum EmbeddedFileRelationship {
pub enum AttachedFileRelationship {
/// The PDF document was created from the source file.
Source,
/// The file was used to derive a visual presentation in the PDF.

View File

@ -1,15 +1,21 @@
//! PDF-specific functionality.
mod embed;
mod attach;
pub use self::embed::*;
pub use self::attach::*;
use crate::foundations::{Module, Scope};
use crate::foundations::{Deprecation, Element, Module, Scope};
/// Hook up all `pdf` definitions.
pub fn module() -> Module {
let mut pdf = Scope::deduplicating();
pdf.start_category(crate::Category::Pdf);
pdf.define_elem::<EmbedElem>();
pdf.define_elem::<AttachElem>();
pdf.define("embed", Element::of::<AttachElem>()).deprecated(
// Remember to remove "embed" from `path_completion` when removing this.
Deprecation::new()
.with_message("the name `embed` is deprecated, use `attach` instead")
.with_until("0.15.0"),
);
Module::new("pdf", pdf)
}

View File

@ -5,40 +5,40 @@ use krilla::embed::{AssociationKind, EmbeddedFile};
use typst_library::diag::{SourceResult, bail};
use typst_library::foundations::{NativeElement, StyleChain};
use typst_library::layout::PagedDocument;
use typst_library::pdf::{EmbedElem, EmbeddedFileRelationship};
use typst_library::pdf::{AttachElem, AttachedFileRelationship};
pub(crate) fn embed_files(
pub(crate) fn attach_files(
typst_doc: &PagedDocument,
document: &mut Document,
) -> SourceResult<()> {
let elements = typst_doc.introspector.query(&EmbedElem::ELEM.select());
let elements = typst_doc.introspector.query(&AttachElem::ELEM.select());
for elem in &elements {
let embed = elem.to_packed::<EmbedElem>().unwrap();
let span = embed.span();
let derived_path = &embed.path.derived;
let elem = elem.to_packed::<AttachElem>().unwrap();
let span = elem.span();
let derived_path = &elem.path.derived;
let path = derived_path.to_string();
let mime_type = embed
let mime_type = elem
.mime_type
.get_ref(StyleChain::default())
.as_ref()
.map(|s| s.to_string());
let description = embed
let description = elem
.description
.get_ref(StyleChain::default())
.as_ref()
.map(|s| s.to_string());
let association_kind = match embed.relationship.get(StyleChain::default()) {
let association_kind = match elem.relationship.get(StyleChain::default()) {
None => AssociationKind::Unspecified,
Some(e) => match e {
EmbeddedFileRelationship::Source => AssociationKind::Source,
EmbeddedFileRelationship::Data => AssociationKind::Data,
EmbeddedFileRelationship::Alternative => AssociationKind::Alternative,
EmbeddedFileRelationship::Supplement => AssociationKind::Supplement,
AttachedFileRelationship::Source => AssociationKind::Source,
AttachedFileRelationship::Data => AssociationKind::Data,
AttachedFileRelationship::Alternative => AssociationKind::Alternative,
AttachedFileRelationship::Supplement => AssociationKind::Supplement,
},
};
let data: Arc<dyn AsRef<[u8]> + Send + Sync> = Arc::new(embed.data.clone());
let compress = should_compress(&embed.data);
let data: Arc<dyn AsRef<[u8]> + Send + Sync> = Arc::new(elem.data.clone());
let compress = should_compress(&elem.data);
let file = EmbeddedFile {
path,
@ -51,7 +51,7 @@ pub(crate) fn embed_files(
};
if document.embed_file(file).is_none() {
bail!(span, "attempted to embed file {derived_path} twice");
bail!(span, "attempted to attach file {derived_path} twice");
}
}

View File

@ -25,7 +25,7 @@ use typst_library::visualize::{Geometry, Paint};
use typst_syntax::Span;
use crate::PdfOptions;
use crate::embed::embed_files;
use crate::attach::attach_files;
use crate::image::handle_image;
use crate::link::handle_link;
use crate::metadata::build_metadata;
@ -63,7 +63,7 @@ pub fn convert(
);
convert_pages(&mut gc, &mut document)?;
embed_files(typst_document, &mut document)?;
attach_files(typst_document, &mut document)?;
document.set_outline(build_outline(&gc));
document.set_metadata(build_metadata(&gc));
@ -546,19 +546,19 @@ fn convert_error(
}
}
ValidationError::EmbeddedFile(e, s) => {
// We always set the span for embedded files, so it cannot be detached.
// We always set the span for attached files, so it cannot be detached.
let span = to_span(*s);
match e {
EmbedError::Existence => {
error!(
span, "{prefix} document contains an embedded file";
hint: "embedded files are not supported in this export mode"
span, "{prefix} document contains an attached file";
hint: "file attachments are not supported in this export mode"
)
}
EmbedError::MissingDate => {
error!(
span, "{prefix} document date is missing";
hint: "the document must have a date when embedding files";
hint: "the document must have a date when attaching files";
hint: "`set document(date: none)` must not be used in this case"
)
}

View File

@ -1,7 +1,7 @@
//! Exporting Typst documents to PDF.
mod attach;
mod convert;
mod embed;
mod image;
mod link;
mod metadata;

View File

@ -23,8 +23,8 @@ description: Changes in Typst 0.13.0
would be displayed in italics
- You can now specify which charset should be [covered]($text.font) by which
font family
- The [`pdf.embed`] function lets you embed arbitrary files in the exported
PDF
- The [`pdf.embed`]($pdf.attach) function lets you embed arbitrary files in the
exported PDF
- HTML export is currently under active development. The feature is still _very_
incomplete, but already available for experimentation behind a feature flag.
@ -231,7 +231,8 @@ description: Changes in Typst 0.13.0
- A shebang `#!` at the very start of a file is now ignored
## PDF export
- Added [`pdf.embed`] function for embedding arbitrary files in the exported PDF
- Added [`pdf.embed`]($pdf.attach) function for embedding arbitrary files in the
exported PDF
- Added support for PDF/A-3b export
- The PDF timestamp will now contain the timezone by default

View File

@ -28,9 +28,9 @@ Currently, Typst supports these PDF/A output profiles:
- PDF/A-3b: The basic conformance level of ISO 19005-3. This version of PDF/A is
based on PDF 1.7 and results in archivable PDF files that can contain
arbitrary other related files as [attachments]($pdf.embed). The only
difference between it and PDF/A-2b is the capability to embed
non-PDF/A-conformant files within.
arbitrary other related files as [attachments]($pdf.attach). The only
difference between it and PDF/A-2b is the capability to attach
non-PDF/A-conformant files.
When choosing between exporting PDF/A and regular PDF, keep in mind that PDF/A
files contain additional metadata, and that some readers will prevent the user

View File

@ -1,18 +1,18 @@
// Test file embeddings. The tests here so far are unsatisfactory because we
// Test file attachments. The tests here so far are unsatisfactory because we
// have no PDF testing infrastructure. That should be improved in the future.
--- pdf-embed ---
#pdf.embed("/assets/text/hello.txt")
#pdf.embed(
--- pdf-attach ---
#pdf.attach("/assets/text/hello.txt")
#pdf.attach(
"/assets/data/details.toml",
relationship: "supplement",
mime-type: "application/toml",
description: "Information about a secret project",
)
--- pdf-embed-bytes ---
#pdf.embed("hello.txt", read("/assets/text/hello.txt", encoding: none))
#pdf.embed(
--- pdf-attach-bytes ---
#pdf.attach("hello.txt", read("/assets/text/hello.txt", encoding: none))
#pdf.attach(
"a_file_name.txt",
read("/assets/text/hello.txt", encoding: none),
relationship: "supplement",
@ -20,8 +20,8 @@
description: "A description",
)
--- pdf-embed-invalid-relationship ---
#pdf.embed(
--- pdf-attach-invalid-relationship ---
#pdf.attach(
"/assets/text/hello.txt",
// Error: 17-23 expected "source", "data", "alternative", "supplement", or none
relationship: "test",
@ -29,6 +29,11 @@
description: "A test file",
)
--- pdf-embed-invalid-data ---
// Error: 38-45 expected bytes, found string
#pdf.embed("/assets/text/hello.txt", "hello")
--- pdf-attach-invalid-data ---
// Error: 39-46 expected bytes, found string
#pdf.attach("/assets/text/hello.txt", "hello")
--- pdf-embed-deprecated ---
// Warning: 6-11 the name `embed` is deprecated, use `attach` instead
// Hint: 6-11 it will be removed in Typst 0.15.0
#pdf.embed("/assets/text/hello.txt")