diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index 0abc38400..ab376e0a2 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -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] diff --git a/crates/typst-layout/src/rules.rs b/crates/typst-layout/src/rules.rs index 089b4e671..8bcd66547 100644 --- a/crates/typst-layout/src/rules.rs +++ b/crates/typst-layout/src/rules.rs @@ -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 = |elem, _, styles| { @@ -846,4 +846,4 @@ const EQUATION_RULE: ShowFn = |elem, _, styles| { } }; -const EMBED_RULE: ShowFn = |_, _, _| Ok(Content::empty()); +const ATTACH_RULE: ShowFn = |_, _, _| Ok(Content::empty()); diff --git a/crates/typst-library/src/foundations/func.rs b/crates/typst-library/src/foundations/func.rs index 5b43ad37c..da498a407 100644 --- a/crates/typst-library/src/foundations/func.rs +++ b/crates/typst-library/src/foundations/func.rs @@ -446,6 +446,15 @@ impl PartialEq<&'static NativeFuncData> for Func { } } +impl PartialEq for Func { + fn eq(&self, other: &Element) -> bool { + match &self.repr { + Repr::Element(elem) => elem == other, + _ => false, + } + } +} + impl From for Func { fn from(repr: Repr) -> Self { Self { repr, span: Span::detached() } diff --git a/crates/typst-library/src/pdf/embed.rs b/crates/typst-library/src/pdf/attach.rs similarity index 73% rename from crates/typst-library/src/pdf/embed.rs rename to crates/typst-library/src/pdf/attach.rs index 2b93a591a..50e54add5 100644 --- a/crates/typst-library/src/pdf/embed.rs +++ b/crates/typst-library/src/pdf/attach.rs @@ -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, + pub relationship: Option, - /// The MIME type of the embedded file. + /// The MIME type of the attached file. pub mime_type: Option, - /// A description for the embedded file. + /// A description for the attached file. pub description: Option, } -/// 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. diff --git a/crates/typst-library/src/pdf/mod.rs b/crates/typst-library/src/pdf/mod.rs index 786a36372..7b7f5581f 100644 --- a/crates/typst-library/src/pdf/mod.rs +++ b/crates/typst-library/src/pdf/mod.rs @@ -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::(); + pdf.define_elem::(); + pdf.define("embed", Element::of::()).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) } diff --git a/crates/typst-pdf/src/embed.rs b/crates/typst-pdf/src/attach.rs similarity index 79% rename from crates/typst-pdf/src/embed.rs rename to crates/typst-pdf/src/attach.rs index 203e39607..895fe9ffc 100644 --- a/crates/typst-pdf/src/embed.rs +++ b/crates/typst-pdf/src/attach.rs @@ -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::().unwrap(); - let span = embed.span(); - let derived_path = &embed.path.derived; + let elem = elem.to_packed::().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 + Send + Sync> = Arc::new(embed.data.clone()); - let compress = should_compress(&embed.data); + let data: Arc + 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"); } } diff --git a/crates/typst-pdf/src/convert.rs b/crates/typst-pdf/src/convert.rs index 728bda9e8..5e4003d79 100644 --- a/crates/typst-pdf/src/convert.rs +++ b/crates/typst-pdf/src/convert.rs @@ -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" ) } diff --git a/crates/typst-pdf/src/lib.rs b/crates/typst-pdf/src/lib.rs index 96bbac591..7beeacaa2 100644 --- a/crates/typst-pdf/src/lib.rs +++ b/crates/typst-pdf/src/lib.rs @@ -1,7 +1,7 @@ //! Exporting Typst documents to PDF. +mod attach; mod convert; -mod embed; mod image; mod link; mod metadata; diff --git a/docs/changelog/0.13.0.md b/docs/changelog/0.13.0.md index 1cca48aa2..23ee97e71 100644 --- a/docs/changelog/0.13.0.md +++ b/docs/changelog/0.13.0.md @@ -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 diff --git a/docs/reference/export/pdf.md b/docs/reference/export/pdf.md index b220ae946..21eedc49d 100644 --- a/docs/reference/export/pdf.md +++ b/docs/reference/export/pdf.md @@ -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 diff --git a/tests/suite/pdf/embed.typ b/tests/suite/pdf/attach.typ similarity index 51% rename from tests/suite/pdf/embed.typ rename to tests/suite/pdf/attach.typ index 4546532b7..99d3e5869 100644 --- a/tests/suite/pdf/embed.typ +++ b/tests/suite/pdf/attach.typ @@ -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")